diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8a6255c..9d27ce01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,16 +4,26 @@ All contributions require a review from the [code owners](.github/CODEOWNERS). -## Code Style and Code Quality +## Code Quality -The project uses several formatters and linters. [poetry](https://github.com/python-poetry/poetry) and -[pre-commit-hooks](https://github.com/pre-commit/pre-commit-hooks) are used to manage and execute those. +The project uses several formatters and linters. +[poetry](https://github.com/python-poetry/poetry) and [pre-commit-hooks](https://github.com/pre-commit/pre-commit-hooks) are used to manage and execute those. When contributing code, please make sure to execute the checks. After you have installed `poetry` for your platform install the tools required by DWYU: `poetry install`. -Then, you can execute all relevant checks via `poetry run pre-commit run --all-files` or configure `pre-commit-hooks` -to run automatically for each commit. +Then, you can execute all relevant checks via `poetry run pre-commit run --all-files` or configure `pre-commit-hooks` to run automatically for each commit. + +## Code Style + +### Markdown + +Most of the markdown stile is automatically enforced with [mdformat](https://github.com/executablebooks/mdformat). +Some part is however maintained manually: + +- Each sentence starts in a new line. +- Sentences are not wrapped, no matter how long they are. +- `
` is used to enforce a newline. # Bug reports diff --git a/README.md b/README.md index 1cda14c8..49de9079 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,15 @@ DWYU is a Bazel aspect for C++ projects making sure the headers your Bazel targets are using are aligned with their dependency lists. -DWYUs enforces the design principle:
-**A `cc_*` target X shall depend directly on the targets providing the header files which are included in the source code of X.** +DWYUs enforces the design principle:
+**A `cc_*` target _X_ shall depend directly on the targets providing the header files which are included in the source code of _X_.** The main features are: - Finding include statements which are not available through a direct dependency, aka **preventing to rely on transitive dependencies for includes**. - Finding unused dependencies. -- Given one uses the latest experimental Bazel features, making sure one distinguishes properly between public and - private dependencies for `cc_library`. For more details see [features chapter Implementation_deps](#Implementation_deps). +- Given one uses [`implementation_deps`](https://bazel.build/reference/be/c-cpp#cc_library.implementation_deps), making sure one distinguishes properly between public and private dependencies for `cc_library` targets. + For more details see [features chapter Implementation_deps](#Implementation_deps). More information about the idea behind DWYU and the implementation of the project is available in [the docs](docs/). @@ -70,7 +70,6 @@ dwyu_setup_step_3() ### Configure the aspect -Configure an aspect with the desired behavior. The features which can be configured through the aspect factory attributes are documented at [Features](#features). Put the following inside a `aspect.bzl` file (file name is exemplary): @@ -84,12 +83,11 @@ your_dwyu_aspect = dwyu_aspect_factory() ### Use the aspect -Invoke the aspect through the command line on a target:
+Invoke the aspect through the command line on a target:
`bazel build --aspects=//:aspect.bzl%your_dwyu_aspect --output_groups=cc_dwyu_output` -If no problem is found, the command will exit with `INFO: Build completed successfully`.
-If a problem is detected, the build command will fail with an error and a description of the problem will be printed in -the terminal. For example: +If no problem is found, the command will exit with `INFO: Build completed successfully`.
+If a problem is detected, the build command will fail with an error and a description of the problem will be printed in the terminal. For example: ``` ================================================================================ @@ -102,10 +100,10 @@ Unused dependencies in 'deps' (none of their headers are referenced): =============================================================================== ``` -### Create a rule invoking the aspect. +### Create a rule invoking the aspect -You can invoke the aspect from within a rule. This can be useful to make the execution part of a bazel build (e.g. -`bazel build //...`) without having to manually execute the longish aspect build command. +You can invoke the aspect from within a rule. +This can be useful to make the execution part of a bazel build without having to manually execute the longish aspect build command. The Bazel documentation for invoking an aspect from within a rule can be found [here](https://bazel.build/rules/aspects#invoking_the_aspect_from_a_rule). A concrete example for doing so for the DWYU aspect can be found in [a rule in the recursion test cases](test/aspect/recursion/rule.bzl). @@ -123,7 +121,8 @@ You can exclude a custom set of header files by providing a config file in json your_aspect = dwyu_aspect_factory(config = "//.json") ``` -The config file can contain these fields which should be lists of strings. All fields are optional: +The config file can contain these fields which should be lists of strings. +All fields are optional: | Field | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -135,34 +134,30 @@ Examples and the correct format can be seen at the [custom config test cases](te ## Skipping targets -If you want the DWYU aspect to skip certain targets and negative target patterns are not an option you can do so by -setting the `no-dwyu` tag on those. +If you want the DWYU aspect to skip certain targets and negative target patterns are not an option you can do so by setting the `no-dwyu` tag on those. ## Recursion By default, DWYU analyzes only the target it is being applied to. -You can also activate recursive analysis. Meaning the aspect analyzes recursively all dependencies of the target it is -being applied to: +You can also activate recursive analysis. Meaning the aspect analyzes recursively all dependencies of the target it is being applied to: ```starlark your_aspect = dwyu_aspect_factory(recursive = True) ``` -This can be used to create a rule invoking DWYU on a target and all its dependencies as part of a normal build command. -Also, it can be a convenience to analyze specific fraction of your stack without utilizing bazel (c)query. +Analyzing a targets and its whole build tree is a common use case. +This feature allows you doing so without manually using (c)query to create the corresponding target list and forwarding it to DWYU. Examples for this can be seen at the [recursion test cases](test/aspect/recursion). ## Implementation_deps -Starting with version 5.0.0 Bazel offers experimental feature [`implementation_deps`](https://docs.bazel.build/versions/5.0.0/be/c-cpp.html#cc_library.implementation_deps) -to distinguish between public (aka interface) and private (aka implementation) dependencies for `cc_library`. +Bazel offers the experimental feature [`implementation_deps`](https://bazel.build/reference/be/c-cpp#cc_library.implementation_deps) to distinguish between public (aka interface) and private (aka implementation) dependencies for `cc_library`. Headers from the private dependencies are not made available to users of the library to trim down dependency trees. -DWYU analyzes the usage of headers from the dependencies and can raise an error if a dependency is used only in -private files, but not put into the private dependency attribute. Meaning, it can find dependencies which should be -moved from `deps` to `implementation_deps`. +DWYU analyzes the usage of headers from the dependencies and can raise an error if a dependency is used only in private files, but not put into the private dependency attribute. +Meaning, it can find dependencies which should be moved from `deps` to `implementation_deps`. Activate this behavior via: @@ -176,9 +171,8 @@ Examples for this can be seen at the [implementation_deps test cases](test/aspec - If includes are added through a macro, this is invisible to DWYU. - Fundamental support for processing preprocessor defines is present. - However, if header A specifies a define X and is included in header B, header B is not aware of X from header A. - Right now only defines specified through Bazel (e.g. toolchain or `cc_*` target attributes) or defines specified - inside a file itself are used to process a file and discover include statements. + However, if header _A_ specifies a define _X_ and is included in header _B_, header _B_ is not aware of _X_ from header _A_. + Right now only defines specified through Bazel (e.g. toolchain or `cc_*` target attributes) or defines specified inside a file itself are used to process a file and discover include statements. We aim to resolve this limitation in a future release. - Include statements utilizing `..` are not recognized if they are used on virtual or system include paths. @@ -187,52 +181,51 @@ Examples for this can be seen at the [implementation_deps test cases](test/aspec DWYU offers a tool to automatically fix some detected problems. ⚠ -Please note that **the tool cannot guarantee that your build is not being broken** by the changes. Always make sure your -project is still valid after the changes and review the performed changes. +Please note that **the tool cannot guarantee that your build is not being broken** by the changes. +Always make sure your project is still valid after the changes and review the performed changes. The workflow is the following: -1. Execute DWYU on your workspace. DWYU will create report files containing information about discovered problems in the - Bazel output directory for each analyzed target. -1. Execute `bazel run @depend_on_what_you_use//:apply_fixes -- `. The tool discovers the report files - generated in the previous step and gathers the problems for which a fix is available. Then, - [buildozer](https://github.com/bazelbuild/buildtools/blob/master/buildozer/README.md) is utilized to adapt the BUILDS - files in your workspace. +1. Execute DWYU on your workspace. + DWYU will create report files containing information about discovered problems in the Bazel output directory for each analyzed target. +1. Execute `bazel run @depend_on_what_you_use//:apply_fixes -- `. + The tool discovers the report files generated in the previous step and gathers the problems for which a fix is available. + Then, [buildozer](https://github.com/bazelbuild/buildtools/blob/master/buildozer/README.md) is utilized to adapt the BUILD files in your workspace. -The `apply_fixes` tool requires you to explicitly choose which kind or errors you want to be fixed. You can see the full -command line interface and more information about the script behavior and limitations by executing:
+The `apply_fixes` tool requires you to explicitly choose which kind or errors you want to be fixed. +You can see the full command line interface and more information about the script behavior and limitations by executing:
`bazel run @depend_on_what_you_use//:apply_fixes -- --help` -If the `apply_fixes` tool is not able to discover the report files, this can be caused by the `bazel-bin` convenience -symlink at the workspace root not existing or not pointing to the output directory which was used by to generate the -report files. The tool offers options to control how the output directory is discovered. +If the `apply_fixes` tool is not able to discover the report files, this can be caused by the `bazel-bin` convenience symlink at the workspace root not existing or not pointing to the output directory which was used by to generate the report files. +The tool offers options to control how the output directory is discovered. Unfortunately, the tool cannot promise perfect results due to various constraints: -- If alias targets are involved, this cannot be processed properly. Alias targets are resolved to their actual target - before the DWY aspect is running. Thus, the DWYU report file contains the actual targets in its report and buildozer - is not able to modify the BUILD files containing the alias name. -- Buildozer is working on the plain BUILD files as a user would see them in the editor. Meaning without alias resolution - or macro expansion. Consequently, buildozer cannot work on targets which are generated inside a macro or whose name - is constructed in a list comprehension. -- Generally the fixes should not break your build. But there are edge cases. For example a dependency X might be unused - in library A, but the downstream user of library A transitively depends on it. Removing the unused dependency from - library A will break the build as the downstream dependency no longer finds dependency X. -- Adding missing direct dependencies is based on a heuristic and not guaranteed to find the correct dependency. Also - analyzing the visibility of potential direct dependencies is not yet implemented, which can cause a broken build if - a target without the proper visibility is chosen. +- If alias targets are involved, this cannot be processed properly. + Alias targets are resolved to their actual target before the DWYU aspect is running. + Thus, the DWYU report file contains the actual targets in its report and buildozer is not able to modify the BUILD files containing the alias name. +- Buildozer is working on the plain BUILD files as a user would see them in the editor. + Meaning without alias resolution or macro expansion. + Consequently, buildozer cannot work on targets which are generated inside a macro or whose name is constructed. +- Adding missing direct dependencies is based on a heuristic and not guaranteed to find the correct dependency. +- If you execute DWYU only on some targets and not the complete build tree, this can break the overall build. + For example dependency _X_ in library _A_ is unused and would be removed. + But a downstream user of library _A_ might transitively depend on _X_. + Removing the unused dependency will break the build as the downstream dependency no longer finds dependency _X_. # Preconditions -**The code has to be compilable**
-DWYU is not performing a compilation itself. It works by statically analyzing the source code and build tree. However, -non compiling code can contain errors which infringe the assumptions DWYU is based on. For example, including header -files which do not exist at the expected path. +##### The code has to be compilable -**Include paths have to be unambiguous**
-In other words, there shall not be multiple header files in the dependency tree of a target matching an -include statement. Even if analysing the code works initially, it might break at any time if the ordering of paths in -the analysis changes. +DWYU is not performing a compilation itself. +It works by statically analyzing the source code and build tree. +However, non compiling code can contain errors which infringe the assumptions DWYU is based on. +For example, including header files which do not exist at the expected path. + +##### Include paths have to be unambiguous + +There shall not be multiple header files in the dependency tree of a target matching an include statement. +Even if analysing the code works initially, it might break at any time if the ordering of paths in the analysis changes. # Supported Platforms @@ -247,8 +240,7 @@ the analysis changes. ## Layering check -To make sure no headers from transitive dependencies or private headers from dependencies are used you can use -[Layering check with Clang](https://maskray.me/blog/2022-09-25-layering-check-with-clang) which is natively supported by Bazel. +To make sure no headers from transitive dependencies or private headers from dependencies are used you can use [Layering check with Clang](https://maskray.me/blog/2022-09-25-layering-check-with-clang) which is natively supported by Bazel. This approach has some benefits over DWYU: - Directly integrated into Bazel without need for further tooling. @@ -256,10 +248,11 @@ This approach has some benefits over DWYU: Still, there are reasons to use DWYU instead of or in addition to layering_check: -- DWYU Does not require a compiler, it works purely by text parsing. +- DWYU does not require a compiler, it works purely by text parsing. This is the reason for some of it's [known DWYU limitations](#known-limitations). However, this also makes the tool more flexible and independent of your platform. For example when using a recent clang version is not possible for you. +- DWYU is able to analyze header only libraries. - DWYU detects unused dependencies. - DWYU allows optimizing [implementation_deps](#implementation_deps). - DWYU offers automatic fixes for detected issues. @@ -269,23 +262,19 @@ Still, there are reasons to use DWYU instead of or in addition to layering_check [Gazelle](https://github.com/bazelbuild/bazel-gazelle) is a tool automatically creating `BUILD` files for your code. It seems there is no public and established C++ extension for gazelle. -Still, if one agrees with the best practices enforced by DWYU but cannot use it investing time into a gazelle C++ -extension might be worth it. -Automatically generating correct BUILD files based on your source code is most likely a more efficient approach compared -to having to manually execute DWYU regularly to make sure no error was introduced. +Still, if one agrees with the best practices enforced by DWYU but cannot use it, investing time into a gazelle C++ extension might be worth it. +Automatically generating correct BUILD files based on your source code is a more efficient approach compared to having to manually execute DWYU regularly to make sure no error was introduced. # Versioning This project uses [semantic versioning](https://semver.org/spec/v2.0.0.html). -Please be aware that the project is still in an early phase and until version 1.0.0 has been reached all releases -can contain breaking changes. +Please be aware that the project is still in an early phase and until version 1.0.0 has been reached all releases can contain breaking changes. **The following things can always break** and do not promise stability with respect to the semantic versioning: - The report files DWYU generates to facilitate running automatic fixes are considered an implementation detail. Changing their content is not considered a breaking change. - You are of course free to use those report files in custom scripts of yours, but might have to adapt those scripts for - DWYU updates. + You are of course free to use those report files in custom scripts of yours, but might have to adapt those scripts for DWYU updates. - How to include DWYU in your WORKSPACE file might change at any time. # Contributing @@ -294,7 +283,7 @@ See [Contributing](CONTRIBUTING.md). # License -Copyright © 2022-present, [Martin Medler](https://github.com/martis42).
+Copyright © 2022-present, [Martin Medler](https://github.com/martis42).
This project licensed under the [MIT](https://opensource.org/licenses/MIT) license. This projects references several other projects which each have individual licenses. diff --git a/docs/concept_behind_dwyu.md b/docs/concept_behind_dwyu.md index 5df5b5cc..851a57c8 100644 --- a/docs/concept_behind_dwyu.md +++ b/docs/concept_behind_dwyu.md @@ -3,32 +3,31 @@ Depend On What You Use (DWYU) has been inspired by [Include What You Use (IWYU)](https://github.com/include-what-you-use/include-what-you-use). Essentially DWYU applies the idea behind IWYU to the relationship between C++ include statements and dependencies. -DWYUs design philosophy is
+DWYUs design philosophy is
**A C++ target shall list all dependencies from which it is including header files. Header files from transitive dependencies shall not be used.** -For example, target A depends on `cc_library` B. B depends on `cc_library` C. -A is allowed to include headers from target B. -A is however not allowed to use headers from C, unless it specifies C as direct dependency.
+For example, target _A_ depends on `cc_library` _B_. +_B_ depends on `cc_library` _C_. +_A_ is allowed to include headers from target _B_. +_A_ is however not allowed to use headers from _C_, unless it specifies _C_ as direct dependency.
This rule is not enforced by the compiler. -A using headers from C would compile, since all public headers from all direct and transitive dependencies are present -in the Bazel sandbox and are part of the include path for compiling A. +_A_ using headers from _C_ would compile, since all public headers from all direct and transitive dependencies are present in the Bazel sandbox and are part of the include path for compiling _A_. Adhering to this design principle has several advantages: -- One follows the Bazel dependency modeling best practices. Bazel is not yet generally enforcing its depndency rules, - but this can change at any time. For more details see: +- One follows the Bazel dependency modeling best practices. + Bazel is not yet generally enforcing its dependency rules, but this can change at any time. + For more details see: - `cc_library` design [regarding include paths](https://bazel.build/reference/be/c-cpp#hdrs). - Dependencies concept [documentation](https://bazel.build/concepts/dependencies#actual-and-declared-dependencies). - - Dependency management [documentation](https://bazel.build/basics/dependencies). Note that Bazel is already enforcing - limited access to transitive dependencies for Java code. + - Dependency management [documentation](https://bazel.build/basics/dependencies). + Note that Bazel is already enforcing limited access to transitive dependencies for Java code. - Bazel performs efficient incremental builds by analyzing the dependency tree of the targets and doing only what is really required. A dependency tree modeling the C++ source code as close as possible enables Bazel to work most efficiently. - Depending on transitive dependencies can cause unexpected build failures. - Assume one of your dependencies X is dropping its transitive dependency Y. - This should not influence your target as long as Xs interface is not changing. - However, if you use headers from Y, your build will fail unexpectedly. -- While reading a BUILD file one sees at a glance all other targets directly influencing the content of the BUILD file - without having to read the source code. -- When analyzing the direct downstream dependencies of `cc_library` X (e.g. with bazel query) one can be sure that all - places where headers of X are used are discoverable through the Bazel dependency tree. + Assume one of your dependencies _X_ is dropping its transitive dependency _Y_. + This should not influence your target as long as the interface of _X_ is not changing. + However, if you use headers from _Y_, your build will fail unexpectedly. +- While reading a BUILD file one sees at a glance all other targets directly influencing the content of the BUILD file without having to read the source code. +- When analyzing the direct downstream dependencies of `cc_library` _X_ one can be sure that all places where headers of _X_ are used are discoverable through the Bazel dependency tree. diff --git a/docs/project_design_rationales.md b/docs/project_design_rationales.md index 79d76860..389e4e89 100644 --- a/docs/project_design_rationales.md +++ b/docs/project_design_rationales.md @@ -13,31 +13,34 @@ ## Why use Python -There are many programming languages available which could be used to implement DWYU. Why use Python because: +There are many programming languages available which could be used to implement DWYU. +Why use Python because: - It is well established tool many developers are familiar with. - Most platforms support Python well and developer setups often have an interpreter pre-installed. - There is a wide range of well established third-party libraries. - There is no need to deploy pre compiled binaries for a wide range of platforms. - It is well suited for scripting tasks and testing. -- The task done by DWYU does not require many resources. Thus, efficiency and performance are secondary. -- The project maintainer is experienced with Python. +- The task done by DWYU does not require many resources. + Thus, efficiency and performance are secondary. ## Why use a multi step automatic fixes workflow -Having to execute a separate tool to apply fixes seems bothersome. Ideally, DWYU would perform fixes -while analyzing the problems.
+Having to execute a separate tool to apply fixes seems bothersome. +Ideally, DWYU would perform fixes while analyzing the problems.
However, given DWYU is implemented as a Bazel aspect, there are limitations to what we can do in a single step: -- The DWYU aspect is analyzing the dependencies of the targets. Changing the dependencies while analyzing them would - invalidate the dependency graph and require rebuilding the graph after each fix before continuing to - analyze more targets. There is no standard feature of Bazel aspects allowing this. -- A Bazel aspect is executed in the sandbox. To be able to modify the BUILD files in the workspace, one would have to - escape the sandbox. This is generally considered a bad practice when working with Bazel. - -We circumvent the above problems by using a two step approach. First we discover all problems and store the result in -a machine readable format. Then, we use a separate tool to process the results and apply fixes to the BUILD files in -the workspace. There are no problems regarding the sandboxing, since we utilize `bazel run` to execute the fixing tool. +- The DWYU aspect is analyzing the dependencies of the targets. + Changing the dependencies while analyzing them would invalidate the dependency graph and require rebuilding the graph after each fix before continuing to analyze more targets. + There is no standard feature of Bazel aspects allowing this. +- A Bazel aspect is executed in the sandbox. + To be able to modify the BUILD files in the workspace, one would have to escape the sandbox. + This is generally considered a bad practice when working with Bazel. + +We circumvent the above problems by using a two step approach. +First we discover all problems and store the result in a machine readable format. +Then, we use a separate tool to process the results and apply fixes to the BUILD files in the workspace. +There are no problems regarding the sandboxing, since we utilize `bazel run` to execute the fixing tool. A tool being executed like this can access any part of the host system. # Platforms @@ -48,8 +51,7 @@ The DWYU aspect is incompatible to Bazel \< 4.0.0. Bazel 4.x is not tested, but should still work. We officially support and test only Bazel 5.0.0 and later. -One major feature of DWYU is ensuring the proper usage of `implementation_deps`, which is a feature which is only -available starting from Bazel 5.0.0. +One major feature of DWYU is ensuring the proper usage of `implementation_deps`, which is a feature which is only available starting from Bazel 5.0.0. Furthermore, many Bazel rule sets already rely on Bazel 5.x as well. ## Why is Python \< 3.8 not supported @@ -67,10 +69,9 @@ Essentially, this makes parsing of source code and aggregating the include state One downside is, that this only works for source files, but not for header only code. This could be mitigated by generating source files for the headers and then running the compiler on them. -A major drawback of this approach is however, that the `.d` files list all transitively included headers which are -required for compiling a source file. +A major drawback of this approach is however, that the `.d` files list all transitively included headers which are required for compiling a source file. -For example, given the 3 target `A`, `B` and `C` with the files: +For example, given the 3 targets _A_, _B_ and _C_ with the files: `a.h` @@ -95,7 +96,7 @@ void doC() { doB(); }; ``` The `.d` file for `c.cpp` will list the headers `a.h` and `b.h`. -This makes sense, after all the compiler requires all used headers to compile `c.cpp`. +This makes sense, as the compiler requires all used headers to compile `c.cpp`. However, it makes the `.d` file impractical for DWYU. We need to know if header `a.h` was included directly in `c.cpp` or is used transitively by `b.h`. Without this distinction we cannot compare the include statements to the list of direct dependencies. diff --git a/test/apply_fixes/README.md b/test/apply_fixes/README.md index 359660c1..114faee2 100644 --- a/test/apply_fixes/README.md +++ b/test/apply_fixes/README.md @@ -4,6 +4,6 @@ Each `test_*.py` file represents a test case. The `execute_test.py` is the entry For each test case the workspace template is copied into a temporary directory in which then DWYU is executed to detect a problem. Afterwards, the `//:apply_fixes` tool is executed and the adapted `BUILD` files are analyzed to check if the -expected change happened.
+expected change happened.
This approach based on a temporary workspace is chosen to make sure cleaning up the test side effects is not messing with the source code. diff --git a/test/aspect/REAME.md b/test/aspect/REAME.md index 9babc9cc..9eb954fb 100644 --- a/test/aspect/REAME.md +++ b/test/aspect/REAME.md @@ -5,8 +5,8 @@ The tests are executed with various supported Bazel versions. The test cases and their expected behavior are defined in [execute_tests.py](execute_tests.py). -Execute all tests with various Bazel versions: \ +Execute all tests with various Bazel versions:
`./execute_tests.py` -You can execute specific test cases or use specific Bazel versions. For details see the help: \ +You can execute specific test cases or use specific Bazel versions. For details see the help:
`./execute_tests.py --help` diff --git a/test/aspect/tree_artifact/README.md b/test/aspect/tree_artifact/README.md index c1e8b3a2..b525a1a2 100644 --- a/test/aspect/tree_artifact/README.md +++ b/test/aspect/tree_artifact/README.md @@ -2,5 +2,4 @@ A `TreeArtifact` is a special output of rules where one does not know beforehand Code generators are a common use case relying on this construct. In other words, a `TreeArtifact` is a predeclared directory with unknown content. The content will not be available until the execution phase. -Essentially, this makes it impossible to analyze a `TreeArtifact` in StarLark and requires doing so inside an action -which is performed in the execution phase. +Essentially, this makes it impossible to analyze a `TreeArtifact` in StarLark and requires doing so inside an action which is performed in the execution phase.