Including C system headers through a Swift Package

I'm trying to create an xcframework around some existing C code we have, wrap that in a Swift Package, and then create a sample app using that package. One of our C headers includes iso646.h (among other C system headers), it's used like this:

#if defined(__ANDROID__) or defined(__iOS__)

and errors out with:

Token is not a valid binary operator in a preprocessor subexpression

when building the sample app, on the or in that line (with other similar errors on similar lines).

I think I need to somehow let my app know it needs to pull in the iso646 header file. I tried with a module map that gets included with the xcframework, by adding link "iso646" in my framework module. I also tried to create a separate module at the top level, in that same module map, with:

module std [system] [extern_c] {
  module iso646 {
    header "iso646.h"
    export *
  }
}

Neither of these approaches worked; we kept getting the same error.

My next attempt was to create a Swift Library Package that just wrapped some C system libraries, but I got stuck because I didn't see a way to include that as a dependency on the binaryTarget for my xcframework in my Swift Package.

I've been stuck on this a while, and I'm hoping this newbie is just missing something pretty basic; how can I past this error?

P.S. This is a cross-post from Stack Overflow; I wasn't getting any help there.

Looks like you should use #if A && B in your Minimis.h file to solve "Token is not a valid binary operator in a preprocessor subexpression" issue

Thank you, but do you have any idea why Swift doesn't recognize the symbols defined in iso646.h in this context? I'm trying to figure out if I'm doing something wrong or if this just isn't supported.

Oh, I got your point. There is #define and && in iso646.h. And when the header is included, we can use #if A and B in a .m or .c file. But here we will have problem when using it in a header.

You can copy the contents of iso646.h directly into your header - Minimis.h. And that’s how a #include directive works.

Here is what I found:
If we do not change the content, it will be the same as if we still use #include "iso646.h". And the build will fail.

By modifying #ifndef __ISO646_H to something else, it will be valid to use and in the header.

// Minimis.h
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT double MinimisVersionNumber;
FOUNDATION_EXPORT const unsigned char MinimisVersionString[];
#ifndef __ISO646_H2
#define __ISO646_H2
#ifndef __cplusplus
#define and    &&
#endif
#endif
#if A and B
    #pragma message ("A and B are defined")
#else
    #warning Neither A nor B are defined
#endif
void f(void);

Therefore, it seems that the __ISO646_H has been defined when processing this file. So I update it to the following to try to find where is the previous define.

// Minimis.h
...
#ifndef __ISO646_H2
#define __ISO646_H
...

As expected, I get a warning complaining '__ISO646_H' macro redefined

Here is the final result:

/Users/kyle/Downloads/DemoLib/Sources/Minimis/include/Minimis.h:13:9: warning: '__ISO646_H' macro redefined
#define __ISO646_H
        ^
<command line>:3:9: note: previous definition is here
#define __ISO646_H 1
        ^
<module-includes>:1:9: note: in file included from <module-includes>:1:
#import "/Users/kyle/Downloads/DemoLib/Sources/Minimis/include/Minimis.h"

The log is hinting me it was previously defined in command line. But I could not see it in the compile command. :thinking:

Final, after searching in the swift repo. I found the root cause.

When you build on the Darwin platform(eg. iOS and macOS), the compiler will deliberately add the definition to prevent you from using it.

So the problem will only occur if you try to use iso646.h on Darwin platform.

It was committed 9 years ago in the beginning of Swift project. Maybe @jrose will know some context here.

Oh no :sob: There’s not much more context beyond what’s written there: some system headers use either and or or as an identifier (don’t remember which). What I’m not sure about is why modules aren’t sufficient to fix this: the headers that use and and or wouldn’t be including iso646.h. Maybe someone at Apple can go find the specific motivating example for that change, because maybe it’s been fixed.

We still have to be very careful about changing this though, because perhaps Library A included iso646.h without using it, and Swift bridging header B imports library A and uses one of the reserved words as an identifier. So we might just be stuck for source compatibility reasons.

2 Likes

I think maybe we can pitch and remove it under Swift 6 language mode if it's been fixed. cc @Douglas_Gregor

Otherwise we may wait for the next breaking release version. Or in the worst, we'll never clean such hacky code on Darwin Platform.

cc @tkremenek

Unfortunately language modes cannot easily be used to adjust the importer, because all imported Swift modules need to have the same (or at least compatible) views of imported Clang modules, and you can mix and match Swift versions across modules.

Swift 6 is around the corner and will be the next version after 5.10

I don't know if anyone at Apple is looking at this. Anyway I filed FB13642542 to track this. @eskimo

Mirror: FB13642542: Swift can't include iso646.h header on Darwin platform · Issue #470 · feedback-assistant/reports · GitHub

The will become a bigger issue on Darwin platform as we are adding C++ Interoperability to Swift recently. cc @cxx-interop-workgroup