Skip to content

Commit

Permalink
Merge branch 'axivion-basic-windows-support'
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsauer committed Apr 6, 2022
1 parent fa7e9fc commit 3b14e77
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 78 deletions.
5 changes: 3 additions & 2 deletions .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
.DS_Store

# Bazel
# Ignore build output symlinks
# Ignore build output links
/bazel-*
/external

# Clangd
/compile_commands.json
/.cache
/.cache/
26 changes: 13 additions & 13 deletions ImplementationReadme.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ You should then be able to make local changes and see their effects immediately.

### Word Wrap

To edit this repository happily, you'll likely want to turn on word wrap in your editor. For example, in VSCode, Settings>Editor: Word Wrap>on (or bounded),
To edit this repository happily, you'll likely want to turn on word wrap in your editor. For example, in VSCode, Settings>Editor: Word Wrap>on (or bounded).

We use some fairly long lines and don't hard wrap at e.g. 80ch. The philosophy is that that these days, we're all trying to make the most of variable-width windows rather than the fixed-width terminals/punchcards of yore (punchcards being the origin of the 80ch convention!).

Expand Down Expand Up @@ -85,7 +85,7 @@ This means that fancy things like specifying environment variables in the comman

clangd also tries to introspect the compiler specified to figure out what include paths it adds implicitly. Usually it just checks the relative path, following clang (and maybe others') conventions. Iff you use the --query-driver flag it will directly invoke the compiler and ask it about those includes [[issue about making query driver automatic, which it really should be](https://github.com/clangd/clangd/issues/539)]. If you don't specify --query-driver and it can't find the includes at the relative path (like in the case of Bazel's compiler wrappers) it will miss those default includes. If you're seeing red squigglies under, e.g., standard library headers or system headers that should be included by default, you've probably run into a failure of this type.

All this means it's crucial to de-Bazel the command we write to compile_commands.json so clangd can invoke it to get default header search paths--and so clangd can understand its flags *without* invoking it. No compiler driver wrappers requiring Bazel-specific environment variables to run, nor flags that need custom expansion clangd wouldn't know about, etc. All this happens in [refresh.template.py](./refresh.template.py), details there.
All this means it's crucial to de-Bazel the command we write to compile_commands.json enough that clangd can invoke it to get default header search paths--and so clangd can understand its flags *without* invoking it. No compiler driver wrappers requiring Bazel-specific environment variables to run, nor flags that need custom expansion clangd wouldn't know about, etc. All this happens in [refresh.template.py](./refresh.template.py), details there.

If you see warning messages like "compilation failed" and "index may be incomplete" for almost all entries in the clangd log (view in VSCode under Output>clangd), it's because clangd is misparsing the command in a way that breaks its ability to understand things. A few messages like this are fine; they come from (poorly-designed) headers that depend on include order. (See also note about this in https://github.com/hedronvision/bazel-compile-commands-extractor/issues/2].)

Expand All @@ -95,41 +95,41 @@ If you see warning messages like "compilation failed" and "index may be incomple

We're using the simplest complete subset of compile_commands.json keys (command, file, directory), because it's easy, general, and gets us everything we need.

#### Compilation Working Directory and the //external Symlink
#### Compilation Working Directory and the //external Link

We make the choice to set the compilation working directory ("directory" key) to our Bazel workspace root (`bazel info workspace`), because it always contains *all* the source files we need, rather than a subset.

##### The Trap Being Avoided

I'm calling out this decision explicitly, because there's a tempting trap that caught Bazel's official editor plugins (Android Studio, Xcode, ...) before we helped fix them.

Do not be tempted to set the compilation "directory" to the bazel execroot (`bazel info execution_root`). The execroot may seem to work but breaks subtly on the next build; the execroot directory is reconfigured for whatever new target is being built, deleting the symlinks to external workspaces and top-level packages not used by that particular build. Using the execroot might be tempting because that *is* where Bazel says it's going to invoke the command it gave you, but don't do it! It'll only have the subset of the code used for the last build, not for all build, breaking the paths used for editing the rest of the codebase.
Do not be tempted to set the compilation "directory" to the bazel execroot (`bazel info execution_root`). The execroot may seem to work but breaks subtly on the next build; the execroot directory is reconfigured for whatever new target is being built, deleting the links to external workspaces and top-level packages not used by that particular build. Using the execroot might be tempting because that *is* where Bazel says it's going to invoke the command it gave you, but don't do it! It'll only have the subset of the code used for the last build, not for all build, breaking the paths used for editing the rest of the codebase.

