atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
527 lines (460 loc) • 20.9 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
var _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
var _fs2;
function _fs() {
return _fs2 = _interopRequireDefault(require('fs'));
}
var _os2;
function _os() {
return _os2 = _interopRequireDefault(require('os'));
}
var _commonsNodeNuclideUri2;
function _commonsNodeNuclideUri() {
return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri'));
}
var _rxjsBundlesRxUmdMinJs2;
function _rxjsBundlesRxUmdMinJs() {
return _rxjsBundlesRxUmdMinJs2 = require('rxjs/bundles/Rx.umd.min.js');
}
var _shellQuote2;
function _shellQuote() {
return _shellQuote2 = require('shell-quote');
}
var _nuclideAnalytics2;
function _nuclideAnalytics() {
return _nuclideAnalytics2 = require('../../nuclide-analytics');
}
var _commonsNodeFsPromise2;
function _commonsNodeFsPromise() {
return _commonsNodeFsPromise2 = _interopRequireDefault(require('../../commons-node/fsPromise'));
}
var _nuclideLogging2;
function _nuclideLogging() {
return _nuclideLogging2 = require('../../nuclide-logging');
}
var _nuclideBuckRpc2;
function _nuclideBuckRpc() {
return _nuclideBuckRpc2 = require('../../nuclide-buck-rpc');
}
var _utils2;
function _utils() {
return _utils2 = require('./utils');
}
var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)();
var BUCK_TIMEOUT = 5 * 60 * 1000;
var COMPILATION_DATABASE_FILE = 'compile_commands.json';
/**
* Facebook puts all headers in a <target>:__default_headers__ build target by default.
* This target will never produce compilation flags, so make sure to ignore it.
*/
var DEFAULT_HEADERS_TARGET = '__default_headers__';
var CLANG_FLAGS_THAT_TAKE_PATHS = new Set(['-F', '-I', '-include', '-iquote', '-isysroot', '-isystem']);
var SINGLE_LETTER_CLANG_FLAGS_THAT_TAKE_PATHS = new Set(Array.from(CLANG_FLAGS_THAT_TAKE_PATHS).filter(function (item) {
return item.length === 2;
}));
var INCLUDE_SEARCH_TIMEOUT = 15000;
var _overrideIncludePath = undefined;
function overrideIncludePath(src) {
if (_overrideIncludePath === undefined) {
_overrideIncludePath = null;
try {
// $FlowFB
_overrideIncludePath = require('./fb/custom-flags').overrideIncludePath;
} catch (e) {
// open-source version
}
}
if (_overrideIncludePath != null) {
return _overrideIncludePath(src);
}
return src;
}
var ClangFlagsManager = (function () {
function ClangFlagsManager() {
_classCallCheck(this, ClangFlagsManager);
this._pathToFlags = new Map();
this._cachedBuckProjects = new Map();
this._cachedBuckFlags = new Map();
this._compilationDatabases = new Set();
this._realpathCache = {};
this._flagFileObservables = new Map();
this._flagsChanged = new Set();
this._subscriptions = [];
}
_createDecoratedClass(ClangFlagsManager, [{
key: 'reset',
value: function reset() {
this._pathToFlags.clear();
this._cachedBuckProjects.clear();
this._cachedBuckFlags.clear();
this._compilationDatabases.clear();
this._realpathCache = {};
this._flagFileObservables.clear();
this._flagsChanged.clear();
this._subscriptions.forEach(function (s) {
return s.unsubscribe();
});
this._subscriptions = [];
}
}, {
key: '_getBuckProject',
value: _asyncToGenerator(function* (src) {
// For now, if a user requests the flags for a path outside of a Buck project,
// such as /Applications/Xcode.app/Contents/Developer/Platforms/..., then
// return null. Going forward, we probably want to special-case some of the
// paths under /Applications/Xcode.app so that click-to-symbol works in
// files like Frameworks/UIKit.framework/Headers/UIImage.h.
var buckProjectRoot = yield (_nuclideBuckRpc2 || _nuclideBuckRpc()).BuckProject.getRootForPath(src);
if (buckProjectRoot == null) {
logger.info('Did not try to attempt to get flags from Buck because ' + 'source file %s does not appear to be part of a Buck project.', src);
return null;
}
if (this._cachedBuckProjects.has(buckProjectRoot)) {
return this._cachedBuckProjects.get(buckProjectRoot);
}
var buckProject = new (_nuclideBuckRpc2 || _nuclideBuckRpc()).BuckProject({ rootPath: buckProjectRoot });
this._cachedBuckProjects.set(buckProjectRoot, buckProject);
return buckProject;
})
}, {
key: 'getFlagsChanged',
value: function getFlagsChanged(src) {
return this._flagsChanged.has(src);
}
/**
* @return a space-delimited string of flags or null if nothing is known
* about the src file. For example, null will be returned if src is not
* under the project root.
*/
}, {
key: 'getFlagsForSrc',
value: _asyncToGenerator(function* (src) {
var _this = this;
var data = yield this._getFlagsForSrcCached(src);
if (data == null) {
return null;
}
if (data.flags === undefined) {
var _rawData = data.rawData;
data.flags = _rawData == null ? null : ClangFlagsManager.sanitizeCommand(_rawData.file, _rawData.flags, _rawData.directory);
// Subscribe to changes.
this._subscriptions.push(data.changes.subscribe({
next: function next(change) {
_this._flagsChanged.add(src);
},
error: function error() {}
}));
}
return data.flags;
})
}, {
key: '_getFlagsForSrcCached',
value: _asyncToGenerator(function* (src) {
var cached = this._pathToFlags.get(src);
if (cached == null) {
cached = this._getFlagsForSrcImpl(src);
this._pathToFlags.set(src, cached);
}
return cached;
})
}, {
key: '_getFlagsForSrcImpl',
decorators: [(0, (_nuclideAnalytics2 || _nuclideAnalytics()).trackTiming)('nuclide-clang.get-flags')],
value: _asyncToGenerator(function* (src) {
// Look for a manually provided compilation database.
var dbDir = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.findNearestFile(COMPILATION_DATABASE_FILE, (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.dirname(src));
if (dbDir != null) {
var dbFile = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(dbDir, COMPILATION_DATABASE_FILE);
var dbFlags = yield this._loadFlagsFromCompilationDatabase(dbFile);
var _flags = dbFlags.get(src);
if (_flags != null) {
return _flags;
}
}
var buckFlags = yield this._loadFlagsFromBuck(src);
if ((0, (_utils2 || _utils()).isHeaderFile)(src)) {
// Accept flags from any source file in the target.
if (buckFlags.size > 0) {
return buckFlags.values().next().value;
}
// Try finding flags for a related source file.
var projectRoot = (yield (_nuclideBuckRpc2 || _nuclideBuckRpc()).BuckProject.getRootForPath(src)) || dbDir;
// If we don't have a .buckconfig or a compile_commands.json, we won't find flags regardless.
if (projectRoot == null) {
return null;
}
var sourceFile = yield ClangFlagsManager._findSourceFileForHeader(src, projectRoot);
if (sourceFile != null) {
return this._getFlagsForSrcCached(sourceFile);
}
}
var flags = buckFlags.get(src);
if (flags != null) {
return flags;
}
// Even if we can't get flags, try to watch the build file in case they get added.
var buildFile = yield ClangFlagsManager._guessBuildFile(src);
if (buildFile != null) {
return {
rawData: null,
changes: this._watchFlagFile(buildFile)
};
}
return null;
})
}, {
key: '_loadFlagsFromCompilationDatabase',
value: _asyncToGenerator(function* (dbFile) {
var _this2 = this;
var flags = new Map();
if (this._compilationDatabases.has(dbFile)) {
return flags;
}
try {
yield* (function* () {
var contents = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.readFile(dbFile);
var data = JSON.parse(contents);
(0, (_assert2 || _assert()).default)(data instanceof Array);
var changes = _this2._watchFlagFile(dbFile);
yield Promise.all(data.map(_asyncToGenerator(function* (entry) {
var command = entry.command;
var file = entry.file;
var directory = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.realpath(entry.directory, _this2._realpathCache);
var args = ClangFlagsManager.parseArgumentsFromCommand(command);
var filename = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.resolve(directory, file);
if (yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.exists(filename)) {
var realpath = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.realpath(filename, _this2._realpathCache);
var result = {
rawData: {
flags: args,
file: file,
directory: directory
},
changes: changes
};
flags.set(realpath, result);
_this2._pathToFlags.set(realpath, Promise.resolve(result));
}
})));
_this2._compilationDatabases.add(dbFile);
})();
} catch (e) {
logger.error('Error reading compilation flags from ' + dbFile, e);
}
return flags;
})
}, {
key: '_loadFlagsFromBuck',
value: _asyncToGenerator(function* (src) {
var buckProject = yield this._getBuckProject(src);
if (!buckProject) {
return new Map();
}
var target = (yield buckProject.getOwner(src)).find(function (x) {
return x.indexOf(DEFAULT_HEADERS_TARGET) === -1;
});
if (target == null) {
return new Map();
}
var buckProjectRoot = yield buckProject.getPath();
var key = buckProjectRoot + ':' + target;
var cached = this._cachedBuckFlags.get(key);
if (cached != null) {
return cached;
}
cached = this._loadFlagsForBuckTarget(buckProject, buckProjectRoot, target);
this._cachedBuckFlags.set(key, cached);
return cached;
})
}, {
key: '_loadFlagsForBuckTarget',
value: _asyncToGenerator(function* (buckProject, buckProjectRoot, target) {
var _this3 = this;
// TODO(mbolin): The architecture should be chosen from a dropdown menu like
// it is in Xcode rather than hardcoding things to iphonesimulator-x86_64.
var arch = undefined;
if (process.platform === 'darwin') {
arch = 'iphonesimulator-x86_64';
} else {
arch = 'default';
}
var buildTarget = target + '#compilation-database,' + arch;
// Since this is a background process, limit the number of threads to avoid
// impacting the user too badly.
var maxCpus = Math.ceil((_os2 || _os()).default.cpus().length / 2);
var buildReport = yield buckProject.build([buildTarget, '-j', String(maxCpus)], { commandOptions: { timeout: BUCK_TIMEOUT } });
if (!buildReport.success) {
var error = 'Failed to build ' + buildTarget;
logger.error(error);
throw error;
}
var pathToCompilationDatabase = buildReport.results[buildTarget].output;
pathToCompilationDatabase = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(buckProjectRoot, pathToCompilationDatabase);
var compilationDatabase = JSON.parse((yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.readFile(pathToCompilationDatabase, 'utf8')));
var flags = new Map();
var buildFile = yield buckProject.getBuildFile(target);
var changes = buildFile == null ? (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.empty() : this._watchFlagFile(buildFile);
compilationDatabase.forEach(function (item) {
var file = item.file;
var result = {
rawData: {
flags: item.arguments,
file: file,
directory: buckProjectRoot
},
changes: changes
};
flags.set(file, result);
_this3._pathToFlags.set(file, Promise.resolve(result));
});
return flags;
})
}, {
key: '_watchFlagFile',
value: function _watchFlagFile(flagFile) {
var existing = this._flagFileObservables.get(flagFile);
if (existing != null) {
return existing;
}
var flagFileDir = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.dirname(flagFile);
var flagFileBase = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.basename(flagFile);
var observable = (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.create(function (obs) {
var watcher = (_fs2 || _fs()).default.watch(flagFileDir, {}, function (event, filename) {
if (filename === flagFileBase) {
obs.next(event);
}
});
watcher.on('error', function (err) {
logger.error('Could not watch file ' + flagFile, err);
obs.error(err);
});
return {
unsubscribe: function unsubscribe() {
watcher.close();
}
};
}).share();
this._flagFileObservables.set(flagFile, observable);
return observable;
}
// The file may be new. Look for a nearby BUCK or TARGETS file.
}], [{
key: '_guessBuildFile',
value: _asyncToGenerator(function* (file) {
var dir = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.dirname(file);
var bestMatch = null;
yield Promise.all(['BUCK', 'TARGETS', 'compile_commands.json'].map(_asyncToGenerator(function* (name) {
var nearestDir = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.findNearestFile(name, dir);
if (nearestDir != null) {
var match = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(nearestDir, name);
// Return the closest (most specific) match.
if (bestMatch == null || match.length > bestMatch.length) {
bestMatch = match;
}
}
})));
return bestMatch;
})
}, {
key: 'parseArgumentsFromCommand',
value: function parseArgumentsFromCommand(command) {
var result = [];
// shell-quote returns objects for things like pipes.
// This should never happen with proper flags, but ignore them to be safe.
for (var arg of (0, (_shellQuote2 || _shellQuote()).parse)(command)) {
if (typeof arg !== 'string') {
break;
}
result.push(arg);
}
return result;
}
}, {
key: 'sanitizeCommand',
value: function sanitizeCommand(sourceFile, args_, basePath) {
var args = args_;
// For safety, create a new copy of the array. We exclude the path to the file to compile from
// compilation database generated by Buck. It must be removed from the list of command-line
// arguments passed to libclang.
var normalizedSourceFile = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.normalize(sourceFile);
args = args.filter(function (arg) {
return normalizedSourceFile !== arg && normalizedSourceFile !== (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.resolve(basePath, arg);
});
// Resolve relative path arguments against the Buck project root.
args.forEach(function (arg, argIndex) {
if (CLANG_FLAGS_THAT_TAKE_PATHS.has(arg)) {
var nextIndex = argIndex + 1;
var filePath = overrideIncludePath(args[nextIndex]);
if (!(_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.isAbsolute(filePath)) {
filePath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(basePath, filePath);
}
args[nextIndex] = filePath;
} else if (SINGLE_LETTER_CLANG_FLAGS_THAT_TAKE_PATHS.has(arg.substring(0, 2))) {
var filePath = overrideIncludePath(arg.substring(2));
if (!(_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.isAbsolute(filePath)) {
filePath = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(basePath, filePath);
}
args[argIndex] = arg.substring(0, 2) + filePath;
}
});
// If an output file is specified, remove that argument.
var index = args.indexOf('-o');
if (index !== -1) {
args.splice(index, 2);
}
return args;
}
}, {
key: '_findSourceFileForHeader',
value: _asyncToGenerator(function* (header, projectRoot) {
// Basic implementation: look at files in the same directory for paths
// with matching file names.
var dir = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.dirname(header);
var files = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.readdir(dir);
var basename = ClangFlagsManager._getFileBasename(header);
for (var _file of files) {
if ((0, (_utils2 || _utils()).isSourceFile)(_file) && ClangFlagsManager._getFileBasename(_file) === basename) {
return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(dir, _file);
}
}
// Try searching all subdirectories for source files that include this header.
// Give up after INCLUDE_SEARCH_TIMEOUT.
return (0, (_utils2 || _utils()).findIncludingSourceFile)(header, projectRoot).timeout(INCLUDE_SEARCH_TIMEOUT).catch(function () {
return (_rxjsBundlesRxUmdMinJs2 || _rxjsBundlesRxUmdMinJs()).Observable.of(null);
}).toPromise();
})
// Strip off the extension and conventional suffixes like "Internal" and "-inl".
}, {
key: '_getFileBasename',
value: function _getFileBasename(file) {
var basename = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.basename(file);
var ext = basename.lastIndexOf('.');
if (ext !== -1) {
basename = basename.substr(0, ext);
}
return basename.replace(/(Internal|-inl)$/, '');
}
}]);
return ClangFlagsManager;
})();
module.exports = ClangFlagsManager;
// Will be computed and memoized from rawData on demand.
// Emits file change events for the underlying flags file.
// (rename, change)
// Watch config files (TARGETS/BUCK/compile_commands.json) for changes.