-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
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.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<LanguageService>, | ||
) { | ||
const buildTarget = normalizeNameForBuckQuery(task.buildTarget); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
|
||
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}`); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
Xanewok
Author
Owner
|
||
|
||
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<string> = []; | ||
|
||
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}`; | ||
This comment has been minimized.
Sorry, something went wrong.
mostafaeweda
|
||
|
||
logger.debug(`Built SA artifacts: ${artifacts.join('\n')}`); | ||
logger.debug(`buildCommand: ${buildCommand}`); | ||
|
||
langService.sendLspNotification(fileUri, 'workspace/didChangeConfiguration', | ||
This comment has been minimized.
Sorry, something went wrong.
Xanewok
Author
Owner
|
||
{ | ||
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 | ||
} | ||
} | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Array<string>> { | ||
return BuckService. | ||
query(buckRoot, `filter('.*\\.rs$', inputs('${buildTarget}'))`, []); | ||
} | ||
|
||
export function getSaveAnalysisTargets(buckRoot: string, buildTarget: BuildTarget): Promise<Array<string>> { | ||
// 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; | ||
} |
2 comments
on commit d04d407
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current experience is non-optimal as users have to input & build buck targets from the toolbar & only then, chain extra buck build
commands (without reporting progress), & finally, the users would get their language service working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree that's not optimal right now.
From what I understand, the MultiLspLanguageService acts as a single service that spawns sub-services when it determines the appropriate project dir using a factory.
However, I couldn't find a callback for when that happens and I also can't specify the build command on initializationOptions since that's static information. Ideally I could run the equivalent of buck build (whichever targets correspond to `..` with #save-analysis)
directly when initializing an LSP server or after it's spawned (preferably the latter) - this way the user wouldn't have to do anything else to get its index-based features for Rust projects.
Could you filter out non-rust tasks / build targets? (that'd save a couple of expensive unnecessary
buck query
calls for non-buck targets?