Hi!
I have a low-priority issue with a specific configuration. Not sure about the exact cause of the problem, but I'll try to describe it in details:
I have two iOS targets in my project for iosArm64 and iosX64, both of them includes the same cinterops configuration for an Objective-C framework.
First case - no common target:
If I use the targets separately the project builds and runs just fine. They have the same source directory, but the IDE only recognizes the first target and shows a warning that the second target misses the actual declarations. I've also tried to make one target depend on the other, but the result is the same. This is a bit annoying, as it looks like an error, but at least it works fine when I run it.
Second case - with common target:
Based on a couple examples I've found on GitHub, I've tried to create a common iOS target and make the two existing one dependent on it. It resolves the errors related to the actual declarations, but then the interopped framework is not loaded in the common target, which breaks the build.
I've tried to add the cinterops config to the common target also, but then the build time increases significantly, as it has to run the cinterop script 3 times.
So my question related to the first case:
Is it possible to somehow get rid of the actual warnings with the same source folder, without using a common target? Because otherwise the first approach works.
And for the second case:
Is it possible to reference the interopped framework in the common target from one of the child targets? I guess the problem is that the cinterop script would only run for a dependent target after the main target build finishes, but that requires the interopped framework for the build.
Bonus question:
Would it be possible to only run the cinterop script for the common target, as the interopped framework supports both architecture anyway? Now if I remove it from the child targets I get reference errors.
Your project is misconfigured.
If I use the targets separately the project builds and runs just fine. They have the same source directory
Different targets must not use the same source directory.
I've tried to add the
cinteropsconfig to the common target also
Interop is target-dependent, adding cinterops to common target is not supported yet.
Please take a look on the following approach:
https://kotlinlang.org/docs/tutorials/native/mpp-ios-android.html#updating-gradle-scripts
The problem with the linked example is that it only builds the selected iOS architecture, however in my use-case I have to build both to generate a fat framework output in the final step. This is the reason why the two targets are using the same source directory.
I've found a couple examples on GitHub where a configuration used a common target for two iOS architecture, for example this one: https://github.com/ktorio/ktor-samples/blob/21d849dda83da9d409681d38093ec2997359dba4/mpp/client-mpp/build.gradle
In this case using the same source for both architectures works, but then I have to add the cinterops script for the two child targets and also the common target which significantly slows the build time.
If there's no way to do this configuration differently at the moment, I understand, but I think this could be improved in the future.
PS: Based on the tutorial you linked, I'm also thinking about a 3rd solution which I haven't tried yet: Maybe it would be possible to modify the iOSTarget variable dynamically with gradle after the selected target's build is done, then build the other target and create the fat framework output afterwards. I'll try to implement this when I have the chance.
but then I have to add the
cinteropsscript for the two child targets and also the common target which significantly slows the build time.
This is not supposed to work properly.
Maybe it would be possible to modify the
iOSTargetvariable dynamically with gradle after the selected target's build is done,
You can run two Gradle builds with different settings. Alternatively it may be possible to have second target disabled when importing the project to IDE.
Alright, thanks for the help!
I'll try this new approach and will post my configuration when I get it working.
So I might have over-engineered this a little bit, but here's my solution in summary:
I have a task for creating the fat framework with lipo. This was already in the project, I just moved one level up into the root gradle file.
task combineIosArchitectures(type: Exec) {
executable 'lipo'
args = [
'-create',
'-arch', 'arm64', 'Sample/build/xcode-frameworks/Sample_iosArm64.framework/Sample',
'-arch', 'x86_64', 'Sample/build/xcode-frameworks/Sample_iosX64.framework/Sample',
'-output', 'Sample/build/xcode-frameworks/Sample.framework/Sample',
]
}
I've made two GradleBuild tasks in the root gradle file to build the frameworks for the two architectures.
task runBuildIosArm64(type: GradleBuild) {
buildFile = 'Sample/build.gradle'
tasks = ['linkReleaseFrameworkIos']
startParameter.setProjectProperties([
'IOS_TARGET': 'iosArm64',
'XCODE_CONFIGURATION': 'RELEASE'
])
}
task runBuildIosX64(type: GradleBuild) {
buildFile = 'Sample/build.gradle'
tasks = ['linkReleaseFrameworkIos']
startParameter.setProjectProperties([
'IOS_TARGET': 'iosX64',
'XCODE_CONFIGURATION': 'RELEASE'
])
}
combineIosArchitectures.dependsOn runBuildIosArm64
combineIosArchitectures.dependsOn runBuildIosX64
So after this I can get the supplied target name in the multiplatform plugin configuration:
final def targetName = project.findProperty("IOS_TARGET") ?: 'iosArm64'
final def iOSTarget = targetName == 'iosArm64' ? presets.iosArm64 : presets.iosX64
fromPreset(iOSTarget, 'ios') {
(Existing configuration, cinterops, etc...)
}
In the last step the generated frameworks are copied into the xcode-frameworks folder and the combineIosArchitectures task runs to create the final output. I'll make a sample repo later with a bit cleaner configuration.
This kind of solves the original problem, no more need for two targets with shared sources, however it still takes some time to run cinterops twice. Maybe the two target builds could be parallelized, but that's a Gradle question.
Nice work!
however it still takes some time to run
cinteropstwice.
cinterops is target-dependent, you can't run it once properly for two different targets currently.
Alright, well there might be other ways to speed up the process. I've noticed that if I run the build just for one target, the interopped framework will get cached and it doesn't need to be rebuilt every time when I make a new build. However with the two targets one of the always have to run, I guess because they are using the same build folders. Maybe if there would be a way to store both interopped frameworks it could speed up the process.
But anyway, it might a be a too specific issue for now, so I'm fine with the current solution.
That indeed looks like very interesting solution, @Endanke! I've posted the link to it to #kotlin-native channel on Slack for people to see (https://kotlinlang.slack.com/archives/C3SGXARS6/p1551300452086700). Please join if you see the benefit in further discussion.