Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement addNodeAddonIncludePaths #6731

Merged
merged 11 commits into from
Jan 14, 2021

Conversation

bmacnaughton
Copy link
Contributor

@bmacnaughton bmacnaughton commented Jan 1, 2021

This PR addresses the common cases for #4854 by adding the include paths specified by nan and node-addon-api when they are listed as dependencies in package.json. This was the first time I've looked at vscode-cpptools code and it's also the first time I've written any typescript, so suggestions about how I can improve in either area are welcome.

I haven't changed the changelog nor even looked at adding a specific test pending feedback. Afaict, it passes the unit tests and integration tests but I don't see any positive feedback to be sure.

I modified the insiders branch, so that is the target for this PR. Not sure whether that's the regular practice or not.

Questions:

  • Is "machine-overridable" the correct scope package.json 1109
  • should it check settings.addNodeAddonIncludePaths or just concat an empty array configuration.ts 327
  • mapping the dependencies to commands uses an inline definition. not sure the best practices for this configuration.ts 398.
  • pfs (fs/promises) is defined at the top of the file but only used one place configuration.ts 408. prefer it locally scoped?
  • the include path loop uses a labeled loop. I didn't see those anywhere else; it could be replaced with an if-level. configuration.ts 414

Obviously, you're welcome to change names, wording, etc. or ask me to do so.

@ghost
Copy link

ghost commented Jan 1, 2021

CLA assistant check
All CLA requirements met.

@sean-mcmanus
Copy link
Collaborator

FYI, we might not get around to reviewing this till Wednesday.

@sean-mcmanus
Copy link
Collaborator

I haven't changed the changelog

That's fine. We generally update the changelog ourselves before a release, e.g. https://github.com/microsoft/vscode-cpptools/pull/6674/files -- the reason is that modifying the changelog with every PR can cause a merge conflict with every commit, which is annoying.

I modified the insiders branch

Targeting the insiders branch seems fine and we can merge to master (insiders is almost identical to master currently).

Is "machine-overridable" the correct scope package.json 1109

No, I think it should be "resource". "machine" and "machine-overridable" scopes are for scopes that are not expected to be valid across different machines/OS's, such as file paths.

mapping the dependencies to commands uses an inline definition

Seems okay to me. It could also be made local to the function or file scope, but I'm not familiar with the recommended TypeScript practice either.

More comments on the way...

Copy link
Collaborator

@sean-mcmanus sean-mcmanus left a comment

Choose a reason for hiding this comment

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

You should install the ESLint extension, and run the the "lint" npm script or "gulp lint". The linter enforces are our whitespace/style rules.

configurations.ts
24:7 error expected variable-declaration: 'pfs' to have a typedef (tslint:typedef) @typescript-eslint/tslint/config
399:1 error Expected indentation of 8 spaces but found 6 @typescript-eslint/indent
400:1 error Expected indentation of 8 spaces but found 6 @typescript-eslint/indent
400:86 error Unexpected trailing comma comma-dangle
403:66 error Unexpected space before the ':' @typescript-eslint/type-annotation-spacing
408:23 error expected variable-declaration: 'package_json' to have a typedef (tslint:typedef) @typescript-eslint/tslint/config
411:1 error Expected indentation of 20 spaces but found 18 @typescript-eslint/indent
412:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
413:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
413:27 error expected variable-declaration: 'stdout' to have a typedef (tslint:typedef) @typescript-eslint/tslint/config
414:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
414:23 error Expected { after 'if' condition curly
416:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
417:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
418:1 error Expected indentation of 28 spaces but found 26 @typescript-eslint/indent
419:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
420:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
421:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
422:1 error Expected indentation of 28 spaces but found 26 @typescript-eslint/indent
423:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
424:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
425:1 error Expected indentation of 28 spaces but found 26 @typescript-eslint/indent
426:1 error Expected indentation of 24 spaces but found 22 @typescript-eslint/indent
431:1 error Expected indentation of 12 spaces but found 10 @typescript-eslint/indent

✖ 24 problems (24 errors, 0 warnings)
21 errors and 0 warnings potentially fixable with the --fix option.

@bobbrow
Copy link
Member

bobbrow commented Jan 5, 2021

Targeting the insiders branch seems fine and we can merge to master (insiders is almost identical to master currently).

Process-wise, I think it's better if PR's always target main because we won't regularly merge insiders back to main (I was thinking we really only did it one direction main -> insiders since it's a "release" branch).

@sean-mcmanus
Copy link
Collaborator

Targeting the insiders branch seems fine and we can merge to master (insiders is almost identical to master currently).

Process-wise, I think it's better if PR's always target main because we won't regularly merge insiders back to main (I was thinking we really only did it one direction main -> insiders since it's a "release" branch).

The potential problem is that main might have breaking changes that don't work without unreleased binaries, while the insider branch is guaranteed to work with the 1.2.0-preview binaries.

There shouldn't be any issue with merging from insiders to main, right?

@bmacnaughton
Copy link
Contributor Author

@sean-mcmanus @bobbrow - thank you for the feedback. i will make these changes this weekend (unless you have a need for them before that or i find a little spare time) as this is a side project of mine.

