UNPKG

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.

537 lines (474 loc) 21 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * 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 _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); 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 }; } var _commonsNodeProcess2; function _commonsNodeProcess() { return _commonsNodeProcess2 = require('../../commons-node/process'); } var _commonsNodeFsPromise2; function _commonsNodeFsPromise() { return _commonsNodeFsPromise2 = _interopRequireDefault(require('../../commons-node/fsPromise')); } 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 _createBuckWebSocket2; function _createBuckWebSocket() { return _createBuckWebSocket2 = _interopRequireDefault(require('./createBuckWebSocket')); } var _nuclideLogging2; function _nuclideLogging() { return _nuclideLogging2 = require('../../nuclide-logging'); } var _ini2; function _ini() { return _ini2 = _interopRequireDefault(require('ini')); } var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)(); /** * As defined in com.facebook.buck.cli.Command, some of Buck's subcommands are * read-only. The read-only commands can be executed in parallel, but the rest * must be executed serially. * * TODO(mbolin): This does not account for the case where the user runs * `buck build` from the command line while Nuclide is also trying to build. */ var BLOCKING_BUCK_COMMAND_QUEUE_PREFIX = 'buck'; /** * Represents a Buck project on disk. All Buck commands for a project should be * done through an instance of this class. * * TODO(hansonw): This should be a stateless set of global functions. * In the meantime, don't introduce any additional state here. */ var BuckProject = (function () { /** * @param options.rootPath Absolute path to the directory that contains the * .buckconfig file to configure the project. */ function BuckProject(options) { _classCallCheck(this, BuckProject); this._rootPath = options.rootPath; this._serialQueueName = BLOCKING_BUCK_COMMAND_QUEUE_PREFIX + this._rootPath; } _createClass(BuckProject, [{ key: 'dispose', value: function dispose() { // This method is required by the service framework. } }, { key: 'getPath', value: function getPath() { return Promise.resolve(this._rootPath); } /** * Given a file path, returns path to the Buck project root i.e. the directory containing * '.buckconfig' file. */ }, { key: 'getBuildFile', /** * Gets the build file for the specified target. */ value: _asyncToGenerator(function* (targetName) { try { var result = yield this.query('buildfile(' + targetName + ')'); if (result.length === 0) { return null; } return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(this._rootPath, result[0]); } catch (e) { logger.error('No build file for target "' + targetName + '" ' + e); return null; } }) /** * @param args Do not include 'buck' as the first argument: it will be added * automatically. */ }, { key: '_runBuckCommandFromProjectRoot', value: function _runBuckCommandFromProjectRoot(args, commandOptions) { var _getBuckCommandAndOptions2 = this._getBuckCommandAndOptions(commandOptions); var pathToBuck = _getBuckCommandAndOptions2.pathToBuck; var options = _getBuckCommandAndOptions2.buckCommandOptions; logger.debug('Buck command:', pathToBuck, args, options); return (0, (_commonsNodeProcess2 || _commonsNodeProcess()).checkOutput)(pathToBuck, args, options); } /** * @return The path to buck and set of options to be used to run a `buck` command. */ }, { key: '_getBuckCommandAndOptions', value: function _getBuckCommandAndOptions() { var commandOptions = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; // $UPFixMe: This should use nuclide-features-config var pathToBuck = global.atom && global.atom.config.get('nuclide.nuclide-buck.pathToBuck') || 'buck'; var buckCommandOptions = _extends({ cwd: this._rootPath, queueName: this._serialQueueName }, commandOptions); return { pathToBuck: pathToBuck, buckCommandOptions: buckCommandOptions }; } /** * Returns an array of strings (that are build targets) by running: * * buck audit owner <path> * * @param filePath absolute path or a local or a remote file. * @return Promise that resolves to an array of build targets. */ }, { key: 'getOwner', value: _asyncToGenerator(function* (filePath) { var args = ['audit', 'owner', filePath]; var result = yield this._runBuckCommandFromProjectRoot(args); var stdout = result.stdout.trim(); if (stdout === '') { return []; } return stdout.split('\n'); }) /** * Reads the configuration file for the Buck project and returns the requested property. * * @param section Section in the configuration file. * @param property Configuration option within the section. * * @return Promise that resolves to the value, if it is set, else `null`. */ }, { key: 'getBuckConfig', value: _asyncToGenerator(function* (section, property) { var buckConfig = this._buckConfig; if (!buckConfig) { buckConfig = this._buckConfig = yield this._loadBuckConfig(); } if (!buckConfig.hasOwnProperty(section)) { return null; } var sectionConfig = buckConfig[section]; if (!sectionConfig.hasOwnProperty(property)) { return null; } return sectionConfig[property]; }) /** * TODO(natthu): Also load .buckconfig.local. Consider loading .buckconfig from the home directory * and ~/.buckconfig.d/ directory. */ }, { key: '_loadBuckConfig', value: _asyncToGenerator(function* () { var header = 'scope = global\n'; var buckConfigContent = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.readFile((_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.join(this._rootPath, '.buckconfig')); return (_ini2 || _ini()).default.parse(header + buckConfigContent); }) /** * Runs `buck build --keep-going --build-report <tempfile>` with the specified targets. Regardless * whether the build is successful, this returns the parsed version of the JSON report * produced by the {@code --build-report} option: * http://facebook.github.io/buck/command/build.html. * * An error should be thrown only if the specified targets are invalid. * @return Promise that resolves to a build report. */ }, { key: 'build', value: function build(buildTargets, options) { return this._build(buildTargets, options || {}); } /** * Runs `buck install --keep-going --build-report <tempfile>` with the specified targets. * * @param run If set to 'true', appends the buck invocation with '--run' to run the * installed application. * @param debug If set to 'true', appends the buck invocation with '--wait-for-debugger' * telling the launched application to stop at the loader breakpoint * waiting for debugger to connect * @param simulator The UDID of the simulator to install the binary on. * @return Promise that resolves to a build report. */ }, { key: 'install', value: function install(buildTargets, simulator, runOptions) { return this._build(buildTargets, { install: true, simulator: simulator, runOptions: runOptions }); } }, { key: '_build', value: _asyncToGenerator(function* (buildTargets, options) { var report = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.tempfile({ suffix: '.json' }); var args = this._translateOptionsToBuckBuildArgs({ baseOptions: _extends({}, options), pathToBuildReport: report, buildTargets: buildTargets }); try { yield this._runBuckCommandFromProjectRoot(args, options.commandOptions); } catch (e) { // The build failed. However, because --keep-going was specified, the // build report should have still been written unless any of the target // args were invalid. We check the contents of the report file to be sure. var stat = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.stat(report).catch(function () { return null; }); if (stat == null || stat.size === 0) { throw e; } } try { var json = yield (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.readFile(report, { encoding: 'UTF-8' }); try { return JSON.parse(json); } catch (e) { throw Error('Failed to parse:\n' + json); } } finally { (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.unlink(report); } }) /** * Same as `build`, but returns additional output via an Observable. * @return An Observable with the following implementations: * onNext: Calls the Observer with successive strings from stdout and stderr. * Each update will be of the form: {stdout: string;} | {stderr: string;} * TODO: Use a union to exactly match `{stdout: string;} | {stderr: string;}` when the service * framework supports it. Use an object with optional keys to mimic the union. * onError: If the build fails, calls the Observer with the string output * from stderr. * onCompleted: Only called if the build completes successfully. */ }, { key: 'buildWithOutput', value: function buildWithOutput(buildTargets, extraArguments) { return this._buildWithOutput(buildTargets, { extraArguments: extraArguments }).publish(); } /** * Same as `build`, but returns additional output via an Observable. * @return An Observable with the following implementations: * onNext: Calls the Observer with successive strings from stdout and stderr. * Each update will be of the form: {stdout: string;} | {stderr: string;} * TODO: Use a union to exactly match `{stdout: string;} | {stderr: string;}` when the service * framework supports it. Use an object with optional keys to mimic the union. * onError: If the build fails, calls the Observer with the string output * from stderr. * onCompleted: Only called if the build completes successfully. */ }, { key: 'testWithOutput', value: function testWithOutput(buildTargets, extraArguments) { return this._buildWithOutput(buildTargets, { test: true, extraArguments: extraArguments }).publish(); } /** * Same as `install`, but returns additional output via an Observable. * @return An Observable with the following implementations: * onNext: Calls the Observer with successive strings from stdout and stderr. * Each update will be of the form: {stdout: string;} | {stderr: string;} * TODO: Use a union to exactly match `{stdout: string;} | {stderr: string;}` when the service * framework supports it. Use an object with optional keys to mimic the union. * onError: If the install fails, calls the Observer with the string output * from stderr. * onCompleted: Only called if the install completes successfully. */ }, { key: 'installWithOutput', value: function installWithOutput(buildTargets, extraArguments, simulator, runOptions) { return this._buildWithOutput(buildTargets, { install: true, simulator: simulator, runOptions: runOptions, extraArguments: extraArguments }).publish(); } /** * Does a build/install. * @return An Observable that returns output from buck, as described by the * docblocks for `buildWithOutput` and `installWithOutput`. */ }, { key: '_buildWithOutput', value: function _buildWithOutput(buildTargets, options) { var args = this._translateOptionsToBuckBuildArgs({ baseOptions: _extends({}, options), buildTargets: buildTargets }); var _getBuckCommandAndOptions3 = this._getBuckCommandAndOptions(); var pathToBuck = _getBuckCommandAndOptions3.pathToBuck; var buckCommandOptions = _getBuckCommandAndOptions3.buckCommandOptions; return (0, (_commonsNodeProcess2 || _commonsNodeProcess()).observeProcess)(function () { return (0, (_commonsNodeProcess2 || _commonsNodeProcess()).safeSpawn)(pathToBuck, args, buckCommandOptions); }); } /** * @param options An object describing the desired buck build operation. * @return An array of strings that can be passed as `args` to spawn a * process to run the `buck` command. */ }, { key: '_translateOptionsToBuckBuildArgs', value: function _translateOptionsToBuckBuildArgs(options) { var baseOptions = options.baseOptions; var pathToBuildReport = options.pathToBuildReport; var buildTargets = options.buildTargets; var install = baseOptions.install; var simulator = baseOptions.simulator; var test = baseOptions.test; var extraArguments = baseOptions.extraArguments; var runOptions = baseOptions.runOptions || { run: false }; var args = [test ? 'test' : install ? 'install' : 'build']; args = args.concat(buildTargets); args.push('--keep-going'); if (pathToBuildReport) { args = args.concat(['--build-report', pathToBuildReport]); } if (install) { if (simulator) { args.push('--udid'); args.push(simulator); } if (runOptions.run) { args.push('--run'); if (runOptions.debug) { args.push('--wait-for-debugger'); } } } if (extraArguments != null) { args = args.concat(extraArguments); } return args; } }, { key: 'listAliases', value: _asyncToGenerator(function* () { var args = ['audit', 'alias', '--list']; var result = yield this._runBuckCommandFromProjectRoot(args); var stdout = result.stdout.trim(); return stdout ? stdout.split('\n') : []; }) /** * Currently, if `aliasOrTarget` contains a flavor, this will fail. */ }, { key: 'resolveAlias', value: _asyncToGenerator(function* (aliasOrTarget) { var args = ['targets', '--resolve-alias', aliasOrTarget]; var result = yield this._runBuckCommandFromProjectRoot(args); return result.stdout.trim(); }) /** * Returns the build output metadata for the given target. * This will contain one element if the target is unique; otherwise it will * contain data for all the targets (e.g. for //path/to/targets:) * * The build output path is typically contained in the 'buck.outputPath' key. */ }, { key: 'showOutput', value: _asyncToGenerator(function* (aliasOrTarget) { var args = ['targets', '--json', '--show-output', aliasOrTarget]; var result = yield this._runBuckCommandFromProjectRoot(args); return JSON.parse(result.stdout.trim()); }) }, { key: 'buildRuleTypeFor', value: _asyncToGenerator(function* (aliasOrTarget) { var canonicalName = aliasOrTarget; // The leading "//" can be omitted for build/test/etc, but not for query. // Don't prepend this for aliases though (aliases will not have colons) if (canonicalName.indexOf(':') !== -1 && !canonicalName.startsWith('//')) { canonicalName = '//' + canonicalName; } // Buck query does not support flavors. var flavorIndex = canonicalName.indexOf('#'); if (flavorIndex !== -1) { canonicalName = canonicalName.substr(0, flavorIndex); } var args = ['query', canonicalName, '--json', '--output-attributes', 'buck.type']; var result = yield this._runBuckCommandFromProjectRoot(args); var json = JSON.parse(result.stdout); // If aliasOrTarget is an alias, targets[0] will be the fully qualified build target. var targets = Object.keys(json); // "target:" rules build all rules in that particular BUCK file. // Let's just choose the first one. if (!targets || !canonicalName.endsWith(':') && targets.length !== 1) { throw new Error('Error determining rule type of \'' + aliasOrTarget + '\'.'); } return json[targets[0]]['buck.type']; }) }, { key: 'getHTTPServerPort', value: _asyncToGenerator(function* () { var args = ['server', 'status', '--json', '--http-port']; var result = yield this._runBuckCommandFromProjectRoot(args); var json = JSON.parse(result.stdout); return json['http.port']; }) /** Runs `buck query --json` with the specified query. */ }, { key: 'query', value: _asyncToGenerator(function* (_query) { var args = ['query', '--json', _query]; var result = yield this._runBuckCommandFromProjectRoot(args); var json = JSON.parse(result.stdout); return json; }) /** * Runs `buck query --json` with a query that contains placeholders and therefore expects * arguments. * @param query Should contain '%s' placeholders. * @param args Should be a list of build targets or aliases. The query will be run for each arg. * It will be substituted for '%s' when it is run. * @return object where each arg in args will be a key. Its corresponding value will be the list * of matching build targets in its results. */ }, { key: 'queryWithArgs', value: _asyncToGenerator(function* (query, args) { var completeArgs = ['query', '--json', query].concat(args); var result = yield this._runBuckCommandFromProjectRoot(completeArgs); var json = JSON.parse(result.stdout); // `buck query` does not include entries in the JSON for params that did not match anything. We // massage the output to ensure that every argument has an entry in the output. for (var arg of args) { if (!json.hasOwnProperty(arg)) { json[arg] = []; } } return json; }) // TODO: Nuclide's RPC framework won't allow BuckWebSocketMessage here unless we cover // all possible message types. For now, we'll manually typecast at the callsite. }, { key: 'getWebSocketStream', value: function getWebSocketStream(httpPort) { return (0, (_createBuckWebSocket2 || _createBuckWebSocket()).default)(this, httpPort).publish(); } }], [{ key: 'getRootForPath', value: function getRootForPath(file) { return (_commonsNodeFsPromise2 || _commonsNodeFsPromise()).default.findNearestFile('.buckconfig', file); } }]); return BuckProject; })(); exports.BuckProject = BuckProject; // The service framework doesn't support imported types /* AsyncExecuteOptions */