Caching swiftmodule files

Hi,

We are trying to create a swift module cache for ios.

As far as I know, swiftmodule files are like header files. When we opened it we found:

  1. absolute paths
  2. Bridging and other related obj-c headers in plain text

When we tried to reuse a swiftmodule on another computer we got a duplicate header import issue:
...
error: failed to import bridging header '/path/to/bridging/header'
...
/path/to/header/file/header1.h:11:1: error: duplicate interface definition for class 'someinterface'
@interface someinterface : NSObject
^
/path/to/header/file/header1.h:11:12: note: previous definition is here
@interface someinterface : NSObject
^
...

We've been trying to understand how the swift compiler works, but we are not sure why it tries to import the same header file twice? How does swift decide what header files to import from the swiftmodule file? Or what not to import?

If we run the exact same swiftc command and reference the swiftmodules that were build on the same computer, it runs without any errors.

Can you explain more about what you're actually trying to do? What's the point of caching a swiftmodule? Or are you really trying to cache a whole built framework?

Putting the bridging header into the swiftmodule is a bad debugging hack; I say "bad" because it doesn't really accomplish its original goal of "behaving the same as when you compiled even if headers have changed on the filesystem". We're very leery of removing it, though, because we're not sure who's relying on this bad behavior. Anyway, that explains your relocatability problem.

That said, libraries are not intended to have bridging headers at all; only apps and unit tests. There's no support going to this use case because it can cause all sorts of problems for debugging.


As for the absolute paths, a number of those are search paths, also for debugging purposes, and also considered bad. We don't have a good answer for what to replace those with, though—if your library requires custom search paths to find some of its dependencies, where else is the debugger going to get them from?

(It used to get them from debug info only, actually, but then we found that there were situations where something could be compiled but not debugged, even though the debugger was doing nothing wrong. Keeping the compiler and debugger in sync decreases the chances of issues between them.)

Thanks for the quick answer, I appreciate it!

We have a modular architecture, and we are trying to cache ios modules. Our modules can either have:

  1. only swift code

  2. only obj-c code

  3. both

The problem comes when we are trying to cache a module that has swift and a bridging header. We cache the generated ".a" binary, and the generated ".swiftmodule" file as well. Afaik, other modules rely on the existence of this swiftmodule file. Is that right?

You mentioned putting bridging headers into the swiftmodule is a "bad hack". What do you suggest we should do to avoid this happenning? I still don't really understand why this is causing duplicate import issues for us?

This is how I imagine the swift compiler works when generating a swiftmodule, please correct me if I'm wrong here (I'm pretty sure I am):

  1. goes through the imports in a swift module called "A"

  2. finds that it needs to import module "B"

  3. reads "B"'s "swiftmodule" file, loads the header files it finds inside the swiftmodule file (Bridging-header, other headers)

  4. then loads "A"'s bridging-header

  5. "A"'s bridging-header has the same header files imported as "B"'s, which causes a duplicate import issue

+) For some reason though, if we create both A and B swiftmodule on the same computer, this issue isn't happenning. Probably because the compiler knows that the header file it found in module A is the same as it is in B?

Follow-up question, is there any way to compile swift without creating swiftmodule files? Do we need them "only" for debugging? If so, how would one tell xcodebuild/swiftc not create any swiftmodules?

I managed to solve the issue. The problem was here:
if (headerFile && headerFile->getSize() == expectedSize &&
headerFile->getModificationTime() == expectedModTime) {
return importBridgingHeader(header, adapter, diagLoc, false, true);
}

Although I can understand why checking to modification time and the size of a file to see if it has changed, it has its drawbacks. In our case, every developer has the header files with a different modification time, as they cloned the repository at different times. This caused the swift compiler to import the bridging header from the file on disk, and then moved onto to import the other header files in the swiftmodule file, hence the duplicate import.

Anyways, now we are able to cache swift files and distribute them if the git repo is always at the same absolute path. Hopefully with swift 5 other use cases will be covered as well.(https://github.com/apple/swift/pull/17665)

Thanks again for your help, @jrose !

I'm still very concerned. If you are using bridging headers in your library targets at all, this is unsupported. If you use modules for everything, you won't have any of these problems, including (most of) the search path issues.

I think I may have miscommunicated our intent, sorry for that.

A module for us is one build target in xcode, which is not to be confused with the module in swift. These targets each have their on bridging headers so that they can use other module targets' classes/interfaces. (and we aren't trying to use bridging headers with libraries)
We have more than 40 module targets, and a clean build takes almost an hour to finish. In one pull request, most of the time only one module target's source changes, so it makes sense for the other modules to come from cache.

I hope this clear things up a bit.

Terms of Service

Privacy Policy

Cookie Policy