Incremental builds not working for one specific framework

We have an app with a bunch of framework dependencies that we build in-house. I noticed recently that one of the higher-level framework dependencies seemed to always re-compile, even if I have not changed a single file. The other lower frameworks do not re-compile anything. I've double checked the framework references and there are no cyclic dependencies and everything imported by this framework is explicitly referenced in the linker settings.

I enabled -driver-show-incremental and started seeing a couple of errors emitted

error: invalid Swift parseable output message (invalid pid (no subtask record)): { "kind": "finished", "name": "compile", "pid": 52000, "process": { "real_pid": 52000 }, "exit-status": 0 } (in target 'AppFramework' from project 'AppFramework')

and

Command CompileSwiftSources emitted errors but did not return a nonzero exit code to indicate failure

In addition, in the build output I see a bunch of queueing lines like the following

Queuing because of external dependencies: {compile: TracksLayerSection.o <= TracksLayerSection.swift}

Interestingly at the same point every time this output gets corrupted with some JSON output whenever the first error above is encountered

Queuing because of external dependencies: {compile: GPSProfileWizardStepViewController.o <= GPSProfileWizardStepViewController.swift} Queuing because of external dependencies: {compile: ProfileItemPickerViewController.o <= ProfileItemPickerViewController.swift} Queuing because of external dependencies: {compile: GPSSRPickerViewController.o <= GPSSRPickerViewController.swift} Queuing because of external dependencies: {compile: MapSRPickerViewController.o <= MapSRPickerViewController.swift} Queuing because of external dependencies: {compile: UIBarBut14672 { "kind": "began", "name": "compile", .............. ], "pid": 52000, "process": { "real_pid": 52000 } } error: invalid Swift parseable output message (invalid pid (no subtask record)): { "kind": "finished", "name": "compile", "pid": 52000, "process": { "real_pid": 52000 }, "exit-status": 0 }

I've tried disabling batch mode -disable-batch-mode I get the same errors.

I've tried the legacy build system and that does also re-compiles all of the files in that framework but doesn't emit the same error. I've also tried Xcode 11.7 and 12.0.1

I should point out that MOST of the time the error emitted matches this exactly

error: invalid Swift parseable output message (invalid pid (no subtask record)): { "kind": "finished", "name": "compile", "pid": -1000, "process": { "real_pid": 62841 }, "exit-status": 0 }

note the pid = -1000

Anyone have any ideas?

This does seem like a bug. If you can privately share the project, you could file a bug report through feedbackassistant.apple.com and attach your project, that usually helps in narrowing down the issue quicker. Otherwise, you could file a bug report on bugs.swift.org.

Interestingly... i deleted a chunk of files from that framework... random, nothing specific... and the incremental builds are working. So in the chunk of files I removed it must cause the build system to throw it's hands up and say "i cant figure it out build it all"

Unfortunately I can't share this project. I'll create a bug on bugs.swift.org though with as much information as I can add

Found the issue. One of the Swift frameworks we have includes some C code that is internal to the framework. We had previously run into an issue with some of the C symbols being exported so we had a post-build script that would modify the .modulemap file for the framework so that the C headers would not need to be included.

This was necessary at some version of Xcode/Swift, but seems to no longer be necessary. Simply removing our post-build script and having those headers continue to be private to the framework seems to be working just fine and has restored incremental builds.

1 Like

Right - when we load a module we take a dependency on everything in that path. For clang modules, that means not just the headers, but the modulemap too. The incremental build currently relies on those inputs being a constant in the happy path, because we cannot know a priori how a change to such external inputs affects the shape of imported code or the module itself. Therefore, whenever external dependencies have a mod time that differs from the last incremental build, we basically invalidate the entire target.

Removing this script will stop the symptoms as you’ve already noticed. You can put it back as long as you replace it with an incremental version that doesn’t touch the modulemap or any headers if there are no changes.

I’ve wondered before if we could improve things here by precompiling clang modules into swiftmodule files and letting the recent work on cross-module incremental builds kick in to help. But that’s an avenue we have yet to explore.

2 Likes

Ah, thanks for that explanation Robert, that makes sense now. The only question I have about that now is why didn't the target whose modulemap changed need to be rebuilt every time as well?

A modulemap file isn't really all that relevant to a target's incremental build with respect to that target. They are not usually referenced as a kind of resource from any of the translation units in the module, and changes to the modulemap alone don't affect the layout of source files in the project. It's mostly meant to alter the way clients see the structure of a group of related headers, so it makes sense that something even as simple as touch *.modulemap doesn't really turn over much. It might cause some extra file copying jobs but otherwise that's about it.