diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 607e5d46..3107474e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,5 @@ +define: &generated_doc_files "^docs/(cc_info_mapping|dwyu_aspect).md" + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -11,15 +13,19 @@ repos: - id: check-json # Code style - id: end-of-file-fixer + exclude: *generated_doc_files - id: trailing-whitespace # Python code quality - id: debug-statements + - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 hooks: - id: mdformat + exclude: *generated_doc_files additional_dependencies: - mdformat-gfm + - repo: https://github.com/keith/pre-commit-buildifier rev: 7.1.2 hooks: @@ -30,6 +36,7 @@ repos: "--diff_command='diff'", "--warnings=-module-docstring,-function-docstring,-function-docstring-header,-print" ] + - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.5.5 diff --git a/MODULE.bazel b/MODULE.bazel index ff15b3ec..fb3f3291 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,6 +16,8 @@ use_repo(non_module_dependencies, "dwyu_pcpp") # Keep in sync with third_party/dependencies.bzl bazel_dep(name = "bazel_skylib", version = "1.7.1", dev_dependency = True) +bazel_dep(name = "aspect_bazel_lib", version = "2.7.9", dev_dependency = True) +bazel_dep(name = "stardoc", version = "0.7.0", dev_dependency = True) python = use_extension( "@rules_python//python/extensions:python.bzl", diff --git a/README.md b/README.md index b46af487..df84dc90 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,7 @@ - [Get a specific commit](#get-a-specific-commit) - [Use DWYU](#use-dwyu) - [Configuring DWYU](#configuring-dwyu) - - [Custom header ignore list](#custom-header-ignore-list) - [Skipping Targets](#skipping-targets) - - [Recursion](#recursion) - [Implementation_deps](#Implementation_deps) - [Target mapping](#target-mapping) - [Verbosity](#verbosity) @@ -123,28 +121,6 @@ This is demonstrated in the [rule_using_dwyu example](/examples/rule_using_dwyu) # Configuring DWYU -## Custom header ignore list - -By default, DWYU ignores all header from the standard library when comparing include statements to the dependencies. -This list of headers can be seen in [std_header.py](src/analyze_includes/std_header.py). - -You can exclude a custom set of header files by providing a config file in json format to the aspect: - -```starlark -your_aspect = dwyu_aspect_factory(ignored_includes = "//.json") -``` - -The config file can contain these fields which should be lists of strings. -All fields are optional: - -| Field | Description | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ignore_include_paths` | List of include paths which are ignored by the analysis. Setting this **disables ignoring the standard library include paths**. | -| `extra_ignore_include_paths` | List of include paths which are ignored by the analysis. If `ignore_include_paths` is specified as well, both list are combined. If `ignore_include_paths` is not set, the default list of standard library headers is extended. | -| `ignore_include_patterns` | List of patterns for include paths which are ignored by the analysis. Patterns have to be compatible to Python [regex syntax](https://docs.python.org/3/library/re.html#regular-expression-syntax). The [match](https://docs.python.org/3/library/re.html#re.match) function is used to process the patterns. | - -This is demonstrated in the [ignoring_includes example](/examples/ignoring_includes). - ## 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. @@ -165,19 +141,6 @@ your_aspect = dwyu_aspect_factory(skip_external_targets = True) Both options are demonstrated in the [skipping_targets example](/examples/skipping_targets). -## 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: - -```starlark -your_aspect = dwyu_aspect_factory(recursive = True) -``` - -This is demonstrated in the [recursion example](/examples/recursion). - ## Implementation_deps 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`. diff --git a/docs/BUILD b/docs/BUILD new file mode 100644 index 00000000..1d70aa41 --- /dev/null +++ b/docs/BUILD @@ -0,0 +1,13 @@ +load("@aspect_bazel_lib//lib:docs.bzl", "stardoc_with_diff_test", "update_docs") + +stardoc_with_diff_test( + name = "cc_info_mapping", + bzl_library_target = "//src/cc_info_mapping:cc_info_mapping", +) + +stardoc_with_diff_test( + name = "dwyu_aspect", + bzl_library_target = "//src/aspect:factory", +) + +update_docs() diff --git a/docs/cc_info_mapping.md b/docs/cc_info_mapping.md new file mode 100644 index 00000000..312216a6 --- /dev/null +++ b/docs/cc_info_mapping.md @@ -0,0 +1,52 @@ + + +# Motivation + +Sometimes users don't want to follow the DWYU rules for all targets or have to work with external dependencies not following the DWYU principles. +While one can completely exclude targets from the DWYU analysis (e.g. via tags), one might not want to disable DWYU completely, but define custom rules for specific dependencies. +One can do so by defining exceptions where includes can be provided by selected transitive dependencies instead of direct dependencies. +In other words, one can virtually change which header files are treated as being available from direct dependencies. + +One example use case for this are unit tests based on gtest. +Following strictly the DWYU principles each test using a gtest header should depend both on the gtest library and the gtest main: +```starlark +cc_test( + name = "my_test", + srcs = ["my_test.cc"], + deps = [ + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) +``` +This can be considered superfluous noise without a significant benefit. +The mapping feature described here allows defining that `@com_google_googletest//:gtest_main` offers the header files from `@com_google_googletest//:gtest`. +Then a test can specify only the dependency to `@com_google_googletest//:gtest_main` without DWYU raising an error while analysing the test. + + + +## dwyu_make_cc_info_mapping + +
+load("@depend_on_what_you_use//src/cc_info_mapping:cc_info_mapping.bzl", "dwyu_make_cc_info_mapping")
+
+dwyu_make_cc_info_mapping(name, mapping)
+
+ +Map include paths available from one or several targets to another target. + +Create a mapping allowing treating targets as if they themselves would offer header files, which in fact are coming from their dependencies. +This enables the DWYU analysis to skip over some usage of headers provided by transitive dependencies without raising an error. + +Using this rule and the various mapping techniques is demonstrated in the [target_mapping example](/examples/target_mapping). + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | Unique name for this target. Will be the prefix for all private intermediate targets. | none | +| mapping | Dictionary containing various targets and how they should be mapped. Possible mappings are:
- An explicit list of targets which are mapped to the main target. Be careful only to choose targets which are dependencies of the main target!
- The `MAP_DIRECT_DEPS` token which tells the rule to map all direct dependencies to the main target.
- The `MAP_TRANSITIVE_DEPS` token which tells the rule to map recursively all transitive dependencies to the main target. | none | + + diff --git a/docs/dwyu_aspect.md b/docs/dwyu_aspect.md new file mode 100644 index 00000000..bccf987c --- /dev/null +++ b/docs/dwyu_aspect.md @@ -0,0 +1,35 @@ + + + + + + +## dwyu_aspect_factory + +
+load("@depend_on_what_you_use//src/aspect:factory.bzl", "dwyu_aspect_factory")
+
+dwyu_aspect_factory(ignored_includes, recursive, skip_external_targets, skipped_tags,
+                    target_mapping, use_implementation_deps, verbose)
+
+ +Create a "Depend on What You Use" (DWYU) aspect. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| ignored_includes | By default, DWYU ignores all headers from the standard library when comparing include statements to the dependencies. This list of headers can be seen in [std_header.py](/src/analyze_includes/std_header.py).
You can extend this list of ignored headers or replace it with a custom one by providing a json file with the information to this attribute. This feature is demonstrated in the [ignoring_includes example](/examples/ignoring_includes).
Specification of possible files in the json file:
- `ignore_include_paths` : List of include paths which are ignored by the analysis. Setting this **disables ignoring the standard library include paths**.
- `extra_ignore_include_paths` : List of concrete include paths which are ignored by the analysis. Those are always ignored, no matter what other fields you provide.
- `ignore_include_patterns` : List of patterns for include paths which are ignored by the analysis. Patterns have to be compatible to Python [regex syntax](https://docs.python.org/3/library/re.html#regular-expression-syntax). The [match](https://docs.python.org/3/library/re.html#re.match) function is used to process the patterns.
| `None` | +| recursive | By default, the DWYU aspect analyzes only the target it is being applied to. You can change this to recursively analyzing dependencies following the `deps` and `implementation_deps` attributes by setting this to True.
This feature is demonstrated in the [recursion example](/examples/recursion). | `False` | +| skip_external_targets | If a target is from an external workspace DWYU skips analyzing it. | `False` | +| skipped_tags | Do not execute the aspect on targets with at least one of those tags. By default skips the analysis for targets tagged with 'no-dwyu'. | `None` | +| target_mapping | A target providing a map of target labels to alternative CcInfo provider objects for those targets. Typically created with the dwyu_make_cc_info_mapping rule. | `None` | +| use_implementation_deps | If true, ensure cc_library dependencies which are used only in private files are listed in implementation_deps. Only available if flag '--experimental_cc_implementation_deps' is provided. | `False` | +| verbose | If true, print debugging information about what DWYU does internally. | `False` | + +**RETURNS** + +Configured DWYU aspect + + diff --git a/src/aspect/BUILD b/src/aspect/BUILD index b82fd10f..5d397a04 100644 --- a/src/aspect/BUILD +++ b/src/aspect/BUILD @@ -1,3 +1,4 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load("@rules_python//python:defs.bzl", "py_binary") py_binary( @@ -5,3 +6,21 @@ py_binary( srcs = ["process_target.py"], visibility = ["//visibility:public"], ) + +bzl_library( + name = "factory", + srcs = [ + "dwyu.bzl", + "factory.bzl", + "@bazel_tools//tools/build_defs/cc:action_names.bzl", + ], + visibility = ["//visibility:public"], + deps = [ + "//src/cc_info_mapping", + ], +) + +exports_files( + ["factory.bzl"], + visibility = ["//docs:__pkg__"], +) diff --git a/src/aspect/dwyu.bzl b/src/aspect/dwyu.bzl index ed205ac3..e5073d28 100644 --- a/src/aspect/dwyu.bzl +++ b/src/aspect/dwyu.bzl @@ -1,6 +1,6 @@ load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "CPP_COMPILE_ACTION_NAME") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") -load("@depend_on_what_you_use//src/cc_info_mapping:cc_info_mapping.bzl", "DwyuCcInfoRemappingsInfo") +load("@depend_on_what_you_use//src/cc_info_mapping:providers.bzl", "DwyuCcInfoRemappingsInfo") load("@rules_cc//cc:defs.bzl", "CcInfo", "cc_common") def _is_external(ctx): diff --git a/src/aspect/factory.bzl b/src/aspect/factory.bzl index 16973bc8..7c3d2caf 100644 --- a/src/aspect/factory.bzl +++ b/src/aspect/factory.bzl @@ -1,4 +1,4 @@ -load("@depend_on_what_you_use//src/cc_info_mapping:cc_info_mapping.bzl", "DwyuCcInfoRemappingsInfo") +load("@depend_on_what_you_use//src/cc_info_mapping:providers.bzl", "DwyuCcInfoRemappingsInfo") load(":dwyu.bzl", "dwyu_aspect_impl") def dwyu_aspect_factory( @@ -13,10 +13,21 @@ def dwyu_aspect_factory( Create a "Depend on What You Use" (DWYU) aspect. Args: - ignored_includes: Configuration file specifying which include statements should be skipped during analysis. When - nothing is specified, the standard library headers are ignored by default. - recursive: If true, execute the aspect on all transitive dependencies. - If false, analyze only the target the aspect is being executed on. + ignored_includes: By default, DWYU ignores all headers from the standard library when comparing include statements to the dependencies. + This list of headers can be seen in [std_header.py](/src/analyze_includes/std_header.py).
+ You can extend this list of ignored headers or replace it with a custom one by providing a json file with the information to this attribute. + This feature is demonstrated in the [ignoring_includes example](/examples/ignoring_includes).
+ Specification of possible files in the json file:
+ - `ignore_include_paths` : List of include paths which are ignored by the analysis. + Setting this **disables ignoring the standard library include paths**.
+ - `extra_ignore_include_paths` : List of concrete include paths which are ignored by the analysis. + Those are always ignored, no matter what other fields you provide.
+ - `ignore_include_patterns` : List of patterns for include paths which are ignored by the analysis. + Patterns have to be compatible to Python [regex syntax](https://docs.python.org/3/library/re.html#regular-expression-syntax). + The [match](https://docs.python.org/3/library/re.html#re.match) function is used to process the patterns.
+ recursive: By default, the DWYU aspect analyzes only the target it is being applied to. + You can change this to recursively analyzing dependencies following the `deps` and `implementation_deps` attributes by setting this to True.
+ This feature is demonstrated in the [recursion example](/examples/recursion). skip_external_targets: If a target is from an external workspace DWYU skips analyzing it. skipped_tags: Do not execute the aspect on targets with at least one of those tags. By default skips the analysis for targets tagged with 'no-dwyu'. @@ -26,6 +37,7 @@ def dwyu_aspect_factory( listed in implementation_deps. Only available if flag '--experimental_cc_implementation_deps' is provided. verbose: If true, print debugging information about what DWYU does internally. + Returns: Configured DWYU aspect """ diff --git a/src/cc_info_mapping/BUILD b/src/cc_info_mapping/BUILD index e69de29b..d662b41f 100644 --- a/src/cc_info_mapping/BUILD +++ b/src/cc_info_mapping/BUILD @@ -0,0 +1,21 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "cc_info_mapping", + srcs = [ + "cc_info_mapping.bzl", + "providers.bzl", + "@bazel_tools//tools/cpp:toolchain_utils.bzl", + "@rules_cc//cc:bzl_srcs", + ], + visibility = ["//visibility:public"], + deps = [ + "//src/cc_info_mapping/private:bzl_srcs", + "//src/utils", + ], +) + +exports_files( + ["cc_info_mapping.bzl"], + visibility = ["//docs:__pkg__"], +) diff --git a/src/cc_info_mapping/cc_info_mapping.bzl b/src/cc_info_mapping/cc_info_mapping.bzl index e5f930d9..b520e600 100644 --- a/src/cc_info_mapping/cc_info_mapping.bzl +++ b/src/cc_info_mapping/cc_info_mapping.bzl @@ -1,19 +1,38 @@ +""" +# Motivation + +Sometimes users don't want to follow the DWYU rules for all targets or have to work with external dependencies not following the DWYU principles. +While one can completely exclude targets from the DWYU analysis (e.g. via tags), one might not want to disable DWYU completely, but define custom rules for specific dependencies. +One can do so by defining exceptions where includes can be provided by selected transitive dependencies instead of direct dependencies. +In other words, one can virtually change which header files are treated as being available from direct dependencies. + +One example use case for this are unit tests based on gtest. +Following strictly the DWYU principles each test using a gtest header should depend both on the gtest library and the gtest main: +```starlark +cc_test( + name = "my_test", + srcs = ["my_test.cc"], + deps = [ + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) +``` +This can be considered superfluous noise without a significant benefit. +The mapping feature described here allows defining that `@com_google_googletest//:gtest_main` offers the header files from `@com_google_googletest//:gtest`. +Then a test can specify only the dependency to `@com_google_googletest//:gtest_main` without DWYU raising an error while analysing the test. +""" + load("@depend_on_what_you_use//src/cc_info_mapping/private:direct_deps.bzl", "mapping_to_direct_deps") load("@depend_on_what_you_use//src/cc_info_mapping/private:explicit.bzl", "explicit_mapping") load("@depend_on_what_you_use//src/cc_info_mapping/private:providers.bzl", "DwyuCcInfoRemapInfo") load("@depend_on_what_you_use//src/cc_info_mapping/private:transitive_deps.bzl", "mapping_to_transitive_deps") load("@depend_on_what_you_use//src/utils:utils.bzl", "label_to_name") +load(":providers.bzl", "DwyuCcInfoRemappingsInfo") MAP_DIRECT_DEPS = "__DWYU_MAP_DIRECT_DEPS__" MAP_TRANSITIVE_DEPS = "__DWYU_MAP_TRANSITIVE_DEPS__" -DwyuCcInfoRemappingsInfo = provider( - "Dictionary of targets labels wnd which CcInfo provider DWYU should use for analysing them", - fields = { - "mapping": "Dictionary with structure {'target label': CcInfo provider which should be used by DWYU}", - }, -) - def _make_remapping_info_impl(ctx): return DwyuCcInfoRemappingsInfo(mapping = { remap[DwyuCcInfoRemapInfo].target: remap[DwyuCcInfoRemapInfo].cc_info @@ -30,18 +49,20 @@ _make_remapping_info = rule( def dwyu_make_cc_info_mapping(name, mapping): """ - Create a mapping which allows treating targets as if they themselves would offer header files, which in fact are - coming from their dependencies. This enables the DWYU analysis to skip over some usage of headers provided by - transitive dependencies without raising an error. + Map include paths available from one or several targets to another target. + + Create a mapping allowing treating targets as if they themselves would offer header files, which in fact are coming from their dependencies. + This enables the DWYU analysis to skip over some usage of headers provided by transitive dependencies without raising an error. + + Using this rule and the various mapping techniques is demonstrated in the [target_mapping example](/examples/target_mapping). Args: name: Unique name for this target. Will be the prefix for all private intermediate targets. - mapping: Dictionary containing various targets and how they should be mapped. Possible mappings are: - - An explicit list of targets which are mapped to the main target. Be careful only to choose targets - which are dependencies of the main target! - - The MAP_DIRECT_DEPS token which tells the rule to map all direct dependencies to the main target. - - The MAP_TRANSITIVE_DEPS token which tells the rule to map recursively all transitive dependencies to - the main target. + mapping: Dictionary containing various targets and how they should be mapped. Possible mappings are:
+ - An explicit list of targets which are mapped to the main target. + Be careful only to choose targets which are dependencies of the main target!
+ - The `MAP_DIRECT_DEPS` token which tells the rule to map all direct dependencies to the main target.
+ - The `MAP_TRANSITIVE_DEPS` token which tells the rule to map recursively all transitive dependencies to the main target. """ mappings = [] for target, map_to in mapping.items(): diff --git a/src/cc_info_mapping/private/BUILD b/src/cc_info_mapping/private/BUILD index e69de29b..0fbdb49e 100644 --- a/src/cc_info_mapping/private/BUILD +++ b/src/cc_info_mapping/private/BUILD @@ -0,0 +1,12 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "bzl_srcs", + srcs = [ + "direct_deps.bzl", + "explicit.bzl", + "providers.bzl", + "transitive_deps.bzl", + ], + visibility = ["//src/cc_info_mapping:__pkg__"], +) diff --git a/src/cc_info_mapping/providers.bzl b/src/cc_info_mapping/providers.bzl new file mode 100644 index 00000000..65acb288 --- /dev/null +++ b/src/cc_info_mapping/providers.bzl @@ -0,0 +1,6 @@ +DwyuCcInfoRemappingsInfo = provider( + "Mapping of targets to CcInfo providers which DWYU should use for analyis instead of the targets original CcInfo.", + fields = { + "mapping": "Dictionary with structure {'target label': CcInfo}", + }, +) diff --git a/src/utils/BUILD b/src/utils/BUILD index e69de29b..cdead589 100644 --- a/src/utils/BUILD +++ b/src/utils/BUILD @@ -0,0 +1,9 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "utils", + srcs = [ + "utils.bzl", + ], + visibility = ["//src:__subpackages__"], +)