Skip to content
This repository was archived by the owner on Nov 18, 2022. It is now read-only.

reintroduce multi project setup #638

Merged
merged 10 commits into from
Sep 18, 2019

Conversation

jannickj
Copy link
Contributor

@jannickj jannickj commented Aug 7, 2019

This will allow you to have project structures like this:

projectA/Cargo.toml
projectA/src
projectB/Cargo.toml
projectB/src
subProjects/projectD/Cargo.toml
subProjects/projectD/src
subProjects/projectE/Cargo.toml
subProjects/projectE/src

when you open a file it will search up parent folders until it finds a Cargo.toml, and either reuse the RLS instance for that one or create one if it doesn't exists.

Changes

  • enable multiple projects with multiple rls running
  • change the URI key back to string for workspaces (as it's unreliable)
  • add selectors such that only the correct rls is used for a given project
  • take great care to ensure the old setup works as normal
  • add a configuration and turn it off by default.

Second attempt at: #457

@jannickj jannickj force-pushed the allow-multiple-rls-projects branch from 3f814f0 to 3eb24b2 Compare August 7, 2019 07:26
* enable multiple projects with multiple rls running
* change the URI key back to string for workspaces (as it's unreliable)
* add selectors such that only the correct rls is used for a given project
* take great care to ensure the old setup works normally
* add a configuration and turn it off by default.
@jannickj jannickj force-pushed the allow-multiple-rls-projects branch from 3eb24b2 to a8b652a Compare August 7, 2019 07:35
@jannickj
Copy link
Contributor Author

jannickj commented Aug 7, 2019

forced pushed to make it more clear what I am actually changing :)

@jannickj jannickj force-pushed the allow-multiple-rls-projects branch from a8b652a to 6684a08 Compare August 7, 2019 07:44
Copy link

@MonsieurMan MonsieurMan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested some really small changes, I don't know if they make sense regarding to tslint and variable scope as I did not have time to clone the repo.

I also suggested another workflow I'd like to discuss if possible.

runRlsCommand(this.folder, cmd),
),
commands.registerCommand('rls.run', (cmd: Execution) => {
const ws =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this took me some second to understand that ws was workspace, also personal preference but I find ternary easier to read like so :

const workspace = (multiProjectEnabled && activeWorkspace)
      ? activeWorkspace
      : this;
runRlsCommand(workspace.folder, cmd);

I don't know if it's ok with the tslint configs.

},
);
this.disposables.push(rustupUpdateDisposable);

