How to use Fastlane with a CMake generated XCode project with dependency on WebP?

402 views Asked by At

I have a project written in C++ where CMake is used to generate the build system for various platforms including iOS. The project has a dependency on WebP. You can find an example project on GitHub here that can be used to reproduce things & I've included the relevant source files at the end of this post for completeness.

The Xcode build system for iOS is generated using CMake as follows:

cmake -G Xcode -DCMAKE_TOOLCHAIN_FILE=third_party/ios-cmake/ios.toolchain.cmake -DPLATFORM=OS64 -DDEPLOYMENT_TARGET=15.0 -DENABLE_BITCODE=0 -S . -B cmake-build-release

We can now attempt to build/archive the app using Fastlane with the command from within the generated cmake-build-release directory:

bundle exec fastlane ios beta

However this fails due to being unable to locate various webp object files (that based on console output it appears to have previously successfully compiled):

...

▸ Compiling buffer_dec.c
▸ Compiling alpha_dec.c
▸ Building library libwebpdsp.a

...

 ** ARCHIVE FAILED **
▸ The following build commands failed:
▸   Libtool /Users/dbotha/Library/Developer/Xcode/DerivedData/CMakeFastlaneWebpTest-dlwvukebfiwjqvaqiepshuxqklhh/ArchiveIntermediates/CMakeFastlaneWebpTest/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/libwebpdecoder.a normal (in target 'webpdecoder' from project 'CMakeFastlaneWebpTest')
▸ (1 failure)
▸ ❌  error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: can't open file: /Users/dbotha/CLionProjects/CMakeFastlaneWebpTest/cmake-build-release/third_party/libwebp/CMakeFastlaneWebpTest.build/Release-iphoneos/webpdecode.build/Objects-normal/arm64/alpha_dec.o (No such file or directory)
▸ ❌  error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool: can't open file: /Users/dbotha/CLionProjects/CMakeFastlaneWebpTest/cmake-build-release/third_party/libwebp/CMakeFastlaneWebpTest.build/Release-iphoneos/webpdecode.build/Objects-normal/arm64/buffer_dec.o (No such file or directory)

...

Internally Fastlane attempted to build/archive the project with the following command:

xcodebuild -scheme CMakeFastlaneWebpTest -project ./CMakeFastlaneWebpTest.xcodeproj -configuration Release -destination 'generic/platform=iOS' -archivePath ./out.xcarchive archive

Interestingly an archive can be successfully generated if I use the following xcodebuild command (note how -target flag is used instead of -scheme):

xcodebuild -project CMakeFastlaneWebpTest.xcodeproj archive -target CMakeFastlaneWebpTest -configuration Release

After this successful attempt bundle exec fastlane ios beta will now also succeed as the compiled object files are where it expected them to be.

Now I'd happily workaround this issue using my xcodebuild + -target flag approach and then use the fastlane command to push to Testflight, etc. but the real project (not this toy example) takes a very long time to build so building it twice is really wasteful from a cost point of view on CI platforms.

Does anyone have any idea what's going on here & how I can successfully build things in the first instance using fastlane without my own explicit call to xcodebuild first? Or alternatively how can I have Fastlane use the successfully built objects from my workaround so it doesn't need to rebuild the entire project from scratch?


CMakeLists.txt

cmake_minimum_required(VERSION 3.23)
project(CMakeFastlaneWebpTest)

set(CMAKE_CXX_STANDARD 14)

add_executable(CMakeFastlaneWebpTest src/main.cpp)

# Skip building of unused webp tools which fail for me under ios:
set(WEBP_BUILD_ANIM_UTILS OFF)
set(WEBP_BUILD_CWEBP OFF)
set(WEBP_BUILD_DWEBP OFF)
set(WEBP_BUILD_GIF2WEBP OFF)
set(WEBP_BUILD_IMG2WEBP OFF)
set(WEBP_BUILD_VWEBP OFF)
set(WEBP_BUILD_WEBPINFO OFF)
set(WEBP_BUILD_WEBPMUX OFF)
set(WEBP_BUILD_EXTRAS OFF)
set(WEBP_BUILD_WEBP_JS OFF)
add_subdirectory(third_party/libwebp EXCLUDE_FROM_ALL)
target_link_libraries(CMakeFastlaneWebpTest PRIVATE webpdecoder webpdemux)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/libwebp/src)

configure_file(${CMAKE_SOURCE_DIR}/fastlane/Appfile ${CMAKE_BINARY_DIR}/fastlane/Appfile COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/fastlane/Fastfile ${CMAKE_BINARY_DIR}/fastlane/Fastfile COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/Gemfile ${CMAKE_BINARY_DIR}/Gemfile COPYONLY)
configure_file(${CMAKE_SOURCE_DIR}/Gemfile.lock ${CMAKE_BINARY_DIR}/Gemfile.lock COPYONLY)

set_target_properties(CMakeFastlaneWebpTest PROPERTIES
        XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}
        MACOSX_BUNDLE TRUE
        MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/src/iOS-Info.plist.in
        MACOSX_BUNDLE_GUI_IDENTIFIER com.dbotha.CMakeFastlaneWebpTest
        MACOSX_BUNDLE_BUNDLE_NAME CMakeFastlaneWebpTest
        MACOSX_BUNDLE_BUNDLE_VERSION "0.1"
        MACOSX_BUNDLE_SHORT_VERSION_STRING "0.1"
        )

set_xcode_property(CMakeFastlaneWebpTest PRODUCT_BUNDLE_IDENTIFIER "com.dbotha.CMakeFastlaneWebpTest" All)
set_xcode_property(CMakeFastlaneWebpTest CODE_SIGN_IDENTITY "iPhone Developer" All)
set_xcode_property(CMakeFastlaneWebpTest DEVELOPMENT_TEAM "GFP63373B2" All)

fastlane/Appfile

app_identifier("com.dbotha.CMakeFastlaneWebpTest") # The bundle identifier of your app
apple_id("REPLACE_ME") # Your Apple Developer Portal username

itc_team_id("REPLACE_ME") # App Store Connect Team ID
team_id("REPLACE_ME") # Developer Portal Team ID

fastlane/Fastfile

default_platform(:ios)

platform :ios do
  desc "Push a new beta build to TestFlight"
  lane :beta do
    build_app(scheme: "CMakeFastlaneWebpTest", configuration: "Release")
    upload_to_testflight
  end
end

src/main.cpp

#include <iostream>
#include <webp/demux.h>

int main() {
    WebPAnimDecoderOptions decOptions;
    (void)decOptions;
    std::cout << "Hello, World!" << std::endl;
    return 0;
}
1

There are 1 answers

0
Drejc On

I'm not an expert in this topic but according to the documentation you should provide workspace to build your scheme.

To build an Xcode workspace, you must pass both the -workspace and -scheme options to define the build. The parameters of the scheme will control which targets are built and how they are built, although you may pass other options to xcodebuild to override some parameters of the scheme.

Scheme controls what target will be build, and guessing by your example, since you do not provide a workspace, it gets lost in the process.

The scheme is not lost anymore if you build the target manually. Since it is already build the scheme does not have to do a thing.

My proposals:

  • Option 1: Try adding workspace to build_app parameters in Fastfile.
  • Option 2: Don't bother with building scheme just use target in the build_app parameters in Fastfile like so: build_app(target: "CMakeFastlaneWebpTest", configuration: "Release")