i'm not sure how the lint errors came about; i have eslint and the extension installed. i didn't run the lint script because i presumed that the extension would use the same rules as the script. i'll run the script and take care of the errors too.

@sean-mcmanus
Copy link
Collaborator

@sean-mcmanus @bobbrow - thank you for the feedback. i will make these changes this weekend (unless you have a need for them before that or i find a little spare time) as this is a side project of mine.

i'm not sure how the lint errors came about; i have eslint and the extension installed. i didn't run the lint script because i presumed that the extension would use the same rules as the script. i'll run the script and take care of the errors too.

That's fine. Our goal is to release 1.2.0-insiders some time next week, but I'm not sure what day yet, but this could go into the next release if it doesn't make that.

Yeah, ESLint is supposed to squiggle the lines with issues without having to run the specific command.

@bmacnaughton
Copy link
Contributor Author

addressed with the exception of dynamic enabling of the setting. i'm having to dig around a bit to understand that.

questions:

  • i used : any for pfs and package_json. i saw multiple uses of : any for reading package.json so that seems ok. but is there a better way to get access to require('fs').promises?
  • i'm digging into how to dynamically enable the setting but if you can point me at an example that would be great.

btw, my problem not catching the eslint problems was that i didn't notice that eslint wanted to ask me permission to use the locally installed version.

@bmacnaughton
Copy link
Contributor Author

bmacnaughton commented Jan 8, 2021 via email

@sean-mcmanus
Copy link
Collaborator

  • i used : any for pfs and package_json. i saw multiple uses of : any for reading package.json so that seems ok. but is there a better way to get access to require('fs').promises?
  • i'm digging into how to dynamically enable the setting but if you can point me at an example that would be great.

Yeah, dynamic enabling seemed tricky because it would involve having to somehow delay the processing until after addNodeAddonIncludeLocations has generated the nodeAddonIncludes , which I don't think we have any similar case/example that does that, so that's why I was thinking just letting the user know a reload was required was sufficient, particular since it's not expected for that setting to change frequently. Enabling should be simple if nodeAddonIncludes has the data already though (assuming you don't want it to try to regenerate that dynamically).

"any" are used in cases where we don't have type info or if the object is a dynamic type. I think it would be better to just use fs.promises directly instead of defining 'pfs'.

@bmacnaughton
Copy link
Contributor Author

bmacnaughton commented Jan 8, 2021 via email

@bmacnaughton
Copy link
Contributor Author

i appreciate your help; i will have more time to devote on the weekend; i'm sorry i haven't really given this the attention it deserves this week.

Extension/package.json Outdated Show resolved Hide resolved
Extension/src/LanguageServer/configurations.ts Outdated Show resolved Hide resolved
@sean-mcmanus
Copy link
Collaborator

i appreciate your help; i will have more time to devote on the weekend; i'm sorry i haven't really given this the attention it deserves this week.

Yeah, that's fine...we usually don't work on weekends so you'll have to wait till Monday for a response.

@bmacnaughton
Copy link
Contributor Author

bmacnaughton commented Jan 11, 2021

ok, just pushed changes. biggest changes:

  • the code now reads any node addon include paths at start up. this change allows the setting to be changed on the fly (mostly see below)
  • addNodeAddonIncludePaths() uses vscode.window.showErrorMessage() when it detects errors.
  • i'm thinking of renaming addNodeAddonIncludePaths to readNodeAddonIncludePaths because it doesn't add them.