Remember that the key goal of compile_commands.json is to "de-Bazel" the build commands into something clangd can understand, independent of bazel. Not pointing into bazel's temporary build scratch space (execroot) is an important part of decoupling from bazel.
Remember that the key goal of compile_commands.json is to "de-Bazel" the build commands into something clangd can understand. Not pointing into bazel's temporary build scratch space (execroot) is an important part of decoupling from bazel's transitory state.

##### Generated files: //external Symlink makes external dependencies work
##### Generated files: //external link makes external dependencies work

Having avoided the execroot trap, we have compile_commands.json describing compile commands directly in our workspace.

There are two other important cases to consider: generated files and external code. In each of these cases, we can't point to a bazel-independent source; Bazel generates the files! But we can choose to point into a cache where Bazel *accumulates* the files rather than the execroot build directory, where some disappear on each build.

For generated files, Bazel creates a nice symlink for us in our workspace, `//bazel-out` that points into the cache. This location accumulates the most recent build products for each platform (despite being in execroot.) Commands then just work because `//bazel-out` has the same name and relative path as the `bazel-out` in Bazel's compilation sandbox. Great!
For generated files, Bazel creates a nice link for us in our workspace, `//bazel-out` that points into the cache. This location accumulates the most recent build products for each platform (despite being in execroot.) Commands then just work because `//bazel-out` has the same name and relative path as the `bazel-out` in Bazel's compilation sandbox. Great!

