Skip to content

[Modules] Don't fail when an unused textual header is missing. #138227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025

Conversation

vsapsai
Copy link
Collaborator

@vsapsai vsapsai commented May 2, 2025

According to the documentation

A header declaration that does not contain exclude nor textual specifies a header that contributes to the enclosing module.

Which means that exclude and textual header don't contribute to the enclosing module and their presence isn't required to build such a module. The keywords tell clang how a header should be treated in a context of the module but they don't add headers to the module.

When a textual header is used, clang still emits "file not found" error pointing to the location where the missing file is included.

According to the documentation
> A header declaration that does not contain `exclude` nor `textual` specifies a header that contributes to the enclosing module.

Which means that `exclude` and `textual` header don't contribute to the
enclosing module and their presence isn't required to build such a module.
The keywords tell clang how a header should be treated in a context of
the module but they don't add headers to the module.

When a textual header *is* used, clang still emits "file not found" error
pointing to the location where the missing file is included.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules labels May 2, 2025
@llvmbot
Copy link
Member

llvmbot commented May 2, 2025

@llvm/pr-subscribers-clang

Author: Volodymyr Sapsai (vsapsai)

Changes

According to the documentation
> A header declaration that does not contain exclude nor textual specifies a header that contributes to the enclosing module.

Which means that exclude and textual header don't contribute to the enclosing module and their presence isn't required to build such a module. The keywords tell clang how a header should be treated in a context of the module but they don't add headers to the module.

When a textual header is used, clang still emits "file not found" error pointing to the location where the missing file is included.


Full diff: https://github.com/llvm/llvm-project/pull/138227.diff

3 Files Affected:

  • (modified) clang/lib/Lex/ModuleMap.cpp (+4-2)
  • (modified) clang/test/Modules/Inputs/submodules/module.modulemap (+4)
  • (modified) clang/test/Modules/missing-header.m (+3)
diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp
index c2f13fa48d464..1ba02b919a22a 100644
--- a/clang/lib/Lex/ModuleMap.cpp
+++ b/clang/lib/Lex/ModuleMap.cpp
@@ -310,8 +310,10 @@ void ModuleMap::resolveHeader(Module *Mod,
   } else if (Header.HasBuiltinHeader && !Header.Size && !Header.ModTime) {
     // There's a builtin header but no corresponding on-disk header. Assume
     // this was supposed to modularize the builtin header alone.
-  } else if (Header.Kind == Module::HK_Excluded) {
-    // Ignore missing excluded header files. They're optional anyway.
+  } else if ((Header.Kind == Module::HK_Excluded) ||
+             (Header.Kind == Module::HK_Textual)) {
+    // Ignore excluded and textual header files as a module can be built with
+    // such headers missing.
   } else {
     // If we find a module that has a missing header, we mark this module as
     // unavailable and store the header directive for displaying diagnostics.
diff --git a/clang/test/Modules/Inputs/submodules/module.modulemap b/clang/test/Modules/Inputs/submodules/module.modulemap
index 1c1b76a08969e..9e8143b8101de 100644
--- a/clang/test/Modules/Inputs/submodules/module.modulemap
+++ b/clang/test/Modules/Inputs/submodules/module.modulemap
@@ -30,3 +30,7 @@ module missing_umbrella_with_inferred_submodules {
   module * { export * }
   export *
 }
+
+module missing_textual_header {
+  textual header "missing_textual.h"
+}
diff --git a/clang/test/Modules/missing-header.m b/clang/test/Modules/missing-header.m
index c162e1b5f08b3..84d82e5ceda32 100644
--- a/clang/test/Modules/missing-header.m
+++ b/clang/test/Modules/missing-header.m
@@ -8,6 +8,9 @@
 @import missing_unavailable_headers.not_missing; // OK
 // CHECK-NOT: missing_unavailable_headers
 
+@import missing_textual_header; // OK
+// CHECK-NOT: missing_textual_header
+
 @import missing_headers;
 // CHECK: module.modulemap:15:27: error: header 'missing.h' not found
 // CHECK: could not build module 'missing_headers'

@llvmbot
Copy link
Member

llvmbot commented May 2, 2025

@llvm/pr-subscribers-clang-modules

Author: Volodymyr Sapsai (vsapsai)

Changes

According to the documentation
> A header declaration that does not contain exclude nor textual specifies a header that contributes to the enclosing module.

Which means that exclude and textual header don't contribute to the enclosing module and their presence isn't required to build such a module. The keywords tell clang how a header should be treated in a context of the module but they don't add headers to the module.

When a textual header is used, clang still emits "file not found" error pointing to the location where the missing file is included.


Full diff: https://github.com/llvm/llvm-project/pull/138227.diff

3 Files Affected:

  • (modified) clang/lib/Lex/ModuleMap.cpp (+4-2)
  • (modified) clang/test/Modules/Inputs/submodules/module.modulemap (+4)
  • (modified) clang/test/Modules/missing-header.m (+3)
diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp
index c2f13fa48d464..1ba02b919a22a 100644
--- a/clang/lib/Lex/ModuleMap.cpp
+++ b/clang/lib/Lex/ModuleMap.cpp
@@ -310,8 +310,10 @@ void ModuleMap::resolveHeader(Module *Mod,
   } else if (Header.HasBuiltinHeader && !Header.Size && !Header.ModTime) {
     // There's a builtin header but no corresponding on-disk header. Assume
     // this was supposed to modularize the builtin header alone.
-  } else if (Header.Kind == Module::HK_Excluded) {
-    // Ignore missing excluded header files. They're optional anyway.
+  } else if ((Header.Kind == Module::HK_Excluded) ||
+             (Header.Kind == Module::HK_Textual)) {
+    // Ignore excluded and textual header files as a module can be built with
+    // such headers missing.
   } else {
     // If we find a module that has a missing header, we mark this module as
     // unavailable and store the header directive for displaying diagnostics.
diff --git a/clang/test/Modules/Inputs/submodules/module.modulemap b/clang/test/Modules/Inputs/submodules/module.modulemap
index 1c1b76a08969e..9e8143b8101de 100644
--- a/clang/test/Modules/Inputs/submodules/module.modulemap
+++ b/clang/test/Modules/Inputs/submodules/module.modulemap
@@ -30,3 +30,7 @@ module missing_umbrella_with_inferred_submodules {
   module * { export * }
   export *
 }
+
+module missing_textual_header {
+  textual header "missing_textual.h"
+}
diff --git a/clang/test/Modules/missing-header.m b/clang/test/Modules/missing-header.m
index c162e1b5f08b3..84d82e5ceda32 100644
--- a/clang/test/Modules/missing-header.m
+++ b/clang/test/Modules/missing-header.m
@@ -8,6 +8,9 @@
 @import missing_unavailable_headers.not_missing; // OK
 // CHECK-NOT: missing_unavailable_headers
 
+@import missing_textual_header; // OK
+// CHECK-NOT: missing_textual_header
+
 @import missing_headers;
 // CHECK: module.modulemap:15:27: error: header 'missing.h' not found
 // CHECK: could not build module 'missing_headers'

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 2, 2025

The change for exclude headers was done in 040e126.

Copy link
Contributor

@Bigcheese Bigcheese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just looking at the missing_textual_header module alone this is a bit odd, but that's just the semantics of textual headers. Really textual headers just exist to control the non-modular include warning, so this is fine.

@vsapsai vsapsai merged commit 64bb60a into llvm:main May 8, 2025
15 checks passed
@vsapsai vsapsai deleted the accept-missing-textual-header branch May 8, 2025 16:07
@vsapsai
Copy link
Collaborator Author

vsapsai commented May 8, 2025

Thanks for the review!

@bgra8
Copy link
Contributor

bgra8 commented May 16, 2025

We have bisected build breakages internally at google at this change. Ironically the compiler complains about a missing header while before it didn't.

We do not have a reproducer yet.

@ilya-biryukov
Copy link
Contributor

Here's a reproducer for our breakage:

// RUN: rm -rf %t
// RUN: split-file %s %t
//
// First, build a module with a header.
//
// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/modules1.map -fmodule-name=a -emit-module -xc++ -fmodules-embed-all-files -o %t/a.pcm %t/modules1.map
// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/modules1.map -fmodule-map-file=%t/modules2.map -fmodule-name=b -emit-module \
// RUN:   -fmodule-file=%t/a.pcm -xc++ -fmodules-embed-all-files -o %t/b.pcm %t/modules2.map
// 
// Remove the header and check we can still build the code that finds it in a PCM.
//
// RUN: rm %t/foo.h
// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/modules2.map -fmodule-file=%t/b.pcm -fsyntax-only %t/use.cpp

//--- modules1.map
module a {
  module foo {
    header "foo.h"
    export *
  }
  export *
}

//--- modules2.map
module all_textual {
  module foo {
    textual header "foo.h"
    export *
  }
  module wrap_foo {
    textual header "wrap_foo.h"
    export *
  }
  export *
}

module b {
  module wrap_foo {
    private header "wrap_foo.h"
    export *
  }
  export *
}


//--- foo.h
#ifndef FOO_H
#define FOO_H
void foo();
#endif

//--- wrap_foo.h
#include "foo.h"

//--- use.cpp
#include "wrap_foo.h"

void test() {
  foo();
}

It previously built fine because the all_textual.foo and its parent module used to be marked as unavailable because foo.h could not be found here:

Mod->markUnavailable(/*Unimportable=*/false);

and therefore it made us pick the module b.wrap_foo later when resolving wrap_foo.h instead of all_textual.wrap_foo based on the code here:

After the change, we no longer mark all_textual as unavailable and pick all_textual.wrap_foo, which causes us to reprocess the header.

I know what we do is cheesy and we might need to reconsider how to approach it, but this is a breaking change and it would be great to find a resolution for it before committing to the new behavior. Could we revert this while we discuss the potential ways out of it?

@alexfh
Copy link
Contributor

alexfh commented May 20, 2025

@vsapsai Please revert if there's no obvious fix.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 21, 2025

I know what we do is cheesy and we might need to reconsider how to approach it, but this is a breaking change and it would be great to find a resolution for it before committing to the new behavior. Could we revert this while we discuss the potential ways out of it?

What is your timeframe for stopping putting the same header ("wrap_foo.h") into multiple modules? If you do that it was never guaranteed which module would be used for a header. I'm willing to unblock you but I'd like to know if you are committed to fixing the issue in the module maps.

@ilya-biryukov
Copy link
Contributor

What is your timeframe for stopping putting the same header ("wrap_foo.h") into multiple modules? If you do that it was never guaranteed which module would be used for a header. I'm willing to unblock you but I'd like to know if you are committed to fixing the issue in the module maps.

I don't think we can ever get rid of that pattern and we will keep relying on Clang supporting this. I am sure there are ways to meet both your needs and our needs.

To reiterate, Clang supports having the same header in multiple module maps and we don't view our current setup as having at a high level. I just want to explicit that our stance is that there are no issues that need "fixing in the module maps".
Relying on Clang picking a header from a module when it's available is also something we'd like to keep.

How we achieve this today is shaky (relying on availability doesn't look right), but we absolutely need:

  • to allow multiple modules own the same header,
  • whenever resolving module for the header, pick one that has the header as modular over textual.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 21, 2025

It's weird that clang picked b.wrap_foo for "wrap_foo.h" as it is specified as private header. To me it looks like circumventing "use of private header from outside its module [-Wprivate-header]".

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 21, 2025

Interesting, changing private header "wrap_foo.h" to header "wrap_foo.h" in b.wrap_foo stops reproducing the error. I'm not sure it guarantees

whenever resolving module for the header, pick one that has the header as modular over textual.

But seems we are closer to this behavior than I expected.

@alexfh
Copy link
Contributor

alexfh commented May 22, 2025

Interesting, changing private header "wrap_foo.h" to header "wrap_foo.h" in b.wrap_foo stops reproducing the error. I'm not sure it guarantees

whenever resolving module for the header, pick one that has the header as modular over textual.

But seems we are closer to this behavior than I expected.

@vsapsai I guess, it's a good sign? :) Do you see how our use case can be supported by a trivial and low-risk forward fix? If not, I'd insist on a revert before we can figure out the way forward. We can run this sort of a change through our testing before it relands, and ensure it doesn't break our code. We did this on multiple occasions before (especially for changes that touch Clang header modules).

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 22, 2025

@vsapsai I guess, it's a good sign? :) Do you see how our use case can be supported by a trivial and low-risk forward fix? If not, I'd insist on a revert before we can figure out the way forward. We can run this sort of a change through our testing before it relands, and ensure it doesn't break our code. We did this on multiple occasions before (especially for changes that touch Clang header modules).

I still don't understand what is your use case. My [very ungenerous] interpretation is "to access a file you were told not to access without triggering any tripwires". And I'm not sure such a use case is worth supporting.

But I understand that is only my own interpretation which can be incorrect. And I want to believe you have a better use case that doesn't rely on accessing private headers.

@ilya-biryukov
Copy link
Contributor

ilya-biryukov commented May 22, 2025

And I'm not sure such a use case is worth supporting.
But I understand that is only my own interpretation which can be incorrect. And I want to believe you have a better use case that doesn't rely on accessing private headers

The code I shared compiles with no error before this change and has errors now. It is okay to discuss if our use-case is "worth supporting", but we heavily rely on this behavior and this has already broken us and this puts us in a corner.

Could we revert this and have a discussion on how to satisfy both our use cases on equal grounds?
Is there any reason why this commit must be present at head and can't wait until we figure out a mutual way out of this?

vsapsai added a commit that referenced this pull request May 23, 2025
#138227)"

This reverts commit 64bb60a.

Revert to give more time affected parties to adjust to the change.
@vsapsai
Copy link
Collaborator Author

vsapsai commented May 23, 2025

Ok, I'm going to revert the change to help you out. But I'm going to re-land it in a week or when you are ready, whichever comes first.

There was no indication there is anything wrong with the change or if the issue is wide-spread. And if a single company relies on an existing side effect, there is no obligation for everyone else to maintain such a side effect forever.

I'm willing to help figure out how to achieve the desired result in a different way. But for that need to know what is the desired result.

@ilya-biryukov
Copy link
Contributor

Ok, I'm going to revert the change to help you out. But I'm going to re-land it in a week or when you are ready, whichever comes first.

There was no indication there is anything wrong with the change or if the issue is wide-spread. And if a single company relies on an existing side effect, there is no obligation for everyone else to maintain such a side effect forever.

I'm willing to help figure out how to achieve the desired result in a different way. But for that need to know what is the desired result.

I would like a second opinion on your proposed course of action from @llvm/clang-area-team.
Please let me know if this is the right escalation path, if not, I am happy to find another one if this is wrong.

It is concerning to me that we as a company are given a week to adjust to the change and there is no discussion either about alternative approaches or the scale of the changes needed to support this. We do rely on all kinds of weird behaviors and side effects in Clang, and are happy to adjust to make the best for the community. I am pretty sure we can find a way to make this work too, but not under undue pressure.

I agree that maintaining anything for a single company is not something that the community must or should do, but we are also not asking for maintaining this behavior indefinitely. We ask to give us reasonable time to understand the problem and adjust and engage with us to help fix this.

I also do not know how to react to the xkcd, it seems rather snarky and dismissive. I hope it was not intended that way, but I still wanted to call it out that it came over like this to me.

With all that being said, thank you for reverting the commit, this really helps relieve some pressure from the discussion.

@ilya-biryukov
Copy link
Contributor

Back to the original issue.

I'm willing to help figure out how to achieve the desired result in a different way. But for that need to know what is the desired result.

We would need to example I shared above to keep working. We rely on an optimization that picks a module with a modular header and imports it rather than a textual one whenever it's available. The reason we cannot change that easily is that:

  • our module maps are generated by the build system,
  • our builds are run remotely and we have an optimization that reduces the number of inputs by not shipping the headers if they is an available module that has the header as modular,
  • our codebase is really big and we have no easy way to find and replace the places we have.

Any change on our side is either in compile time or will take long to implement.
From first principles, we need to realign the build optimization for reducing number of inputs and Clang's module semantics. I would need to confirm that, but I think that'll boil down to preferring to use a module where header is modular over another where header is textual, even if the underlying header is marked as private. Currently we happen to have this behavior by accident (module with textual header is marked unavailable).

We already prefer modules where header is modular over textual, but we the non-private vs private header takes higher priority.
I think the change we want is to always prioritize modular over textual, before we look at non-private vs private.
The open questions that I have are:

  • Would that change be breaking?
  • What are the consequences of picking a different module? (Given that multiple modules for a single header is unusual, it might be that it would not matter, but per Hyrum's law, we want cause problems)

If that does not work by default, we would probably need a (-cc1? ) flag that we can keep using on our side.

@benlangmuir
Copy link
Collaborator

I don't think we can ever get rid of that pattern and we will keep relying on Clang supporting this.
We would need to example I shared above to keep working.
I think the change we want is to always prioritize modular over textual, before we look at non-private vs private.

Can you clarify, are you saying this pattern of having a header in two different modules has to keep working indefinitely, or are you willing to migrate off of it?

I don't understand what the reason is for the header to be in two different modules in the first place. It sounds like it is somehow related to a build optimization, but what exactly is that?

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 23, 2025

It is concerning to me that we as a company are given a week to adjust to the change and there is no discussion either about alternative approaches or the scale of the changes needed to support this. We do rely on all kinds of weird behaviors and side effects in Clang, and are happy to adjust to make the best for the community. I am pretty sure we can find a way to make this work too, but not under undue pressure.
Do you realize you can ask for a different deadline? If you don't tell how much time you need, you get an external deadline imposed on you.

What makes you think that no discussion is possible? For the record, "we need this to work, you must make it work" is not a discussion.

I agree that maintaining anything for a single company is not something that the community must or should do, but we are also not asking for maintaining this behavior indefinitely.
Sorry, my bad, I've missed how much time do you need. I.e., for how long this behavior should be maintained.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 23, 2025

Back to the original issue.

Do you control the module map that specifies a header as private? Does it need to stay private?

Personally, to me it looks like a bug that a private header is getting picked up [somewhat accidentally]. I'm not planning to fix this bug, just that relying on outright buggy behavior doesn't look promising long-term.

@rnk
Copy link
Collaborator

rnk commented May 23, 2025

(This is mostly written wearing my "Googler" hat, not the clang area team lead hat)

There's been a lot of discussion of what the exact use case is. I think it's helpful to share the context that Google essentially uses Clang header modules to wrap generated proto headers, and this is critical for enabling our builds to scale. The details here about private modules, module wrappers, and textual inclusions are Blazel-generated, low-level implementation details that don't matter in the grand scheme of things. The intention is to provide a IWYU-clean, header module programming model that is efficient as possible, meaning sometimes the large generated proto header needs to be included textually, and sometimes handled as a module import.

I believe the arrangement of the module maps is changeable, but it is a low-level detail at the bottom of a performance critical system, so the lead time for changes is probably measured in months, not days, and probably requires significant effort.

Focusing back on the commit at hand, it would have forced our internal build system to provide thousands more header files as inputs to compilations across the system, and we can't afford to do that. We backed it out of the last few internal release candidates, but carrying it over requires manual effort. The purpose of the commit seems to have been to emit fewer errors, not more. I'm sure Google's usage of header modules relies on internal implementation details that it shouldn't, but we appreciate having more time to debug the issue, so thank you for the accommodation.

And, in the end, the folks debugging this (Ilya & co) are busy working on clang header module reproduction and reduction tools: https://github.com/emaxx-google/cvise/branches @emaxx-google

Effective open source collaboration is all about balancing the needs of contributors, and while this work on cvise and reblaze isn't happening in LLVM, I hope the value of these contributions outweighs the inconvenience of dealing with Google's reliance on header module implementation details.

@cyndyishida
Copy link
Member

cyndyishida commented May 23, 2025

And, in the end, the folks debugging this (Ilya & co) are busy working on clang header module reproduction and reduction tools: https://github.com/emaxx-google/cvise/branches

Interestingly enough, @vsapsai is also working on reproducers for explicitly built clang modules, and that is how he ran into the compiler bug about a not-included textual header. When discovering which files to capture for the reproduction, we technically shouldn't need to also include this header input since it's not needed to build the module, it seems like that approach would also help for reducing reproducers.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 27, 2025

Do you include proto headers directly or through the wrapper headers?

Are there any important macros in proto headers?

Do you need to abuse macros to get something out of proto headers? Kinda like #define private public but more realistic.

My initial reaction is to offer a solution by not using private headers (especially that it is so easily subverted by textual headers) and to rely on -fmodules-embed-all-files (which you already do). I believe you have constraints that make this "simple" solution not feasible. So I'm curious to know what about this solution doesn't work.

@ilya-biryukov
Copy link
Contributor

Can you clarify, are you saying this pattern of having a header in two different modules has to keep working indefinitely, or are you willing to migrate off of it?
I don't understand what the reason is for the header to be in two different modules in the first place. It sounds like it is somehow related to a build optimization, but what exactly is that?

Our whole build infrastructure relies on producing those module maps.
In Bazel (and its internal equivalent), you can have multiple targets (cc_library) that own the same header.
Bazel generates a module map for each of the cc_library like this:

  • each file in hdrs becomes a header (can be modular or textual, depending on the target, e.g. all protos produce modular targets, we can also make some targets modular),
  • each header file in srcs becomes a private header.
  • .inc files and textual_hdrs become textual headers.

Things are a little more complicated on top of that, we actually have a submodule per header in each module matching a target. And we also rely on -fmodule-strict-decluse to make sure we never have any headers that are not covered by module maps.

That way we enforce the structure from our BUILD rules: headers are only allowed to include headers from their direct dependencies and only the non-private ones. I believe the way access checking of headers is done today, Clang allows us to have a dependency on a header that you could get through a module that has it publicly available, even if it ends up picking a module where the header was actually private.

In turn, this allows us to add optimization by limiting the number of header and modules we need to ship to distributed build for each of the compile actions. I know there are many details missing from this explanation, but I hope this gives a start.

Changing how our build is done within all of Google is obviously hard. I understand that having multiple modules for a header may not be something others care about, but we need it and are willing to put the work to make it work too.

Does that explanation help to clarify our use case or do folks still have questions?

Do you include proto headers directly or through the wrapper headers?

Directly.

Are there any important macros in proto headers?

I don't think there are. I am not sure why it's important.

Do you need to abuse macros to get something out of proto headers? Kinda like #define private public but more realistic.

My initial reaction is to offer a solution by not using private headers (especially that it is so easily subverted by textual headers) and to rely on -fmodules-embed-all-files (which you already do). I believe you have constraints that make this "simple" solution not feasible. So I'm curious to know what about this solution doesn't work.

As mentioned above, we rely on private headers to check we do not accidentally include a header in srcs from a file that's not part of the same target (caveat: unless there's some other target we depend on exposing the same header as non-private).

Do you realize you can ask for a different deadline? If you don't tell how much time you need, you get an external deadline imposed on you.

I find exchanges like this non-respectful and non-considerate. Please give us some benefit of a doubt, we are trying to explain our use-case and we are happy to put in the work to make both our cases work.

What makes you think that no discussion is possible? For the record, "we need this to work, you must make it work" is not a discussion.

Could you directly quote something in my writing that you interpreted like this?
I definitely did not mean this to come across as a demand.
On the contrary, I have interpreted the xkcd link and some of the replies as hostile and snarky. I would like to move away from that style and find a productive way forward here.

@cyndyishida
Copy link
Member

Firstly, thank you for your responsiveness @ilya-biryukov, and for helping us understand how this bug fix impacts Google's build.

For all individuals involved in this discussion, I would like to remind that our code of conduct does ask us to understand each other when we disagree.

In general, though, I share the same concern as @vsapsai about leaving this patch reverted for too long. But in fear of speaking too much on behalf of others, perhaps it would be helpful for us to have a video call, coordinated by the area team, or otherwise, to understand how to move forward if we cannot come to a resolution soon.

@ilya-biryukov
Copy link
Contributor

In general, though, I share the same concern as @vsapsai about leaving this patch reverted for too long.

I have a concrete proposal to move forward, #141792. I have checked that the failure we've seen in our infrastructure goes away with both changes applied. Given that multiple modules owning the same header seem to be a rare case outside of Google, I hope this also won't cause any breakages and the change acceptable (it's only changing the logic that kicks in when we do have multiple modules for the same header).

I really appreciate the steps forward here and would be happy to hop on a video call if necessary. I would still suggest to discuss #141792 first and if that stalls, we can schedule a call. Does that work?

Unfortunately, I will be OOO until next Monday and won't be able to respond here.
If folks feel there is urgency in relanding this or are happy with merging #141792 without discussion, feel free to do so. (We can definitely live with a local revert for a week). The only major objection that I would raise would be to a path forward that breaks the test case I've added above without a migration path that we can employ.

@ilya-biryukov
Copy link
Contributor

Many thanks for engaging in this discussion and sorry for the delays in my responses too.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 28, 2025

Do you realize you can ask for a different deadline? If you don't tell how much time you need, you get an external deadline imposed on you.

I find exchanges like this non-respectful and non-considerate. Please give us some benefit of a doubt, we are trying to explain our use-case and we are happy to put in the work to make both our cases work.

I am sorry, after re-reading the words I realize it can come across as condescending, I didn't mean it that way. I'm sorry and I understand you have your own requirements and constraints. You've mentioned that you'll be out, so I'm not going to re-land the patch while the conversation is still ongoing.

What makes you think that no discussion is possible? For the record, "we need this to work, you must make it work" is not a discussion.

Could you directly quote something in my writing that you interpreted like this? I definitely did not mean this to come across as a demand. On the contrary, I have interpreted the xkcd link and some of the replies as hostile and snarky. I would like to move away from that style and find a productive way forward here.

Giving the benefit of the doubt I interpret your request as "what I can do in the future to make things better". I would say that

I don't think we can ever get rid of that pattern and we will keep relying on Clang supporting this.

and the lack of the commitment to any work on your side made it sound as a demand.

I'm sorry that my replies came across as hostile and snarky, I didn't mean it that way. Though I'll admit I'm not thrilled about your current approach, that's what I was communicating. But that's what you have right now and there is no easy way to change it fast.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 28, 2025

[skip]

As mentioned above, we rely on private headers to check we do not accidentally include a header in srcs from a file that's not part of the same target (caveat: unless there's some other target we depend on exposing the same header as non-private).

Thanks for the detailed explanation. I'm relieved that you aren't a heavy user of textual headers to make macros work (this is one of the use cases for textual headers).

After reading about Bazel system I think a big issue is the mismatch between the way Bazel rules work and the way modules work. Specifically, in Bazel you know through which dependency a header is included. So you can prohibit it for some dependencies while allow it for others. But with modules you cannot specify through which module a header is accessed. Though depending on the module map granularity and the usage of module maps you can replicate some kind of those checks.

I need to think about it more. But I had an idea if exclude header with -fmodule-strict-decluse gives you a way to enforce no-include of headers in srcs. Need to test it more to see when it doesn't work.

@AaronBallman
Copy link
Collaborator

We reviewed this thread today at the Clang Area Team meeting and it seems like y'all have perhaps found a solution everyone is happy with. However, if you'd like a meeting or if there's some other way we can support you, we're definitely happy to help!

Please let me know if this is the right escalation path, if not, I am happy to find another one if this is wrong.

The usual path is: relevant maintainer(s), then lead maintainer, then relevant area team(s), then project council. But there's really no "wrong" group to escalate to, so this was a perfectly reasonable escalation path.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 30, 2025

I think I understand the logic behind your approach.

  • Access between headers is enforced through decl-use, so you cannot access headers from a target that's not your direct dependency.
  • But some headers shouldn't be accessed outside of a target [regardless of the dependencies], only inside the target. These become private header.
  • Access to headers from .cc files is restricted through the used module maps and maybe through some Bazel sand-boxing. Doesn't particularly matter in this case.

And the same header in multiple modules just happened to be the case, it wasn't a deliberate design choice.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 30, 2025

What about the approach to provide all_textual.pcm to use.cpp and for clang to find foo.h in all_textual.pcm? It doesn't work right now but I'm not sure that it shouldn't (there can be good reasons why it shouldn't work, tbh).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants