diff --git a/pkg/nuclide-rust/lib/BuckIntegration.js b/pkg/nuclide-rust/lib/BuckIntegration.js new file mode 100644 index 00000000000..6c91bb892c9 --- /dev/null +++ b/pkg/nuclide-rust/lib/BuckIntegration.js @@ -0,0 +1,85 @@ +/** + * 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. + * + * @flow + * @format + */ + +import type {TaskInfo} from '../../nuclide-buck/lib/types'; +import type { + AtomLanguageService, + LanguageService, +} from '../../nuclide-language-service'; + +import invariant from 'invariant'; +import {getLogger} from 'log4js'; +import fsPromise from 'nuclide-commons/fsPromise'; +import nuclideUri from 'nuclide-commons/nuclideUri'; +import {getRustInputs, getSaveAnalysisTargets, normalizeNameForBuckQuery} from './BuckUtils'; + +import * as BuckService from '../../nuclide-buck-rpc'; + +const logger = getLogger('nuclide-rust'); + +export async function updateRlsBuildForTask( + task: TaskInfo, + service: AtomLanguageService, +) { + const buildTarget = normalizeNameForBuckQuery(task.buildTarget); + + const files = await getRustInputs(task.buckRoot, buildTarget); + // Probably not a Rust build target, ignore + if (files.length == 0) + return; + // We need only to pick a representative file to get a related lang service + const fileUri = task.buckRoot + '/' + files[0]; + atom.notifications.addInfo(`fileUri: ${fileUri}`); + + const langService = await service.getLanguageServiceForUri(fileUri); + invariant(langService != null); + + // Since `buck` execution is not trivial - the command may be overriden, needs + // to inherit the environment, passes internal FB USER to env etc. the RLS + // can't just invoke that. + // Instead, we build now, copy paths to resulting .json analysis artifacts to + // a temp file and just use `cat $TMPFILE` as a dummy build command. + const analysisTargets = await getSaveAnalysisTargets(task.buckRoot, buildTarget); + logger.debug(`analysisTargets: ${analysisTargets.join('\n')}`); + let artifacts: Array = []; + + const buildReport = await BuckService.build(task.buckRoot, analysisTargets); + if (buildReport.success === false) { + atom.notifications.addError("save-analysis build failed"); + return; + } + + Object.values(buildReport.results) + // TODO: https://buckbuild.com/command/build.html specifies that for + // FETCHED_FROM_CACHE we might not get an output file - can we force it + // somehow? Or we always locally produce a save-analysis .json file? + .forEach((targetReport: any) => artifacts.push(targetReport.output)); + + const tempfile = await fsPromise.tempfile(); + await fsPromise.writeFile(tempfile, artifacts.join('\n')); + + // TODO: Windows? + const buildCommand = `cat ${tempfile}`; + + logger.debug(`Built SA artifacts: ${artifacts.join('\n')}`); + logger.debug(`buildCommand: ${buildCommand}`); + + langService.sendLspNotification(fileUri, 'workspace/didChangeConfiguration', + { + settings: { + rust: { + unstable_features: true, // Required for build_command + build_on_save: true, + build_command: buildCommand, // TODO: Only in RLS branch: https://github.com/Xanewok/rls/tree/external-build + } + } + }); +} diff --git a/pkg/nuclide-rust/lib/BuckUtils.js b/pkg/nuclide-rust/lib/BuckUtils.js new file mode 100644 index 00000000000..c0440c125a2 --- /dev/null +++ b/pkg/nuclide-rust/lib/BuckUtils.js @@ -0,0 +1,50 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +import type {TaskInfo} from '../../nuclide-buck/lib/types'; + +import * as BuckService from '../../nuclide-buck-rpc'; + +export function getRustInputs(buckRoot: string, buildTarget: BuildTarget): Promise> { + return BuckService. + query(buckRoot, `filter('.*\\.rs$', inputs('${buildTarget}'))`, []); +} + +export function getSaveAnalysisTargets(buckRoot: string, buildTarget: BuildTarget): Promise> { + // Save-analysis build flavor is only supported by rust_{binary, library} + // kinds (so exclude prebuilt_rust_library kind) + const query: string = `kind('^rust_.*', deps(${buildTarget}))`; + + return BuckService.query(buckRoot, query, []).then(deps => + deps.map(dep => dep + '#save-analysis'), + ); +} + +export type BuildTarget = string; + +// FIXME: Copied from nuclide-buck-rpc +// Buck query doesn't allow omitting // or adding # for flavors, this needs to be fixed in buck. +export function normalizeNameForBuckQuery(aliasOrTarget: string): BuildTarget { + let canonicalName = aliasOrTarget; + // Don't prepend // for aliases (aliases will not have colons or .) + if ( + (canonicalName.indexOf(':') !== -1 || canonicalName.indexOf('.') !== -1) && + canonicalName.indexOf('//') === -1 + ) { + canonicalName = '//' + canonicalName; + } + // Strip flavor string + const flavorIndex = canonicalName.indexOf('#'); + if (flavorIndex !== -1) { + canonicalName = canonicalName.substr(0, flavorIndex); + } + return canonicalName; +} diff --git a/pkg/nuclide-rust/lib/RustLanguage.js b/pkg/nuclide-rust/lib/RustLanguage.js index abdc42f0ce4..05978ecbd67 100644 --- a/pkg/nuclide-rust/lib/RustLanguage.js +++ b/pkg/nuclide-rust/lib/RustLanguage.js @@ -49,6 +49,11 @@ async function connectionToRustService( useOriginalEnvironment: true, additionalLogFilesRetentionPeriod: 5 * 60 * 1000, // 5 minutes waitForDiagnostics: true, + initializationOptions: { + // Don't let RLS eagerly build (and fail crashing while finding a + // Cargo.toml if the project uses Buck) for now. + omitInitBuild: true, + }, }, ); diff --git a/pkg/nuclide-rust/lib/main.js b/pkg/nuclide-rust/lib/main.js index a853cfb607e..441b2a051c1 100644 --- a/pkg/nuclide-rust/lib/main.js +++ b/pkg/nuclide-rust/lib/main.js @@ -19,6 +19,8 @@ import createPackage from 'nuclide-commons-atom/createPackage'; import UniversalDisposable from 'nuclide-commons/UniversalDisposable'; import {createRustLanguageService} from './RustLanguage'; +import {updateRlsBuildForTask} from './BuckIntegration'; + class Activation { _rustLanguageService: AtomLanguageService; _buckTaskRunnerService: ?BuckTaskRunnerService; @@ -32,6 +34,8 @@ class Activation { } consumeBuckTaskRunner(service: BuckTaskRunnerService): IDisposable { + service.onDidCompleteTask(task => updateRlsBuildForTask(task, this._rustLanguageService)); + this._buckTaskRunnerService = service; return new UniversalDisposable(() => { this._buckTaskRunnerService = null;