For external code (that we've brought in via WORKSPACE), Bazel's build commands look for an `//external` directory in the workspace. Bazel doesn't create a symlink for it by default...so we created one, and everything works.
For external code (that we've brought in via WORKSPACE), Bazel's build commands look for an `//external` directory in the workspace. Bazel doesn't create a link for it by default...so we created one, and everything works.

The external symlink thus makes the Bazel workspace root an accurate reflection of all source files in the project, including external code and generated code, with paths the same as in Bazel's build sandbox. The `//external` symlink is also handy to be able to easily see what code Bazel has pulled in from the outside!
The //external link thus makes the Bazel workspace root an accurate reflection of all source files in the project, including external code and generated code, with paths the same as in Bazel's build sandbox. The `//external` link is also handy to be able to easily see what code Bazel has pulled in from the outside!

###### More details on //external

We created the symlink with `ln -s bazel-out/../../../external .`
We auto-create the link with the equivalent of `ln -s bazel-out/../../../external .` in [refresh.template.py](./refresh.template.py)'s `_ensure_external_workspaces_link_exists`

This points into the accumulating cache under the output base, where external code is cached and accumulated. Crucially, it *doesn't* point to the temporary references to external code in execroot/external. [See above for how execroot is a trap. You can read more about output_base and execution_root [here](https://docs.bazel.build/versions/main/output_directories.html)]

[Linking via `bazel-<WORKSPACE_DIRECTORY_NAME>/../../external` would also have been okay, since it points to the same place, but would have broken if the workspace directory name changed and seemed less clean semantically.]
[Linking via `bazel-<WORKSPACE_DIRECTORY_NAME>/../../external` would also have pointed to the same place, but would have broken if the workspace directory name changed. It also seemed less clean semantically.]

Another good option would be having [refresh.template.py](./refresh.template.py) patch external paths, rather than pointing through a symlink. It could prepend paths starting with "external/" with the path of the symlink to get equivalent behavior. We only because it's also a handy way to browse the source code of external dependencies.
Another option would be having [refresh.template.py](./refresh.template.py) patch external paths, rather than pointing through a link. It could detect and prepend to paths starting with "external/" or "bazel-out/" with the path of the link to get equivalent behavior. However, there's some trickiness there and //external is a handy way for humans to browse the source code of external dependencies.

It looks like long ago--and perhaps still inside Google—Bazel created such an `//external` symlink. Tulsi, Bazel's XCode helper, once needed the `//external` symlink in the workspace to properly pick up external dependencies. This is no longer true, though you can see some of the history [here](https://github.com/bazelbuild/tulsi/issues/164).
It looks like long agoand perhaps still inside Google—Bazel created such an `//external` link. Tulsi, Bazel's XCode helper, once needed the `//external` link in the workspace to properly pick up external dependencies. This is no longer true, though you can see some of the history [here](https://github.com/bazelbuild/tulsi/issues/164).


## Choice of Strategy for Listening To Bazel
Expand Down
19 changes: 5 additions & 14 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,11 @@ We'd strongly recommend you set up [Renovate](https://github.com/renovatebot/ren

If not now, maybe come back to this step later, or watch this repo for updates. [Or hey, maybe give us a quick star, while you're thinking about watching.] Like Abseil, we live at head; the latest commit to the main branch is the commit you want.

### Make external code easily browsable. (also necessary for this tool to function correctly)

From your Bazel workspace root (i.e. `//`), run:

```ln -s bazel-out/../../../external .```

This makes it easy for you—and for build tooling—to see the external dependencies you bring in. It also makes your source tree have the same directory structure as the build sandbox. It looks like long ago—and perhaps still inside Google—Bazel automatically created such an `//external` symlink. In any event, it's a win/win to add it: It's easier for you to browse the code you use, and it eliminates whole categories of edge cases for build tooling. We'd recommend you commit this symlink to your repo so your collaborators have it, too, because they'll need it for this tool to work.

### Get the extractor running.

We'll generate a compile_commands.json file in the root of the Bazel workspace (Product/).
We'll generate a compile_commands.json file in the root of the Bazel workspace.

That file describes how Bazel is compiling all the (Objective-)C(++) files. With the compile commands in a common format, build-system-independent tooling (e.g. clangd autocomplete, clang-tidy linting etc.), can get to work.
That file describeds how Bazel is compiling all the (Objective-)C(++) files. With the compile commands in a common format, build-system-independent tooling (e.g. clangd autocomplete, clang-tidy linting etc.), can get to work.

We'll get it running and then move onto the next section while it whirrs away. But in the future, every time you want tooling (like autocomplete) to see new BUILD-file changes, rerun the command you chose below! Clangd will automatically pick up the changes.

Expand Down Expand Up @@ -115,7 +107,6 @@ refresh_compile_commands(
)
```


## Editor Setup — for autocomplete based on compile_commands.json

### VSCode
Expand All @@ -129,6 +120,8 @@ code --uninstall-extension ms-vscode.cpptools

Then, open VSCode *user* settings, so things will be automatically set up for all projects you open.

Search for "clangd".

Add the following three separate entries to `"clangd.arguments"`:
```
--header-insertion=never
Expand Down Expand Up @@ -168,14 +161,12 @@ Behind the scenes, that compile_commands.json file contains entries describing a

### Here's what you should be expecting, based on our experience:

We use this tool every day to develop a cross-platform library for iOS and Android on macOS. Expect Android completion in Android source, macOS in macOS, iOS in iOS, etc. We have people using it on Linux/Ubuntu, too.
We use this tool every day to develop a cross-platform library for iOS and Android on macOS. Expect Android completion in Android source, macOS in macOS, iOS in iOS, etc. People use it on Linux/Ubuntu and Windows, too.

All the usual clangd features should work. CMD/CTRL+click navigation (or option if you've changed keybindings), smart rename, autocomplete, highlighting etc. Everything you expect in an IDE should be there (because most good IDEs are backed by clangd). As a general principle: If you're choosing tooling that needs to understand a programming language, you want it to be based on a compiler frontend for that language, which clangd does as part of the LLVM/clang project.

Everything should also work for generated files, though you may have to run a build for the generated file to exist.

We haven't yet tested on Windows. Windows might need some patching parallel to that for macOS (in [refresh.template.py](./refresh.template.py)), but it should be a relatively easy adaptation compared to writing things from scratch. If you're trying to use it on Windows, let us know [here](https://github.com/hedronvision/bazel-compile-commands-extractor/issues/8). We'd love to work together to get things working smoothly.

## Rough Edges

We've self-filed issues for the rough edges we know about and are tracking. We'd love to hear from you there about what you're seeing, good and bad. Please add things if you find more rough edges, and let us know if you need help or more features.
Expand Down
1 change: 0 additions & 1 deletion external

This file was deleted.

Loading

0 comments on commit 3b14e77

Please sign in to comment.