(mostly allows changing on the fly) if the user adds nan or node-addon-api to the project on the fly the code doesn't know that and doesn't know to re-execute addNodeAddonIncludePaths(). is there a way to get notified when a specific file (the project's package.json) is modified?

and i need to clean up the showErrorMessage('error.adding.node.addon.includes') but wanted to get your feedback on the preferred way to showing errors like this first.

feedback is appreciated.

- make nodeAddonMap a local
- fail silently
- add nodeAddonIncludesFound()
@bmacnaughton
Copy link
Contributor Author

ok, i think it's pretty close at this point.

  • it now fails silently. the only times it really can fail now are
    • package.json doesn't exist or is malformed (it can't be a node-addon project)
    • requiring node-addon-api/nan fails (they haven't been added yet or are malformed. could keep track of what is present).
    • the directory node-addon-api/nan returns is not valid (can't happen now but maybe could in the future).
    • a coding error (consider reporting this one?)
  • prompts for reload if addNodeAddonIncludePaths is turned on but no addon includes were found by readNodeAddonIncludeLocations. this doesn't catch the case where nan was found but they added node-addon-api before turning on the setting. i looked at creating a filewatcher but chose not to because i don't believe this is a major use of vscode for c/c++, so it should avoid using resources unnecessarily. (it also would require async activity in order to handle it and i didn't see how that could be handled in the file watcher, but that was secondary. if there is a filewatcher that already watches package.json or files added in node_modules and that could be piggy-backed on it might be worth revisiting.)
  • in theory DefaultClient.onDidChangeSettings() could initiate the process by kicking off readNodeAddonIncludeLocations
  • maybe the docs could note that adding node-addon-api or nan require reloading and the check for the length of node addon include paths found could be removed.

@sean-mcmanus
Copy link
Collaborator

The "unbound breakpoint" issue you were hitting may have been caused by the extension not being activated...opening a .cpp should trigger the activation.

- remove experimental artifact
@bmacnaughton
Copy link
Contributor Author

i believe that i have made all the changes you requested. let me know if you have any additional.

@@ -627,6 +696,10 @@ export class CppProperties {
const configuration: Configuration = this.configurationJson.configurations[i];

configuration.includePath = this.updateConfigurationStringArray(configuration.includePath, settings.defaultIncludePath, env);
if (settings.addNodeAddonIncludePaths) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

configuration.includePath is checked/modified in the code below under certain settings. The "unmodified" configuration.includePath should be saved to some variable here to be used later.

Copy link
Collaborator

Choose a reason for hiding this comment

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

if (!configuration.includePath && !!this.defaultIncludes) {
                            configuration.includePath = this.defaultIncludes;
                        }

should check the unmodified/original variable and then do something like

                const includePath: string[] = configuration.includePath || [];
                configuration.includePath = includePath.concat(this.defaultIncludes);

Copy link
Contributor Author

@bmacnaughton bmacnaughton Jan 13, 2021

Choose a reason for hiding this comment

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

thanks for this; i feel like a dog watching tv when i work in this codebase. everything is new and my focus is on a very tiny slice of what is going on.

@@ -288,7 +291,8 @@ export class CppProperties {
}

private applyDefaultIncludePathsAndFrameworks(): void {
if (this.configurationIncomplete && this.defaultIncludes && this.defaultFrameworks && this.vcpkgPathReady) {
if (this.configurationIncomplete && this.defaultIncludes && this.defaultFrameworks && this.vcpkgPathReady
&& this.nodeAddonIncludePathsReady) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This variable is no longer needed. It was only needed when the applyDefaultConfigurationValues was being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, obvious when i see it. you have a much better handle on all the moving pieces than i do - thanks for spending time on this.

error = e;
}
}
if (error && pdjFound) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be something like

if (error) {
if (pdjFound) {
...
}
} else {
this.handleConfigurationChange();
}

i.e. to skip this.handleConfigurationChange when it's not needed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or checking this.nodeAddonIncludes.length > 0 (i.e. in case one of the cases errors?).

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 think it is (conceivably) possible that both node-addon-api and nan are referenced in package.json, both requires succeed, but one of two util.checkDirectoryExists() fail.

but i am changing it as i think it's 1) not possible now given what node-addon-api and nan return and 2) a really tiny edge case going forward because it means the package has to return an invalid directory.

thank you.

.then(pdj => {pdjFound = true; return JSON.parse(pdj); })
.catch(e => (error = e));

const pathToNode: string = which.sync("node");
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be inside the try { below (avoids calling which.sync when it's not needed).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point.

// if addNodeAddonIncludePaths was turned on but no includes have been found yet then 1) presume that nan
// or node-addon-api was installed so prompt for reload.
if (changedSettings["addNodeAddonIncludePaths"] && settings.addNodeAddonIncludePaths && this.configuration.nodeAddonIncludesFound() === 0) {
util.promptForReloadWindowDueToSettingsChange();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will only get shown if a user adds the nan/node-addon-api to their package.json and then enables the setting -- is that intentional? It seems fine to me, but it just covers a subset of the cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

prompts for reload if addNodeAddonIncludePaths is turned on but no addon includes were found by readNodeAddonIncludeLocations. this doesn't catch the case where nan was found but they added node-addon-api before turning on the setting.

above is my original comment on this. it's definitely a subset but seemed like one that is likely to be hit with a typical workflow - create a project, start editing, add node-addon-api or nan, notice the squiggles, and then fix it.

if your experience is different i'm happy to take it out. but it's kind of the best that could be done without creating a file-watcher or changing readNodeAddonIncludeLocations to be synchronous.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, seems fine to me.

@sean-mcmanus
Copy link
Collaborator

i believe that i have made all the changes you requested. let me know if you have any additional.

Yeah, I think it's close. I noticed some additional minor issues.

- remove unused flag
- avoid unnecessary work
- handle alternate include setting path
@bmacnaughton
Copy link
Contributor Author

@sean-mcmanus - thanks for the review. i think i've addressed the issues; i don't think the first one was minor and i appreciate your continuing help.

@sean-mcmanus sean-mcmanus merged commit 41b7678 into microsoft:insiders Jan 14, 2021
@sean-mcmanus
Copy link
Collaborator

@sean-mcmanus - thanks for the review. i think i've addressed the issues; i don't think the first one was minor and i appreciate your continuing help.

Yeah, thanks for your contribution. Looks like it'll make 1.2.0-insiders -- hopefully it'll be available by end of day Thursday unless we find any last minute blocking issues.

sean-mcmanus added a commit that referenced this pull request Jan 14, 2021
Merge to main: implement addNodeAddonIncludePaths (#6731)
@github-actions github-actions bot locked and limited conversation to collaborators Feb 28, 2021
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.

3 participants