const restartServer = commands.registerCommand('rls.restart', async () => {
await this.stop();
return this.start(context);
const ws =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about ws -> workspace here 🙂

@@ -219,6 +219,11 @@
"default": true,
"description": "If one root workspace folder is nested in another root folder, look for the Rust config in the outermost root."
},
"rust-client.enableMultiProjectSetup": {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know much about the extension, didn't have the time to clone it yet, but wouldn't it be possible to make the extension work like :

  • Normal cargo.toml at root of the project is tried
  • If it fails the extension tries to determine if it's not a monorepo

I'd like your opinion on this, if it is quite some work I'd happily lend you a hand 🙂

Copy link
Contributor Author

@jannickj jannickj Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal cargo.toml at root of the project is tried

With this change the cargo.toml at root should also just work.

If it fails the extension tries to determine if it's not a monorepo

How would you detect that? I could imagine some wtf moments where you add a folder and all of the sudden the tooling stops working. I would rather make configuration for stuff like that.

I'd like your opinion on this, if it is quite some work I'd happily lend you a hand

In general I would estimate that 99% will just always have it turned on. So mostly this is about not breaking it for the 1%.

and thx for the offer :)

src/extension.ts Outdated
if (
workspace
.getConfiguration()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workspace.getConfiguration is also called later on, maybe consider storing it into a const configuration and reusing the same variable ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ye, the problem is the function was clearly never meant to take configuration arguments. However it would take some re-architecture to make it nice, and I didn't want to push for too many changes.

workspace.start(context);
} else {
const ws = workspaces.get(folderPath);
activeWorkspace = typeof ws === 'undefined' ? null : ws;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const workspace = workspaces.get(folderPath);
activeWorkspace = (typeof workspace === 'undefined')
    ? null 
    : workspace;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the formatter, which is build server checked, decides stuff like that.

const workspaces: Map<Uri, ClientWorkspace> = new Map();
// Don't use URI as it's unreliable the same path might not become the same URI.
const workspaces: Map<string, ClientWorkspace> = new Map();
let activeWorkspace: ClientWorkspace | null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typescript has a builtin for nullable variables :

let activeWorkspace?: ClientWorkspace;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per above, it'd be great if we could extract this 'global' state somehow to a dedicated module, similar to any other code that manages/fetches appropriate workspace servers.

return rustupUpdate(this.config.rustupConfig());
const ws =
multiProjectEnabled && activeWorkspace ? activeWorkspace : this;
return rustupUpdate(ws.config.rustupConfig());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const workspace = (multiProjectEnabled && activeWorkspace) 
    ? activeWorkspace 
    : this;
return rustupUpdate(workspace.config.rustupConfig());

@jannickj
Copy link
Contributor Author

@nrc / @Xanewok do you have time to look it over?

@Xanewok
Copy link
Member

Xanewok commented Aug 12, 2019

Yep! Before merging, I'd like to release a 0.6.2 first with a fix for #639

@jannickj
Copy link
Contributor Author

@Xanewok Are you ready to review now?

@wyze
Copy link

wyze commented Aug 20, 2019

I've built your branch from source and have been using it for over a week without any issues, in both a multi-project setup and single project.

@jannickj
Copy link
Contributor Author

jannickj commented Sep 3, 2019

👋

Copy link
Member

@Xanewok Xanewok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on it, it's a highly requested feature as you can tell!

I left some comments below - it'd be great to extract the workspace managing bits outside before we delve into details here since it's getting a bit conflated and harder to see which path we should take with the details of the implementation.

Are URIs unreliable for workspaces because they can be not normalized/canonicalized?

src/extension.ts Outdated

commandsUnregistered = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think it'd be more clear to have commandsRegistered instead if we're tracking state like that

src/extension.ts Outdated

// return cur_workspace;
// }
function getCargoTomlWorkspace(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to document this briefly since it's not immediately obvious (at least for me) what is this doing without analyzing the body thoroughly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some comments.
However even though the body might look a bit big, I actually kept it like that to make it simplistic, mostly because it's a function that will run fairly often and I didn't want to risk busy loops.

if (!workspaces.has(folder.uri)) {
const folderPath = folder.uri.toString();

if (!workspaces.has(folderPath)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely move this logic out of didOpenTextDocument. Anything related to organizing and identifying instances across different folders/workspaces should be under src/workspace.ts perhaps? (name open for bikeshedding)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I'm not against refactoring some of the code out, I'm afraid that this will delay this PR even more, as it's now doing two things at once.

const workspaces: Map<Uri, ClientWorkspace> = new Map();
// Don't use URI as it's unreliable the same path might not become the same URI.
const workspaces: Map<string, ClientWorkspace> = new Map();
let activeWorkspace: ClientWorkspace | null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per above, it'd be great if we could extract this 'global' state somehow to a dedicated module, similar to any other code that manages/fetches appropriate workspace servers.

@Xanewok Xanewok added this to the 0.7 milestone Sep 7, 2019
@jannickj
Copy link
Contributor Author

jannickj commented Sep 9, 2019

Are URIs unreliable for workspaces because they can be not normalized/canonicalized?

No, I tried with identical URI's but I noticed that there was some caching going on because when I ran the code in debug I could see that the some private field were being set the second time I used a URI for the same string. Which I concluded was why they hash'ed differently.

I couldn't get it to work on my machine but maybe I was using it wrong, so if someone knows the answer I'm happy to hear it.

@jannickj
Copy link
Contributor Author

jannickj commented Sep 9, 2019

I left some comments below - it'd be great to extract the workspace managing bits outside before we delve into details here since it's getting a bit conflated and harder to see which path we should take with the details of the implementation.

It's good comments and I don't disagree, however I feel a requirement of refactoring some of the designs decisions already made in the code base is a bit much. I'm not against refactoring some of the code however I am very afraid I'll spent a good few hours doing that only to have my PR linger for the next 6 months.

I'll do it but I'll need your word on it not happening.

@jannickj jannickj force-pushed the allow-multiple-rls-projects branch from 4c2c97a to 47c66be Compare September 10, 2019 21:13
@jannickj
Copy link
Contributor Author

I looked into it: I started the work on moving to a "workspace" module the problem is that almost 80% of extensions is about the workspace, more than that the code is littered with global state. Now If one must have global state at least keep it contained to one file. As such I ended up just moving my own function, along with writing some TODOs.

With that said I do think it's a good idea to look at this code and do some refactoring however I don't think this is the PR for it.

@jannickj
Copy link
Contributor Author

👋

@Xanewok
Copy link
Member

Xanewok commented Sep 18, 2019

Changes look good and I won't block this any longer - sorry for the delay!
And many thanks for taking a look at this, I think a lot of people will be happy =)

@Xanewok Xanewok merged commit ffe36f3 into rust-lang:master Sep 18, 2019
@jli
Copy link

jli commented Apr 12, 2020

@jannickj thanks so much for the contribution!

Everything appears to be working with my nested subdirectory. However, running the "cargo build" task doesn't seem to work. Do you know if this is meant to be supported?

(more details here: #419 (comment) )

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants