Extending the modulemap format with conditional declarations

We are planning on extending the modulemap format to add support for conditionally excluding arbitrary declarations within a modulemap. This is driven by some of the differences between Apple platforms in the Apple SDK which share a modulemap. A specific example is AppKit vs. UIKit on different platforms where we need different export_as declarations. Our current workaround here requires using a VFS to override the modulemap.

modulemaps currently have a requires declaration which is a member of a module and controls if that module exists or not. We propose extending this declaration to support arbitrary declarations, for example:

module A {
  header "A.h"
  requires ios {
    header "A-ios.h"
  }
}

The semantics are that if a requires block’s feature-list is satisfied, each declaration within its body are parsed as if they were in the same place as the requires. requires blocks can appear anywhere, including the top level and nested within another requires block. Conflicting declarations within a requires block are still errors.

These semantics are different from the existing requires in that it applies to it’s children, rather than parent. We don’t feel this will be particularly confusing as both uses make sense in context.

We also considered the following alternatives:

Using the C preprocessor:
While easy to implement, this makes it much more difficult for other tools to parse modulemap files as they require external context to parse at all.

Swift style conditional compilation (#if os(macOS)):
While much easier to parse for other tools, it looks identical to the C preprocessor and could lead to confusion, particularly when used with files that Clang directly parses.

Rust style conditional compilation (#[cfg(target_os = "macos")]):
This only applies to a single declaration, and is a foreign syntax for Clang languages.

We decided on extending requires as it’s syntax that already exists and was similar to another syntax we were considering (CSS style media queries). The proposed syntax does require some disambiguation to decide between the existing requires and the new block version, however this can be done as a single token lookahead after parsing the feature-list.

We would appreciate any input, and will be preparing a patch for review soon including adding documentation with a more formal syntax.

1 Like

Nice approach, thanks for working on such improvement.

A specific example is AppKit vs. UIKit on different platforms where we need different export_as declarations. Our current workaround here requires using a VFS to override the modulemap.

Yea, this adds unnecessary complexity to the build system, and the solution might also help unifying modulemaps for different archs (Darwin’s multiple usr/include/module.modulemap for instance).

I’m curious about the semantics for top-level vs submodules. Right now requires causes an error on a mismatch in a top level module but silent skips building when used within a submodules declaration, do you intend to keep the same semantics for this extension? I’m worried that if this proposal errors out on top level modules, it might hinder some of the proposed usability. OTOH, if it silently skips on top level mismatches it could feel weird that it behaves differently from the terse form. What are your thoughts on this?

The semantics I had in mind were that a requires block is totally ignored if its feature-list isn’t satisfied. Thus when used to wrap a top level module it would be ignored. While different from how requires currently works, I’m not really concerned about confusion here as the semantics are easy to explain.

The fact that top level modules (which I missed from your description) can be wrapped with requires is another strong reason to go with the “ignore” behavior, thanks for the clarification. It would be useful to have that specific example added in the doc update as part of your patch. Cheers!

I’ve created a patch for this over at ⚙ D118311 [Clang][ModuleMap] Add conditional parsing via requires block declaration. Moving further discussion there.