{"id":28,"date":"2021-10-02T13:07:59","date_gmt":"2021-10-02T13:07:59","guid":{"rendered":"http:\/\/blogs.igalia.com\/aboya\/?p=28"},"modified":"2021-11-29T07:48:27","modified_gmt":"2021-11-29T07:48:27","slug":"setting-up-visualstudio-code-to-work-with-webkitgtk-using-clangd","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/aboya\/2021\/10\/02\/setting-up-visualstudio-code-to-work-with-webkitgtk-using-clangd\/","title":{"rendered":"Setting up VisualStudio code to work with WebKitGTK using clangd"},"content":{"rendered":"<p>Lately I\u2019m working on a refactor in the append pipeline of the MediaSource Extensions implementation of the WebKit for the GStreamer ports. Working on refactors often triggers many build issues, not only because they often encompass a lot of code, but also because it\u2019s very easy to miss errors in the client code when updating an interface.<\/p>\n<p>The traditional way to tackle this problem is by doing many build cycles: compile, fix the topmost error, and maybe some other errors on view that seem legit (note in C++ it\u2019s very common to have chain errors that are consequence of previous errors), repeat until it builds successfully.<\/p>\n<p>This approach is not very pleasant in a project like WebKit where an incremental build of a single file takes just enough time to cause the need for a distraction. It\u2019s also worsened when it\u2019s not just one file, but a complete build that may stop at any time, depending on the order the build system chooses for the files. Often it does take more time to wait for the compiler to show the error than to fix the error.<\/p>\n<p>Unpleasant unfavors motivation, and lack of motivation unfavors productivity, and by the end of the day you are tired and still undone. Somehow it feels like the time spent fixing trivial build issues is substancially more than the time of a build cycle times the number of errors. Whether that perception is accurate or not, I am acutely aware of the huge impact having helpful tooling has on both productivity and quality of life, both while and after you\u2019re done with the work, so I decided to have a look at the state of modern C++ language servers when working on a large codebase like WebKit. Previous experiences were very unsuccessful, but there are people dedicated to this and progress has been made.<\/p>\n<h3 id=\"creating-a-webkit-project-in-vs-code\">Creating a WebKit project in VS Code<\/h3>\n<ol type=\"1\">\n<li>Open the directory containing the WebKit checkout in VS Code.<\/li>\n<li>WebKit has A LOT of files. If you use Linux you will see a warning telling you increase the number of inotify watchers. Do so if you haven\u2019t done it before, but even then, it will not be enough, because WebKit has more files than the maximum number of inotify watchers supported by the kernel. Also, they use memory.<\/li>\n<li>Go to File\/Preferences\/Settings, click the <em>Workspace<\/em> tab, search for <strong><em>Files: Watcher Exclude<\/em><\/strong> and add the following patterns:\n<pre><code>**\/CMakeFiles\/**\n**\/JSTests\/**\n**\/LayoutTests\/**\n**\/Tools\/buildstream\/cache\/**\n**\/Tools\/buildstream\/repo\/**\n**\/WebKitBuild\/UserFlatpak\/repo\/**<\/code><\/pre>\n<p>This will keep the number of watches on a workable 258k. Still a lot, but under the 1M limit.<\/li>\n<\/ol>\n<h3 id=\"how-to-set-up-clangd\">How to set up clangd<\/h3>\n<p>The following instructions assume you\u2019re using WebKitGTK with the WebKit Flatpak SDK. They should also work for WPE with minimal substitutions.<\/p>\n<ol type=\"1\">\n<li>Microsoft has its own C++ plugin for VS Code, which may be installed by default. The authors of the clangd plugin recommend to <strong>uninstall the built-in C++ plugin<\/strong>, as running both doesn\u2019t make much sense and could cause conflicts.<\/li>\n<li>Install the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=llvm-vs-code-extensions.vscode-clangd\">clangd extension for VS Code from the VS Code Marketplace<\/a>.<\/li>\n<li>The WebKit flatpak SDK already includes clangd, so it\u2019s not necessary to install it if you\u2019re using it. On the other hand, because the flatpak has a virtual filesystem, it\u2019s necessary to map paths from the flatpak to the outside. You can <strong>create this wrapper script for this purpose. Make sure to give it execution rights<\/strong> (<code>chmod +x<\/code>).\n<div id=\"cb2\" class=\"sourceCode\">\n<pre class=\"sourceCode bash\"><code class=\"sourceCode bash\"><span id=\"cb2-1\"><span class=\"co\">#!\/bin\/bash<\/span><\/span>\n<span id=\"cb2-2\"><span class=\"kw\">set<\/span> <span class=\"ex\">-eu<\/span><\/span>\n<span id=\"cb2-3\"><span class=\"co\"># https:\/\/stackoverflow.com\/a\/17841619<\/span><\/span>\n<span id=\"cb2-4\"><span class=\"kw\">function<\/span><span class=\"fu\"> join_by<\/span> <span class=\"kw\">{<\/span> <span class=\"bu\">local<\/span> <span class=\"va\">d=${1-}<\/span> <span class=\"va\">f=${2-}<\/span>; <span class=\"kw\">if<\/span> <span class=\"bu\">shift<\/span> 2<span class=\"kw\">;<\/span> <span class=\"kw\">then<\/span> <span class=\"bu\">printf<\/span> %s <span class=\"st\">\"<\/span><span class=\"va\">$f<\/span><span class=\"st\">\"<\/span> <span class=\"st\">\"<\/span><span class=\"va\">${@\/<\/span>#<span class=\"va\">\/$d}<\/span><span class=\"st\">\"<\/span><span class=\"kw\">;<\/span> <span class=\"kw\">fi<\/span>; <span class=\"kw\">}<\/span><\/span>\n<span id=\"cb2-5\"><\/span>\n<span id=\"cb2-6\"><span class=\"va\">local_webkit=<\/span>\/webkit<\/span>\n<span id=\"cb2-7\"><span class=\"va\">include_path=(<\/span><span class=\"st\">\"<\/span><span class=\"va\">$local_webkit<\/span><span class=\"st\">\"<\/span>\/WebKitBuild\/UserFlatpak\/runtime\/org.webkit.Sdk\/x86_64\/*\/active\/files\/include<span class=\"va\">)<\/span><\/span>\n<span id=\"cb2-8\"><span class=\"kw\">if<\/span><span class=\"bu\"> [<\/span> <span class=\"ot\">!<\/span> <span class=\"ot\">-f<\/span> <span class=\"st\">\"<\/span><span class=\"va\">${include_path[0]}<\/span><span class=\"st\">\/stdio.h\"<\/span><span class=\"bu\"> ]<\/span>; <span class=\"kw\">then<\/span><\/span>\n<span id=\"cb2-9\">  <span class=\"bu\">echo<\/span> <span class=\"st\">\"Couldn't find the directory hosting the \/usr\/include of the flatpak SDK.\"<\/span><\/span>\n<span id=\"cb2-10\">  <span class=\"bu\">exit<\/span> 1<\/span>\n<span id=\"cb2-11\"><span class=\"kw\">fi<\/span><\/span>\n<span id=\"cb2-12\"><span class=\"va\">include_path=<\/span><span class=\"st\">\"<\/span><span class=\"va\">${include_path[0]}<\/span><span class=\"st\">\"<\/span><\/span>\n<span id=\"cb2-13\"><span class=\"va\">mappings=(<\/span><\/span>\n<span id=\"cb2-14\">  <span class=\"st\">\"<\/span><span class=\"va\">$local_webkit<\/span><span class=\"st\">\/WebKitBuild\/GTK\/Debug=\/app\/webkit\/WebKitBuild\/Debug\"<\/span><\/span>\n<span id=\"cb2-15\">  <span class=\"st\">\"<\/span><span class=\"va\">$local_webkit<\/span><span class=\"st\">\/WebKitBuild\/GTK\/Release=\/app\/webkit\/WebKitBuild\/Release\"<\/span><\/span>\n<span id=\"cb2-16\">  <span class=\"st\">\"<\/span><span class=\"va\">$local_webkit<\/span><span class=\"st\">=\/app\/webkit\"<\/span><\/span>\n<span id=\"cb2-17\">  <span class=\"st\">\"<\/span><span class=\"va\">$include_path<\/span><span class=\"st\">=\/usr\/include\"<\/span><\/span>\n<span id=\"cb2-18\">)<\/span>\n<span id=\"cb2-19\"><\/span>\n<span id=\"cb2-20\"><span class=\"bu\">exec<\/span> <span class=\"st\">\"<\/span><span class=\"va\">$local_webkit<\/span><span class=\"st\">\"<\/span>\/Tools\/Scripts\/webkit-flatpak --gtk --debug run -c clangd --path-mappings=<span class=\"st\">\"<\/span><span class=\"va\">$(<\/span><span class=\"ex\">join_by<\/span> , <span class=\"st\">\"<\/span><span class=\"va\">${mappings[@]}<\/span><span class=\"st\">\"<\/span><span class=\"va\">)<\/span><span class=\"st\">\"<\/span> <span class=\"st\">\"<\/span><span class=\"va\">$@<\/span><span class=\"st\">\"<\/span><\/span><\/code><\/pre>\n<\/div>\n<p>Make sure to set the path of your WebKit repository in <code>local_webkit<\/code>.<\/p>\n<p>Then, in VS Code, go to File\/Preferences\/Settings, and in the left pane, search for <strong>Extensions\/clangd<\/strong>. Change <strong><em>Clangd: Path<\/em><\/strong> to the absolute path of the saved script above. <strong>I recomend making these changes in the Workspace tab, so they apply only to WebKit.<\/strong><\/li>\n<li>Create a symlink named <code>compile_commands.json<\/code> inside the root of the WebKit checkout directory pointing to the <code>compile_commands.json<\/code> file of the WebKit build you will be using, for instance: <code>WebKitBuild\/GTK\/Debug\/compile_commands.json<\/code><\/li>\n<li>Create a <code>.clangd<\/code> file inside the root of the WebKit checkout directory with these contents:\n<pre><code>If:\n    PathMatch: \"(\/app\/webkit\/)?Source\/.*\\\\.h\"\n    PathExclude: \"(\/app\/webkit\/)?Source\/ThirdParty\/.*\"\n\nCompileFlags:\n    Add: [-include, config.h]<\/code><\/pre>\n<p>This includes <code>config.h<\/code> in header files in WebKit files, with the exception of those in <code>Source\/ThirdParty<\/code>. Note: If you need to add additional rules, this is done by adding additional YAML <em>documents<\/em>, which are separated by a <code>---<\/code> line.<\/li>\n<li>VS Code clangd plugin doesn\u2019t read <code>.clangd<\/code> by default. Instead, it has to be instructed to do so by adding <code>--enable-config<\/code> to <strong><em>Clangd: Arguments<\/em><\/strong>. Also add <code>--limit-results=5000<\/code>, since the default limit for cross reference search results (100) is too small for WebKit.<strong>Additional tip<\/strong>: clangd will also add <code>#include<\/code> lines when you autocomplete a type. While the intention is good, this often can lead to spurious redundant includes. I have disabled it by adding <code>--header-insertion=never<\/code> to clangd\u2019s arguments.<\/li>\n<li>Restart VS Code. Next time you open a C++ file you will get a prompt requesting confirmating your edited configuration:\n<div><img decoding=\"async\" src=\"https:\/\/blogs.igalia.com\/aboya\/files\/2021\/10\/clangd-prompt.png\"><\/div>\n<\/li>\n<\/ol>\n<p>VS Code will start indexing your code, and you will see a progress count in the status bar.<\/p>\n<h3 id=\"debugging-problems\">Debugging problems<\/h3>\n<p>clangd has a log. To see it, click <strong><em>View\/Output<\/em><\/strong>, then in the Output panel combo box, select <em>clangd<\/em>.<\/p>\n<div><img decoding=\"async\" src=\"https:\/\/blogs.igalia.com\/aboya\/files\/2021\/10\/clangd-output.png\"><\/div>\n<p>The clangd database is stored in <code>.cache\/clangd<\/code> inside the WebKit checkout directory. <code>rm -rf<\/code>\u2019ing that directory will reset it back to its initial state.<\/p>\n<p>For each compilation unit indexed, you\u2019ll find a file following the pattern <code>.cache\/clangd\/index\/&lt;Name&gt;.&lt;Hash&gt;.idx<\/code>. For instance: <code>.cache\/clangd\/index\/MediaSampleGStreamer.cpp.0E0C77DCC76C3567.idx<\/code>. This way you can check whether a particular compilation unit has been indexed.<\/p>\n<h3 id=\"bug-some-files-are-not-indexed\">Bug: Some files are not indexed<\/h3>\n<p>You may notice VS Code has not indexed all your files. This is apparent when using the <em>Find all references<\/em> feature, since you may be missing results. This in particular affects to generated code, in particular unified sources (.cpp files generated by concatenating via <code>#include<\/code> a series of related .cpp files with the purpose of speeding up the build, compared to compiling them as individual units).<\/p>\n<p>I don\u2019t know the reason for this bug, but I can confirm the following workaround: Open a UnifiedSources file. Any UnifiedSources file will do. You can find them in paths such as <code>WebKitBuild\/GTK\/Debug\/WebCore\/DerivedSources\/unified-sources\/UnifiedSource-043dd90b-1.cpp<\/code>. After you open any of them, you\u2019ll see VS Code indexing over a thousand files that were skipped before. You can close the file now. <em>Find all references<\/em> should work once the indexing is done.<\/p>\n<h3 id=\"things-that-work\">Things that work<\/h3>\n<p>Overall I\u2019m quite satisfied with the setup. The following features work:<\/p>\n<ul>\n<li>Autocompletion:\n<div><img decoding=\"async\" src=\"https:\/\/blogs.igalia.com\/aboya\/files\/2021\/10\/clangd-autocompletion.png\"><\/div>\n<\/li>\n<li><code>.<\/code> gets replaced to <code>-&gt;<\/code> when autocompleting a member inside an object accessible by dereferencing a pointer or smart pointer. (<code>.<\/code> will autocomplete not only the members of the object, but also of the pointee).<\/li>\n<li>Right click\/Find All References: What it founds is accurate, although I don\u2019t feel very confident in it being exhaustive, as that requires a full index.<\/li>\n<li>Right click\/Show Call Hierarchy: This a useful tool that shows what functions call the selected function, and so on, automating what otherwise is a very manual process. At least, when it\u2019s exhaustive enough.<\/li>\n<li>Right click\/Type hierarchy: It shows the class tree containing a particular class (ancestors, children classes and siblings).\n<div><img decoding=\"async\" src=\"https:\/\/blogs.igalia.com\/aboya\/files\/2021\/10\/clangd-type-hierarchy.png\"><\/div>\n<\/li>\n<li>Error reporting: the right bar of VS Code will show errors and warnings that clangd identifies with the code. It\u2019s important to note that there is a maximum number of errors per file, after which the checking will stop, so it\u2019s a good idea to start from the top of the file. The errors seem quite precise and avoid a lot of trips to the compiler. Unfortunately, they\u2019re not completely exhaustive, so even after the file shows no errors in clangd, it might still show errors in the actual compiler, but it still catches most with very detailed information.\n<div><img decoding=\"async\" src=\"https:\/\/blogs.igalia.com\/aboya\/files\/2021\/10\/clangd-error.png\"><\/div>\n<\/li>\n<li>Signature completion: after completing a function, you get help showing you what types the parameters expect<\/li>\n<\/ul>\n<h3 id=\"known-issues-and-workarounds\">Known issues and workarounds<\/h3>\n<h4 id=\"go-to-definition-not-working-sometimes\">\u201cGo to definition\u201d not working sometimes<\/h4>\n<p>If \u201cGo to definition\u201d (ctrl+click on the name of a function) doesn\u2019t work on a header file, try opening the source file by pressing Ctrl+o, then go back to the header file by pressing Ctrl+o again and try going to definition again.<\/p>\n<h4 id=\"base-functions-of-overriden-functions-dont-show-up-when-looking-for-references\">Base functions of overriden functions don\u2019t show up when looking for references<\/h4>\n<p>Although this is supposed to be a <a href=\"https:\/\/github.com\/clangd\/clangd\/issues\/46\">closed issue<\/a> I can still reproduce it. For instance, when searching for uses of <code>SourceBufferPrivateGStreamer::enqueueSample()<\/code>, calls to the parent class, <code>SourceBufferPrivate::enqueueSample()<\/code> get ignored.<\/p>\n<p>This is also a common issue when using <em>Show Call Hierarchy<\/em>.<\/p>\n<h4 id=\"lots-of-strange-errors-after-a-rebase\">Lots of strange errors after a rebase<\/h4>\n<p>Clean the cache, reindex the project. Close VS Code, <code>rm -rf .cache\/clangd\/index<\/code> inside the WebKit checkout directory, then open VS Code again. Remember to open a UnifiedSources file to create a complete index.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lately I\u2019m working on a refactor in the append pipeline of the MediaSource Extensions implementation of the WebKit for the GStreamer ports. Working on refactors often triggers many build issues, not only because they often encompass a lot of code, but also because it\u2019s very easy to miss errors in the client code when updating &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blogs.igalia.com\/aboya\/2021\/10\/02\/setting-up-visualstudio-code-to-work-with-webkitgtk-using-clangd\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Setting up VisualStudio code to work with WebKitGTK using clangd&#8221;<\/span><\/a><\/p>\n","protected":false},"author":57,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-28","post","type-post","status-publish","format-standard","hentry","category-uncategorized","entry"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts\/28","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/users\/57"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/comments?post=28"}],"version-history":[{"count":28,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts\/28\/revisions"}],"predecessor-version":[{"id":104,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/posts\/28\/revisions\/104"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/media?parent=28"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/categories?post=28"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/aboya\/wp-json\/wp\/v2\/tags?post=28"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}