I'm trying the KMP/CMP setup within IntelliJ with ...
# multiplatform
o
I'm trying the KMP/CMP setup within IntelliJ with the incubating new Multiplatform plugin. I have a working project for Android, Desktop and recently iOS. When running the project from Xcode, it works. When running from IntelliJ using the Xcode app launcher, it launches but crashes right away.
Copy code
"/Users/.../Library/Caches/JetBrains/IdeaIC2025.1/DerivedData/MyApp-fgmdywopfclxqobcsnyttiffrkhx/Build/Products/Debug-iphonesimulator/MyApp.app"
Uncaught Kotlin exception: org.jetbrains.compose.resources.MissingResourceException: Missing resource with path: /Users/.../Library/Developer/CoreSimulator/Devices/5B4A74A8-04EE-4EE6-9249-2FB5E12CF7B8/data/Containers/Bundle/Application/E48DDD44-7B50-4301-AEA8-5EAEB29802EF/MyApp.app/compose-resources/composeResources/my.package.resources/values/strings.commonMain.cvr
This is a multimodular Gradle setup if that matters. I tried forcing res generation somehow without luck
Copy code
compose.resources {
    publicResClass = true
    packageOfResClass = "my.package.resources"
    generateResClass = always
}
Anyway, I have the feeling that the Xcode/iOS sim launcher doesn't build stuff like
embedAndSignAppleFrameworkForXcode
does and maybe not even building anything at all… What is the impact of
OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED=YES
in IntelliJ? I do have
my-module-shared/build/processedResources/iosSimulatorArm64/main/composeResources/my.package.resources/values/strings.commonMain.cvr
So, I guess this is a matter of packaging/moving stuff around in the final
.app
, but who is responsible for this? which task is not called?
Here are all
strings.commonMain.cvr
I can find
Copy code
./my-module-shared/build/generated/compose/resourceGenerator/assembledResources/iosSimulatorArm64Main/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/generated/assets/copyReleaseComposeResourcesToAndroidAssets/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/generated/assets/copyDebugComposeResourcesToAndroidAssets/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/processedResources/iosSimulatorArm64/main/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/kotlin-multiplatform-resources/assemble-hierarchically/iosSimulatorArm64ResolveSelfResources/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/kotlin-multiplatform-resources/aggregated-resources/iosSimulatorArm64/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/intermediates/assets/release/mergeReleaseAssets/composeResources/my.package.resources/values/strings.commonMain.cvr
./my-module-shared/build/intermediates/assets/debug/mergeDebugAssets/composeResources/my.package.resources/values/strings.commonMain.cvr
When creating a whole new project from scratch, when I launch the Xcode launcher from IntelliJ, it properly triggers the Gradle build
Copy code
Executing ':composeApp:embedAndSignAppleFrameworkForXcode'…
and I can see a bunch of other tasks like
syncComposeResourcesForIos
which is most likely what is missing on my side. For my project, when I launch the Xcode launcher, no Gradle task is triggered, why is so? What controls this? In the launcher, the configuration are similar between the 2 projects, and I have a "Build" dependency at bottom on both sides…
If I add specifically a Gradle task to the launcher in the "Before launch" section to trigger
embedAndSignAppleFrameworkForXcode
it fails telling me
Copy code
Could not infer iOS target architectures. Make sure to build via XCode (directly or via Kotlin Multiplatform Mobile plugin for Android Studio)
like it does in CLI The launcher do something under the hood, but what?! and why does it fail here?
I tried saving the launch config as project file to compare. The config are strictly indentical (except indentation, don't know why and obviously, the scheme & target names which are different). Interestingly, on my project, it suggested
.idea/runConfigurations/MyApp.xml
as output and on the other project, it was
.run/iosApp.run.xml
🤔
Ok, so it appears I finally have a minimal reproducer… I seems to be linked to the use of alternative
Configuration
like
Config.dev.xcconfig
. When the
PRODUCT_NAME
or
PRODUCT_BUNDLE_IDENTIFIER
diverge, it seems I get the issue. So, to reproduce, create a new project using the incubating wizard, only choose iOS. Add a string resource instead of hardcoded strings within app. Add a
Config.dev.xcconfig
near the default
Config.xcconfig
and reference it as a debug config in Xcode project for the
iosApp
configuration. It's still not 100% reproducible with this minimal repro setup, sometimes, changing the values of
Config.dev.xcconfig
is not enough, changing few times is enough to reproduce at some point though.
That being said, in my "real use case", I think it's still slightly different, no Gradle task is invoked at all, here, I seems I have it. I still don't understand who calls the Gradle tasks, when, how to control that… At least, having some logs of what happens between the IDE, Gradle, Xcode,
xcodebuild
would be nice.
I opened KMT-1320
thank you color 2
I also created KMT-1321 for Gradle not being called before running
xcodebuild
in my setup
p
Hello, @Olivier Patry I am working on Xcode build support for KMP Plugin. Thanks for such a detailed case. I'll try to answer your questions and give workarounds > What is the impact of
OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED
This parameter is added to
env
when running
xcodebuild
from IJ. This check is there to prevent Gradle from being invoked by Xcode, and IJ should build Kotlin library using it's Gradle integration. However in your case, Gradle is not run (I'll explain the cause later this message). To workaround this, just remove the
OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED
check in the shell script > When running from IntelliJ using the Xcode app launcher, it launches but crashes right away. I will need more time to investigate why some Compose resources are missing from the bundled library and will get back to you. > If I add
embedAndSignAppleFrameworkForXcode
to the "Before launch" section, it fails Yes, this is intentional behaviour.
embedAndSignAppleFrameworkForXcode
needs several Xcode project properties, that are provided from the project model, so it is not designed to be run manually. If it is really necessary, it can be done by manually providing those in
env
before running a task, but the list of parameters is not documented and may vary from version to version, as it's part of implementation details. Tell me if you need those (i hope you don't) > Gradle not being called before running
xcodebuild
in my setup As for KMT-1321, I'd recommend you to use the workaround with
OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED
above for now. Currently KMP plugin relies on a regex to determine whether a particular target depends on a Kotlin library or not. And it's quite restricted now: 1. It should be written in one line 2. Any arguments\parameters will be discarded (This will be fixed soon in KMT-1211) 3. Only one Gradle task invocation is supported in a line. Under those restrictions, after KMT-1211 is fixed, the proper solution for you will be to change the shell script to following:
Copy code
if [ -z "${IOS_TARGET}" ]; then
  echo "You must define IOS_TARGET to 'all', 'simulator' or 'device' to allow building for iOS."
  exit 1
