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

Merge multiple compile_commands.json files #12911

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

yiftahw
Copy link

@yiftahw yiftahw commented Oct 31, 2024

Abstract

Usage

define the following entries in .vscode/settings.json:

// already implemented
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/merged_compile_commands.json",
// new
"C_Cpp.mergeCompileCommands": { 
"sources": [
        "${workspaceFolder}/build/proj1/compile_commands.json",
        "${workspaceFolder}/build/proj2/compile_commands.json",
    ],
    "destination": "${workspaceFolder}/build/merged_compile_commands.json"
},
  • NOTE: "C_Cpp.default.compileCommands" is not changed (to a list) for backwards compatibility.

Why are glob patterns not supported here?

  • the CMake Tools extension supports glob patterns without consuming heavy CPU resources as it only tries to recursively search for compile_commands.json files when a build is finished succesfully. (one time per build process)
  • the C/C++ extension doesn't know when a build is started (or finished), it will need to recursively monitor files constantly.
    • can create a non-negligible and persistent CPU load if directories contain many files.
    • will trigger many false positive events.

Error Handling

  • files defined in "sources" don't exist
    • how would we notify the user? (should we?)
    • there are currently no squiggles in settings.json
    • logs in C++ Configuration Errors are used exclusively by the native language server
    • error popups seems excessive for this kind of error
  • cannot create the destination folder/file (permissions, invalid path)
    • error popup seems ok in this scenario
  • compile_commands.json can't be parsed
    • error popup seems ok in this scenario

State Machine

stateDiagram-v2 
[*] --> load : settings.json loaded/changed
load : close file watchers<br>load configuration
check_dst: check if destination file exists
load --> check_dst
create : merge compile commands
check_srcs : check if any of the sources<br>is newer than dst
check_dst --> check_srcs : true
check_dst --> create : false
missingbank : periodically check if missing files are created
filewatchers : create source file watchers
check_srcs --> filewatchers: false
check_srcs --> create : true
missingbank --> filewatchers : file created
filewatchers --> missingbank : error<br>file missing
filewatchers --> create : source file changed
filewatchers --> check_dst : error<br>file watcher limit hit
create --> filewatchers : Success
create --> [*] : Error
Loading

TODO:

  • implement periodic file watchers on non-existant files
  • finalize error handling/user notifications scheme
  • some validation in settings.ts in getMergeCompileCommands()
  • add tests
  • remove console.log() prints

