Skip to content

Commit b2f7f11

Browse files
committed
Update RLS index after Buck build
After hitting Buck build button, the relevant save-analysis Buck build is performed and RLS is supplied with a dummy build command to retrieve necessary save-analysis indexing files. Things left to do/worth noting: * We should detect if Buck is an appropriate build system for a given LSP service and if not, not override RLS' external build command * Until path remapping lands in rustc (rust-lang/rust#53110) the index-based features (find refs, goto def etc.) only work for source files in the buck-out/**/$TARGET#save-analysis-container source files. * RLS needs some bugfixin' since currently external build command does not properly refresh file dirty state, leading to an endless build loop.
1 parent 6722738 commit b2f7f11

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the LICENSE file in
6+
* the root directory of this source tree.
7+
*
8+
* @flow
9+
* @format
10+
*/
11+
12+
import type {TaskInfo} from '../../nuclide-buck/lib/types';
13+
import type {
14+
AtomLanguageService,
15+
LanguageService,
16+
} from '../../nuclide-language-service';
17+
18+
import invariant from 'invariant';
19+
import {getLogger} from 'log4js';
20+
import fsPromise from 'nuclide-commons/fsPromise';
21+
import nuclideUri from 'nuclide-commons/nuclideUri';
22+
import {getRustInputs, getSaveAnalysisTargets, normalizeNameForBuckQuery} from './BuckUtils';
23+
24+
import * as BuckService from '../../nuclide-buck-rpc';
25+
26+
const logger = getLogger('nuclide-rust');
27+
28+
export async function updateRlsBuildForTask(
29+
task: TaskInfo,
30+
service: AtomLanguageService<LanguageService>,
31+
) {
32+
const buildTarget = normalizeNameForBuckQuery(task.buildTarget);
33+
34+
const files = await getRustInputs(task.buckRoot, buildTarget);
35+
// Probably not a Rust build target, ignore
36+
if (files.length == 0)
37+
return;
38+
// We need only to pick a representative file to get a related lang service
39+
const fileUri = task.buckRoot + '/' + files[0];
40+
atom.notifications.addInfo(`fileUri: ${fileUri}`);
41+
42+
const langService = await service.getLanguageServiceForUri(fileUri);
43+
invariant(langService != null);
44+
45+
// Since `buck` execution is not trivial - the command may be overriden, needs
46+
// to inherit the environment, passes internal FB USER to env etc. the RLS
47+
// can't just invoke that.
48+
// Instead, we build now, copy paths to resulting .json analysis artifacts to
49+
// a temp file and just use `cat $TMPFILE` as a dummy build command.
50+
const analysisTargets = await getSaveAnalysisTargets(task.buckRoot, buildTarget);
51+
logger.debug(`analysisTargets: ${analysisTargets.join('\n')}`);
52+
let artifacts: Array<string> = [];
53+
54+
const buildReport = await BuckService.build(task.buckRoot, analysisTargets);
55+
if (buildReport.success === false) {
56+
atom.notifications.addError("save-analysis build failed");
57+
return;
58+
}
59+
60+
Object.values(buildReport.results)
61+
// TODO: https://buckbuild.com/command/build.html specifies that for
62+
// FETCHED_FROM_CACHE we might not get an output file - can we force it
63+
// somehow? Or we always locally produce a save-analysis .json file?
64+
.forEach((targetReport: any) => artifacts.push(targetReport.output));
65+
66+
const tempfile = await fsPromise.tempfile();
67+
await fsPromise.writeFile(tempfile, artifacts.join('\n'));
68+
69+
// TODO: Windows?
70+
const buildCommand = `cat ${tempfile}`;
71+
72+
logger.debug(`Built SA artifacts: ${artifacts.join('\n')}`);
73+
logger.debug(`buildCommand: ${buildCommand}`);
74+
75+
langService.sendLspNotification(fileUri, 'workspace/didChangeConfiguration',
76+
{
77+
settings: {
78+
rust: {
79+
unstable_features: true, // Required for build_command
80+
build_on_save: true,
81+
build_command: buildCommand, // TODO: Only in RLS branch: https://github.com/Xanewok/rls/tree/external-build
82+
}
83+
}
84+
});
85+
}