fi

echo "Building for '${IOS_TARGET}' target"

cd "${SRCROOT}/.."
# Please note there are 2 Gradle invocations below

if [ "${OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED}" = "YES" ]; then
  echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to 'YES'."
else
  ./gradlew :tasks-app-shared:embedAndSignAppleFrameworkForXcode -Pios.target="${IOS_TARGET}"
fi

./gradlew :tasks-app-ios:updateXcodeVersionConfig -Pios.target="${IOS_TARGET}"
I should also mention. that in the project you provided, there are both
build.gradle.kts
and
Taskfolio.xcodeproj
in your
tasks-app-ios
directory. Due to IJ architecture limitations, this is not recommended, and I would like to suggest you move your Xcode project one directory deeper, so the module content roots will be different: (as example
<root>/tasks-app-ios/build.gradle.kts
and
<root/>tasks-app-ios/iosApp/Taskfolio.xcodeproj
) We will work on and improve KMP support for IJ and AS, so feel free to come to us with any feedback or problems you have. Wish you a good day!
2
o
Oh, what a nice answer!! I'll take a deeper look at it, but at least, you seem to clearly understand what is going on!! I'll let you know then.
I should also mention. that in the project you provided, there are both
build.gradle.kts
and
Taskfolio.xcodeproj
in your
tasks-app-ios
directory. Due to IJ architecture limitations, this is not recommended, and I would like to suggest you move your Xcode project one directory deeper, so the module content roots will be different:
Regarding the combination of
build.gradle.kts
and
.xcodeproj
in same dir, I wasn't proud of it and thought it could lead to issue (apparently not in my case for now) and I'll think about updating this, I went quick & simple for short term. Thanks for letting me know and warn about potentiel trouble with this 👍
So, I first simplified the
gradlew
call in the Xcode build phase section. Indeed, it solved the issue of Gradle not being called at all 👍 I added a dependency from
embedAndSignAppleFrameworkForXcode
to
updateXcodeVersionConfig
. It's even cleaner as it forces the version to be up to date no matter who calls
embedAndSignAppleFrameworkForXcode
from anywhere. I also managed my setup which relied on
-Pios.target
to also fallback to
IOS_TARGET
env variable to get rid of the need of
-P
as well.
Copy code
gradle.projectsEvaluated {
    project(":tasks-app-shared").tasks.named("embedAndSignAppleFrameworkForXcode") {
        dependsOn(":tasks-app-ios:updateXcodeVersionConfig")
    }
}
By doing this, I understand that 1. it doesn't break the detection of Xcode proj & Gradle task invocation and let the build finish from IntelliJ 2. it still allows building the whole project from Xcode by calling Gradle properly I think it kinda fixes the problem on both sides. Still remains the string resource issue, but that's another story. So, my understanding is that for Gradle to be executed, the Build phase section must contain
:SHARED_PROJ_NAME:embedAndSignAppleFrameworkForXcode
(and not so much more for now) If I call another task which in turns calls
embedAndSignAppleFrameworkForXcode
it's not being triggered at all.
p
Yes right. It must contain
gradlew
and one of three options: 1.
:shared_module:embedAndSignAppleFrameworkForXcode
- builds only one module 2.
embedAndSignAppleFrameworkForXcode
- builds all modules 3.
:embedAndSignAppleFrameworkForXcode
- builds root project if it is a shared library itself After KMT-1211 it will also be possible to add arbitrary parameters before or after task name, they will be passed as-is to Gradle process
👍 1