Reputation: 7171
I'd like to use a few classes from the LLVM project in my own Objective-C app. Specifically, I want to use the classes declared in BitstreamReader.h and BitstreamWriter.h. Unfortunately, I don't have much experience linking C++ libraries, so I don't really know where to begin. I started by installing llvm through Homebrew with brew install llvm@14
. Then, in my Xcode project, I tried linking the libraries in /opt/homebrew/opt/llvm@14/lib
, and adding /opt/homebrew/opt/llvm@14/include/**
to my HEADER_SEARCH_PATHS
.
Now I'm completely stuck. I have a bunch of build errors in Xcode like:
Any help at all would be greatly appreciated. Thanks!
Upvotes: 1
Views: 804
Reputation: 8958
In C++ world there are quite a few ways to incorporate a dependency into your project, I'll outline some of them, but will try to describe in details one which I find the most suitable for your scenario:
This is the most classic approach, when you have the libraries pre-compiler and pre-installed for your specific platform so they are available under default library search-path. It's similar to how system frameworks in iOS are used - you only add linker command to the project, while the frameworks (libraries) exist independently from it (or don't exist, which causes linker error). The problem with this approach, is that you cannot really use the libs for restricted systems, where "default" libraries are unchangeable (iOS, tvOS, iPadOS)
Another approach is to precompile all libraries you need into archives for all platforms the libraries are supposed to be used and then just embed one of the library version which is required into your final app binary. This approach is somewhat cumbersome and unportable because it will require you to compile the lib and re-embed it manually each time you need some parts of library changed or new platform supported.
xcframework
This approach is very similar to the previous one with a few benefits that you can wrap binaries for all platforms under one package and even release it in SPM.
Many projects (and LLVM is not an exception) in C++ are made with use of so-called CMake tool. It's widely adopted multi-platform build system and one of the benefits you can employ is that you can easily include any other project as part of your own. At the same time CMake is not widespread at all in the world of mobile development, so you may have hard time finding relevant information for these platforms.
This is the solution I'd like to describe in more details. In a nutshell this approach consists of 6 steps:
The benefits of this approach is that it gives the most consistent experience (after generating project, all parts are configurable from Xcode), freedom of managing the source code (the LLVM project files can be altered however you want), and luxury of built-in dependency graph (all libraries required to build a target are given under Xcode settings).
You can clone the project from here. Be advised that this has to be in the same folder your future workspace will be in, so you better prepare it in advance:
% mkdir MyWorkspace && cd MyWorkspace
% git clone [email protected]:llvm/llvm-project.git
First, ensure you have CMake installed (it doesn't come out of the box with macOS). You can use brew
or just download the app from the official site. After that create a new folder for the future Xcode project next to llvm-project
repo folder and run cmake
on the LLVM toolkit project:
% mkdir LLVM && cd LLVM
% cmake -GXCode ../llvm-project/llvm
Nothing fancy here, just open Xcode and navigate to File/New/Workspace
menu (Ctrl+Cmd+N shortcut). Ensure that your workspace is created in the MyWorkspace
folder. You can just do it from Xcode:
Then, add the LLVM project created on the previous step. Open File/Add Files to "MyWorkspace"...
menu (Option+Cmd+A) and select the Xcode project file:
If Xcode suggests to auto-create schemes I recommend accepting this suggestion so you don't have to deal with it yourself later on.
This step mimics the previous one with exception that you add your own project to the workspace. I didn't have an existing project for that, so I just created a new one (in my case macOS command-line app) in the workspace directory. If you do the same, ensure that the project is added to "MyWorkspace" and the folder is correct:
Eventually your workspace "Project Navigator" should look something like this:
And here is how the directory tree looks like in a nutshell:
% tree -L 2
.
|-- LLVM
| |-- $(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
| |-- CMakeCache.txt
| |-- CMakeFiles
| |-- CMakeScripts
| |-- CPackConfig.cmake
| |-- CPackSourceConfig.cmake
| |-- Debug
| |-- LLVM.xcodeproj
| |-- MinSizeRel
| |-- RelWithDebInfo
| |-- Release
| |-- benchmarks
| |-- build
| |-- cmake
| |-- cmake_install.cmake
| |-- docs
| |-- examples
| |-- include
| |-- lib
| |-- llvm.spec
| |-- projects
| |-- runtimes
| |-- test
| |-- third-party
| |-- tools
| |-- unittests
| `-- utils
|-- MyProject
| |-- MyProject
| `-- MyProject.xcodeproj
|-- MyWorkspace.xcworkspace
| |-- contents.xcworkspacedata
| |-- xcshareddata
| `-- xcuserdata
`-- llvm-project
|-- CONTRIBUTING.md
|-- LICENSE.TXT
|-- README.md
|-- SECURITY.md
|-- bolt
|-- clang
|-- clang-tools-extra
|-- cmake
|-- compiler-rt
|-- cross-project-tests
|-- flang
|-- libc
|-- libclc
|-- libcxx
|-- libcxxabi
|-- libunwind
|-- lld
|-- lldb
|-- llvm
|-- llvm-libgcc
|-- mlir
|-- openmp
|-- polly
|-- pstl
|-- runtimes
|-- third-party
`-- utils
From this point forward you'll only need Xcode to finish the job. First, let's link the libraries you need to the project and start from the Bitstream archive. Open your project's target General tab settings and under Framework and Libraries click the + sign which should take you to the screen with all local dependencies currently available. It's quite a long list of tools from the LLVM project + native Apple libs, so you may want to filter it as needed to find the required position:
Now the tricky part. You might not actually be required to do that, but if the LLVMBitstreamReader target itself relies on symbols of other libraries (and exposes such a dependency with explicit use of the libraries symbols) the project linking will fail, so to be safe go to the LLVMBitstreamReader build settings and check what it depends on under Target Dependencies section:
Now add these dependencies to your project as well. Eventually your Frameworks and Libraries section should look like this:
Debug
/Release
/MinSizeRel
/RelWithDebInfo
). In order for your own project's targets to find the libraries built with the given configuration you either have to adjust the LLVM project/targets settings, which I don't recommend because it's a lot of manual work, or just add custom library search path to your project. For the latter go the Build Settings of the target where you added the dependencies at the previous step, find Library Search Path item and add $(SRCROOT)/../LLVM/$(CONFIGURATION)/lib
as a new item:$(SRCROOT)/../llvm-project/llvm/include
$(SRCROOT)/../LLVM/include
At this point you should be good to use the LLVMBitstreamReader library and classes defined there. I renamed my main.m
to main.mm
so clang knows i'm going to use C++ in my code, and here is my sample code:
//
// main.mm
// MyProject
//
// Created by Aleksandr Medvedev on 14.12.2022.
//
#import <Foundation/Foundation.h>
#import <llvm/Bitstream/BitstreamReader.h>
#import <iostream>
int main(int argc, const char * argv[]) {
llvm::BitstreamBlockInfo::BlockInfo info;
info.Name = "Hello, LLVM!";
std::cout << info.Name << std::endl;
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
If everything is done right, it should compile without any errors now.
P.S. I many times focused on specific directory hierarchy for a reason - when an XCode project is generated through CMake it often uses absolute paths, not relative and if you move your project to another directory, you will have to either adjust the paths in the build settings of required targets/projects or repeat the step with generating the LLVM Xcode project again.
Upvotes: 1