Description
One highly useful feature of build systems is the ability to ensure strict dependencies. For more details, see this blog post.
What are strict deps
TLDR: Strict deps enforce that code can only depend on its direct dependencies, and not transitive dependencies.
Consider the following code:
// foo.cppm
import bar;
import baz;
// bar.cppm
import baz
# BUILD
cc_module(
name = "foo",
interface = "foo.cppm",
deps = [":bar"]
)
cc_module(
name = "bar",
interface = "bar.cppm",
deps = [":baz"]
)
This code should not compile. foo
imports baz
, but does not declare a dependency on baz
. However, build systems have no easy mechanism to enforce this, as they have to output compile steps along the lines of:
clang++ --precompile baz.cppm -o baz.pcm
clang++ --precompile bar.cppm -fmodule-file=baz=baz.pcm -o bar.pcm
clang++ --precompile foo.cppm -fmodule-file=baz=baz.pcm -fmodule-file=bar=bar.pcm -o foo.pcm
Note that we cannot simply remove the -fmodule-file=baz=...
, otherwise we would get an error if the module bar
ever tried to return a value of a type declared in baz
.
Proposal
I propose that clang adds a new option -findirect-module-file
. This has the same semantics as -fmodule-file
, with the only difference being that if you ever attempt to import the module from the main TU, you get an error.
It might also be a good idea to add -Wstrict-module-deps
or similar, so that this option can be first applied as warnings to an existing codebase, instead of errors.
This would allow the build system to generate:
clang++ --precompile baz.cppm -o baz.pcm
clang++ --precompile bar.cppm -fmodule-file=baz=baz.pcm -o bar.pcm
clang++ --precompile foo.cppm -findirect-module-file=baz=baz.pcm -fmodule-file=bar=bar.pcm -o foo.pcm
Thus allowing foo to fail to compile in the first place.