@yiftahw yiftahw changed the title Merge multiple `compile commands Merge multiple compile_commands.json files Oct 31, 2024
@yiftahw
Copy link
Author

yiftahw commented Oct 31, 2024 via email

@bobbrow
Copy link
Member

bobbrow commented Oct 31, 2024

Hi @yiftahw, before you get too far into this approach, what I believe we would like to see for #7029 would be a modification to the compileCommands property that accepts an array of strings (paths to the individual files to merge) instead of performing an actual merge and saving the results into another file.

The starting point is more like this (this is an incomplete code sample):
c_cpp_properties.schema.json

                    "compileCommands": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        ],
                        "markdownDescription": "Full path to `compile_commands.json` file for the workspace.",
                        "descriptionHint": "Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered.",
                    },

package.json

                    "C_Cpp.default.compileCommands": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        ],
                        "markdownDescription": "%c_cpp.configuration.default.compileCommands.markdownDescription%",
                        "scope": "machine-overridable"
                    },

Then in code, the compileCommands property/setting is treated as string | string[]. To avoid having to create an additional temporary file, we would prefer to pass this new setting down to our language server as a string[] and then in the language server we would parse through each of the files individually and add them all into a single object containing all entries.

Unfortunately, that part of the change requires access to the closed-source portion of the code which you don't have access to.

@yiftahw
Copy link
Author

yiftahw commented Oct 31, 2024

Unfortunately, that part of the change requires access to the closed-source portion of the code which you don't have access to.

Hi @bobbrow thanks for the quick reply! Is there a plan to add this feature to the closed-source part of the code? I'm more than willing to work on the open-source part.

@bobbrow
Copy link
Member

bobbrow commented Oct 31, 2024

Sorting by upvotes, this feature is on the first page of language server feature requests, though it's not terribly close to the top. However, since a lot of the higher voted features are not currently actionable and this one shouldn't be too costly to implement, we may be able to find some time to prioritize it sooner. I can't provide an estimate of when we'd get to it, but if you did the open source part, that could help accelerate us completing the closed source part.

Things we'd like to see on the open source part:

  • updates to the c_cpp_properties.json schema and compileCommands setting to accept either a string or array of strings
  • path validation for the array of strings in c_cpp_properties.json
  • settings validation updates (VS Code doesn't audit the values/types of settings before sending them to us)
  • file watcher support for the list of files provided by the user (I think we'd prefer not to allow glob patterns)

There could be other things I'm not thinking of right now, but we'd figure those out during PR, or perhaps you will run into them during development if you choose to help with this.

Thank you!
Bob

@yiftahw
Copy link
Author

yiftahw commented Oct 31, 2024

@bobbrow I'll start fiddling with your approach and create a new draft once some work is done... Thanks for the tips!

@sean-mcmanus
Copy link
Collaborator

@bobbrow Yeah, the work to enable compileCommands to be an array of strings seems pretty small on the cpptools side.

@yiftahw
Copy link
Author

yiftahw commented Oct 31, 2024

@bobbrow @sean-mcmanus on a different note, may I ask why do we monitor and send a ChangeCompileCommandsNotification to the language server on compile_commands.json files other than the one from the selected configuration? (in updateCompileCommandsFileWatchers())
I'm asking because monitoring groups of files (if multiple configurations exist) seems less trivial than a single file per configuration (although not impossible)
also, the fallback implementation in checkCompileCommands() only monitors the selected configuration file (or the default one)
code snippet in question:

// partial snippet from `src/LanguageServer/configurations.ts`
// `CppProperties::updateCompileCommandsFileWatchers()`
this.configurationJson.configurations.forEach(c => {
    filePaths.add(fileSystemCompileCommandsPath);
});
filePaths.forEach((path: string) => {
    this.compileCommandsFileWatchers.push(fs.watch(path, () => {
        this.compileCommandsFileWatcherTimer = setTimeout(() => {
            this.compileCommandsFileWatcherFiles.forEach((path: string) => {
                this.onCompileCommandsChanged(path); // <- notify language server

@sean-mcmanus
Copy link
Collaborator

sean-mcmanus commented Oct 31, 2024

@yiftahw I think that is a bug related to #12889 .

@yiftahw
Copy link
Author

yiftahw commented Nov 1, 2024

@sean-mcmanus just to make sure, I'm asking wether the language server benfits from knowing in advance that a compile_commands.json file has changed, even if it belongs to a configuration that we don't currently use.

again, the fallback case in checkCompileCommands() only checks the file pointed by the current configuration. (which is not so much a fallback because it's always being checked, even if file watchers don't fail)

monitoring all the files by the file watchers but only the currently used one in the fallback case causes a weird side effect: if a compile_commands.json file form a different configuration index is changed, it triggers a file watcher event immidiately. when we switch to the configuration that uses that file, it also creates a 2nd event by the fallback logic in checkCompileCommands(). (the cached path in compileCommandsFile only changes when we change configuration)

if you think there is no benefit on notifying the language server on changes to compile_commands.json that we don't currently use (i.e from a different configuration index), I will file an issue and work on to fix it.
I think I can benefit from this change going forward with what was discussed here previously with @bobbrow

@sean-mcmanus
Copy link
Collaborator

@yiftahw No, cpptools does not need to know that a compile_commands.json has changed if it's not set in the current configuration -- it just results in unnecessary processing. However, if a configurationProvider is set, then compileCommands may still be used as a fallback (but it doesn't need to be updated if compileCommands is not set).

@yiftahw
Copy link
Author

yiftahw commented Nov 1, 2024

@yiftahw No, cpptools does not need to know that a compile_commands.json has changed if it's not set in the current configuration -- it just results in unnecessary processing. However, if a configurationProvider is set, then compileCommands may still be used as a fallback (but it doesn't need to be updated if compileCommands is not set).

Is there a way in the open source part of the extension to know if there is some issue with the configuration provider and we need to fall back to using compile commands? (if set)

@sean-mcmanus
Copy link
Collaborator

Is there a way in the open source part of the extension to know if there is some issue with the configuration provider and we need to fall back to using compile commands? (if set)

You might be able to call canProvideConfiguration. The communicate with the configuration provider is all done in the open source TypeScript.

Actually, upon further review it appears my answer to your previous question was incorrect -- the current implementation does appear to rely on switching a configuration having already received a didChangeCompileCommands message, and otherwise it looks like it would continue to use the old compile commands, but that seems like the wrong implementation to me. Maybe it could be changed such that when a user switches configurations, then it can send the didChangeCompileCommands then (ideally beforehand).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Pull Request
Development

Successfully merging this pull request may close these issues.

Support multiple compile commands files per project
3 participants