pkg/nuclide-rust/lib/BuckUtils.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the LICENSE file in
6+
* the root directory of this source tree.
7+
*
8+
* @flow strict-local
9+
* @format
10+
*/
11+
12+
import type {TaskInfo} from '../../nuclide-buck/lib/types';
13+
14+
import * as BuckService from '../../nuclide-buck-rpc';
15+
16+
export function getRustInputs(buckRoot: string, buildTarget: BuildTarget): Promise<Array<string>> {
17+
return BuckService.
18+
query(buckRoot, `filter('.*\\.rs$', inputs('${buildTarget}'))`, []);
19+
}
20+
21+
export function getSaveAnalysisTargets(buckRoot: string, buildTarget: BuildTarget): Promise<Array<string>> {
22+
// Save-analysis build flavor is only supported by rust_{binary, library}
23+
// kinds (so exclude prebuilt_rust_library kind)
24+
const query: string = `kind('^rust_.*', deps(${buildTarget}))`;
25+
26+
return BuckService.query(buckRoot, query, []).then(deps =>
27+
deps.map(dep => dep + '#save-analysis'),
28+
);
29+
}
30+
31+
export type BuildTarget = string;
32+
33+
// FIXME: Copied from nuclide-buck-rpc
34+
// Buck query doesn't allow omitting // or adding # for flavors, this needs to be fixed in buck.
35+
export function normalizeNameForBuckQuery(aliasOrTarget: string): BuildTarget {
36+
let canonicalName = aliasOrTarget;
37+
// Don't prepend // for aliases (aliases will not have colons or .)
38+
if (
39+
(canonicalName.indexOf(':') !== -1 || canonicalName.indexOf('.') !== -1) &&
40+
canonicalName.indexOf('//') === -1
41+
) {
42+
canonicalName = '//' + canonicalName;
43+
}
44+
// Strip flavor string
45+
const flavorIndex = canonicalName.indexOf('#');
46+
if (flavorIndex !== -1) {
47+
canonicalName = canonicalName.substr(0, flavorIndex);
48+
}
49+
return canonicalName;
50+
}

pkg/nuclide-rust/lib/RustLanguage.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ async function connectionToRustService(
4949
useOriginalEnvironment: true,
5050
additionalLogFilesRetentionPeriod: 5 * 60 * 1000, // 5 minutes
5151
waitForDiagnostics: true,
52+
initializationOptions: {
53+
// Don't let RLS eagerly build (and fail crashing while finding a
54+
// Cargo.toml if the project uses Buck) for now.
55+
omitInitBuild: true,
56+
},
5257
},
5358
);
5459

pkg/nuclide-rust/lib/main.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import createPackage from 'nuclide-commons-atom/createPackage';
1919
import UniversalDisposable from 'nuclide-commons/UniversalDisposable';
2020
import {createRustLanguageService} from './RustLanguage';
2121

22+
import {updateRlsBuildForTask} from './BuckIntegration';
23+
2224
class Activation {
2325
_rustLanguageService: AtomLanguageService<LanguageService>;
2426
_buckTaskRunnerService: ?BuckTaskRunnerService;
@@ -32,6 +34,8 @@ class Activation {
3234
}
3335

3436
consumeBuckTaskRunner(service: BuckTaskRunnerService): IDisposable {
37+
service.onDidCompleteTask(task => updateRlsBuildForTask(task, this._rustLanguageService));
38+
3539
this._buckTaskRunnerService = service;
3640
return new UniversalDisposable(() => {
3741
this._buckTaskRunnerService = null;

0 commit comments

Comments
 (0)