iOS Static Library + CocoaPods and the duplicate symbols error - ios

I'm developing an Static Library / iOS Framework (Jeff Verkoeyen way) and I've added CocoaPods for manage dependencies (this is the big difference with other questions about duplicate symbols).
I've been struggling with the "duplicate symbols" problem when a client that use CocoaPods use my static library / framework and a 3rd party library that I'm using too.
I've been reading answers talking about not including 3rd party libraries into a custom static library but I want to do it this way, so that is not an option.
I've found this article (thank you Ryan Arana) that talks about this problem and "the best" solution and I've had to adapt it to my CocoaPods driven Static Library:
1. Create an empty NamespacedDependencies.h in the root of workspace and add it to main project.
2. Add this to the prefix header of the main project (before any include of pods):
#import "NamespacedDependencies.h"
3. Add this hook to Podfile:
post_install do | installer |
environment_header = installer.config.project_pods_root + 'Pods-environment.h'
text = "#import \"NamespacedDependencies.h\"\n\n" + environment_header.read
environment_header.open('w') { |file| file.write(text) }
dummy_class = installer.config.project_pods_root + 'Pods-dummy.m'
text = "#import \"NamespacedDependencies.h\"\n\n" + dummy_class.read
dummy_class.open('w') { |file| file.write(text) }
end
4. Copy Ryan Arana's script to the root and edit the variables.
5. Add a "Run Script Build Phase" to Pods Target with:
./generate_namespace_header.sh
6. Build workspace. At this point I have my NamespacedDependencies.h macros.
7. Comment or remove the "Run Script Build Phase" that It has been created before.
8. Build again.
Problems:
It works but it has problems:
1. Require manual steps. When I add/remove a pod to/from Podfile (and run pod install) I've to clean workspace (iphoneos and iphonesimulator in my case), left NamespacedDependencies.h empty, add the "Run Script Build Phase" (its removed by cocoapods) and start again.
2. I don't know if the post_install hook is ok, I just copied code from somewhere and change it but I'm sure that must be a better way to do this. I can't find documentation about it.
Edited:
I'm using CocoaPods 0.29.0, Xcode 5 and iOS 7
Ryan Arana's script is a slightly modification of the Nimbus' script. The "original" featherless's answer help me a lot. (See Ryan Arana's article)
When I run "pod install", the "Run Script Build Phase" and the NamespacedDependencies.h includes are deleted. This is why I've to create that phase every time I add/remove a pod, and why I've added a hook to add includes.
I add the import to Pods-environment.h because it's imported in the prefix header of each pod. But that configuration could change in future versions so I would like to find another way to do this. I don't know how to add it to each pch anyway. "General" Pod target has no pch so I've to add it to Pods-dummy.m directly.
If I don't comment or remove the "Run Script Build Phase" when NamespacedDependencies.h macros and libPods.a are already created, macros is recreated for the prefixed symbols and everything goes wrong.

Related

Can't assign xcassets to the main target on Xcode 11

On Xcode 11 my .xcassets, that don't cause any issue on Xcode 10, cause the compilation to fail with the following message:
error: Multiple commands produce '/Users/user/Library/Developer/Xcode/DerivedData/project-enjiypsgxtcdbnaripixgtnjlagx/Build/Products/Debug-iphonesimulator/project.app/Assets.car':
1) Target 'project' (project 'project') has compile command with input '/Users/user/sandbox/project/Resources/buttons.xcassets'
2) That command depends on command in Target 'project' (project 'project'): script phase “[CP] Copy Pods Resources”
The only way I'm able to compile the project is by removing the .xcassets from the target with the obvious downside of having them not available on the build.
PS: This is happening for 2 ObjC projects.
Add below line at the top of your pod file, right after platform :ios
install! 'cocoapods', :disable_input_output_paths => true
Reference
The answer above is only partially correct: yes, this happens because the pod phase produces Assets.car as an output file, which coincides with the output name for your xcassets - hence Multiple commands produce... error: one command being the script phase [CP] Copy Pods Resources and another - your own project's compile command.
But removing Assets.car from output files will cause the build system to fail to see that the script processes the file, thus it skips it (read this for more details). So what we can do is add Assets.car to the input files - this will create a dependency for the script, telling the build system to wait for the file before executing the script.
NB: like the solution above, you will have to do this every time after updating your pods, since [CP] Copy Pods Resources is generated every time you run pod install or pod update
To be sure that all build steps are performed in the correct order, yet as many steps in parallel as possible, Xcode needs to know for every build step which input files it depends on and which output files it will generate.
E.g. if step A depends on an input file that is the output file of step B, step B surely has to run before step A. If neither step requires the output file of the other step, they can both run in parallel.
While several steps can surely depend on the same input file, it cannot be the case that multiple steps produce the same output file as in that case, whichever step runs last would overwrite the output file of whichever step runs first and this means the resulting output file content would be unpredictable and that is an error in a build process that shall created predictable results!
In your case the problem is that your Copy Resources build phase contains one (or more) .xcassets bundle(s). Xcode won't just copy these bundles, instead it will combine them, convert and optimize their content, and create a file called Assets.car that contains the content of all the assets catalogs in your project.
Yet if you integrate a Pod and this Pod also has an asset catalog, exactly the same thing will happen and a second Assets.car file is created and now Xcode has a problem: Two build steps both say they create an Assets.car file. Which one shall win? Which of these two files shall end up in the final application? And what about other steps that depend on this file? After which of the build steps do they have to run? This is a serious problem that Xcode cannot resolve.
There are currently two possibly solutions:
Use frameworks. When you use frameworks instead of static libraries, the resources of Pods don't end up in the resources folder of your main application but in the resources folder of the built framework. All you need to do is to add use_frameworks! to your Podfile, either top level or just for a specific target. The downside is that your overall app size will grow a bit and app start time will increase a bit.
Have the Pod-author fix the Pod. Instead of resources s/he shall use resources_bundle as the Podspec documentation also strongly recommends. Using resources actually has other disadvantages (resource files are not optimized, name conflicts can arise between different Pods), whereas using a resource bundle is safe, regardless if Pods are embedded as static libraries or as dynamic frameworks. Note, though, that it is not enough to just alter the Podspec, all code that loads bundle resources must be adopted to load them from the correct bundle (the name will be known as when using resources_bundle in a Podspec file, you also must give the bundle(s) a name).
There is an on-going bug report for this issue, you can find it here:
https://github.com/CocoaPods/CocoaPods/issues/8122
Yet the problem currently is, that every possible solution other than the two mentioned above would basically be an ugly hack and the Cocoapods developers are not happy implementing ugly hacks. The best solution, as hard as it seems, is probably to not fix that problem at all but to stop supporting resources in Podspec files and make resources_bundle mandatory for Pods that contain own resources.
In your library project, such as "LADetailPage", open Targets' "Build Phases", create a predefined phase named "Copy Files", then drag your *.xcassets to the new phase.
Then the library won't make Assets.car from *.xcassets files when building, instead, cocoapods copies the *.xcassets to your main project which imports the lib, and the main project builds all *.xcassets into a single Assets.car file.
Please check your dependencies if there is some pods use 'resources'(not 'resources_bundle') to link its own xcassets in podspec.
This way has beed deprecated, because its output file name is 'Assets.car'. It is same with your project xcassets' compile output name.
Xcode->File->Workspace settings->Building System-> Legacy Build System
example

Xcode SwiftGen: build fails in project with multiple targets

How should I configure SwiftGen when using Cocoapods and multiple targets?
I have a project with two targets (MyProject and MyProject Dev) which has SwiftGen integrated with Cocoapods.
I can build the first target with no problems whatsoever. However the 'Dev' target always fails. The script phase is the last step in Build Phases, however running it earlier or later seems to make no difference.
On failure, I've observed the following:
The generated .swift file has an import statement, 'import MyProject' which shouldn't be there
'No type named [ClassName] in module [MyProject]' OR 'No such module [MyProject]'
SwiftGen version I'm using is 5.2.1
Xcode 9.2, targeting iOS 10.0
SwiftGen from version 5 onwards requires a configuration file, so I've set one up as follows:
output_paths: Sources/Generated
storyboards:
- paths: MyProject/Storyboards/Base.lproj
templateName: swift4
output: MyProject/Storyboards/Storyboards.swift
params:
ignoreTargetModule: true
So far I've looked up documentation and the following issue:
https://github.com/SwiftGen/SwiftGen/issues/273
However the difference is that I'm using a different type of SG installation and my project fails to build (rather than simply not generating any resources)
Cleaning project, build folder and deleting derived data had no effect. I'm assuming that I may have missed something in configuration setup, but I can't see what that would be.
First question on here, so apologies if I missed something, will be happy to edit.
I had the same issue with generating Storyboard file. Try to modify your config file:
params:
ignoreTargetModule: true
module: <YOUR_PROJECT_NAME>
Hope it will help.
Hesitant to call this a solution, it's more of a workaround, but the following approach has at least allowed the projects to be built and run.
Remove the SwiftGen script phase from the 'Dev' target
Build the project using the main scheme that includes SwiftGen to generate all the necessary objects and constructs
Switch to Dev scheme and run the project.
The Strings.swift file created by SwiftGen is linked to both targets, so even after running the script in one scheme the resources within that file will be available to both regular and Dev targets.
Hope this helps someone.

What's the equivalent of "development pods" under Carthage?

The teams developing frameworks for our iOS app are migrating from Cocoapods to Carthage.
Under Cocoapods, I could set up dependencies as "development pods". For example, instead of having the main app download a specific version of an xyzzy dependency, I could set up xyzzy as a development pod and point it to my local directory where I had checked out xyzzy from its Git repo. While I was working in the main app's project, any edits I'd do to xyzzy's files would be made in that directory. This let me build and test changes immediately, and when I was ready to check them in, Git would find them in the xyzzy project's directory.
Under Carthage I haven't found a way to do this. I see http://allocinit.io/ios/debugging-carthage-dependencies/ which explains how to create symbolic links so that I can see the dependency source files to make debugging easier, but any edits I make to them are under the main application's Carthage/Builds directory.
How do I set up the equivalent of development pods under Carthage?
I believe Carthage doesn't have something similar to "development pods" yet.
But you could simulate "development pods" just following these steps:
Steps:
Add the .xcodeproj to your workspace
Remove all the dependencies you have in your project of the framework you added in step 1. (probably you may need to remove it from Build Phases -> Run Script -> Input Files too )
Go to General tab of the target you want to run, add the framework under Linked Frameworks and Libraries (it is going to take the one added from the .xcoproj)
(optional) you may need to run carthage bootstrap in the framework's repo you want to add locally.
That's it.
After that you will be able to run your project and update framework's code in the same workspace.
This works just as well as development pods for me, as of Xcode 8.3.3 and Carthage 0.24.0:
In app path, rm -rf Carthage
Point at the appropriate branch or tag in Cartfile
carthage update --use-submodules (generates .gitmodules and clones repo into Carthage/Checkouts)
In Xcode under project -> Build Phases -> Run Script, comment out the line that ends with carthage update --cache-builds if present.
Change to the General tab and remove the lib from Embedded Binaries
Right-click project, Add Files to app..., add lib from Carthage/Checkouts
Under project -> General, re-add the library, choosing the one you added in the previous step.
App should now build with the local lib. Make sure that your .gitignore has Carthage/{Build,Checkouts} and .gitmodules.
This answer is a summary of a successful implementation of the solution introduced here.
A cleaner solution is using local paths for dependencies in Cartfile.
Environment
Xcode 10.1
macOS 10.13.6
Step 1. Symbolic linking
1.1 Change $(SRCROOT_MAIN)/Carthage/Checkouts/$(DEVELOPING_FRAMEWORK_NAME) directory to a symbolic link pointing to source root directory of your developing framework $(SRCROOT_DEVELOPING_FRAMEWORK), where $(SRCROOT_MAIN) is source root directory of your main app. Backup existing directories before this change.
This linking enables version-controlled changes in your developing framework.
Syntax when using ln utility,
$ ln -s "$SRCROOT_DEVELOPING_FRAMEWORK" "$SRCROOT_MAIN/Carthage/Checkouts/$DEVELOPING_FRAMEWORK_NAME"
1.2 Change $(SRCROOT_DEVELOPING_FRAMEWORK)/Carthage/Build directory in your framework to a symbolic link pointing to $(SRCROOT_MAIN)/Carthage/Build directory. Backup existing directories before this change.
This linking enables access to all frameworks built by Carthage from both your developing framework and your main app.
Syntax when using ln utility,
$ ln -s "$SRCROOT_MAIN/Carthage/Build" "$SRCROOT_DEVELOPING_FRAMEWORK/Carthage/Build"
Step 2. Framework Replacement

2.1 Remove your developing framework in Xcode > YOUR_MAIN_APP > General > Linked Frameworks and Libraries (that is, the one located in $(SRCROOT_MAIN)/Carthage/Build/iOS).
2.2 Add $(DEVELOPING_FRAMEWORK_NAME).xcodeproj (found in directory pointed by $(SRCROOT_MAIN)/Carthage/Checkouts/$(DEVELOPING_FRAMEWORK_NAME) symbolic link) into your main app
2.3 Build the developing framework product for device and simulator
2.4 Add the new developing framework auto-detected by Xcode in Xcode > YOUR_MAIN_APP > General > Linked Frameworks and Libraries.
2.5 Add $(DEVELOPING_FRAMEWORK_NAME).framework as a target dependency by adding $(DEVELOPING_FRAMEWORK_NAME).framework in Xcode > YOUR_MAIN_APP > Build Phases > Target Dependencies.
2.6 Copy $(BUILT_PRODUCTS_DIR)/$(DEVELOPING_FRAMEWORK_NAME).framework to $(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/$(DEVELOPING_FRAMEWORK_NAME).framework by adding a new input file $(BUILT_PRODUCTS_DIR)/$(DEVELOPING_FRAMEWORK_NAME).framework and a new output file $(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/$(DEVELOPING_FRAMEWORK_NAME).framework in Xcode > YOUR_MAIN_APP > Build Phases > Run Script of Carthage Embed Framework.
Reference
Debugging Carthage Dependencies
https://allocinit.io/ios/debugging-carthage-dependencies/
Build Setting Reference
https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html

After importing Objective C Framework into my Swift project, my project does not recognise my header file in my Bridging file

I've got to import a Framework into my Swift project that is written in Objective C. So I've created a Bridging file and after I've added the Header files to it and compiled, my project says it cannot find them. The image below shows my Framework and you can clearly see AWController.h but it throws an error in my Bridging file when building.
The Bridging file shows in my Settings so what am I doing wrong or what have I missed? I've set my Defines Module to Yes in Build Settings and my Module Name has no spaces.
I would review the setup for AWS at their website. They even have a section that details using with Swift iOS9.
Among other things, if you are using the Frameworks manual integration, verify that step 4 is complete.
Under the Build Phases tab in your Target, click the + button on the
top left and then select New Run Script Phase. Then setup the build
phase as follows. Make sure this phase is below the Embed Frameworks
phase.:
Shell /bin/sh
bash
"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSCore.framework/strip-frameworks.sh"
Show environment variables in build log: Checked Run script only when
installing: Not checked
Input Files: Empty Output Files: Empty
You might want to install and use Carthage to make this easier. Head over to the Homebrew website then after installing homebrew you'd run brew install carthage from the command line. After that go back to the link above and follow the Carthage instructions.
Secondly, I would take another walkthrough of the Apple Documentation on Working with the bridging header files to see if any of the edge cases apply and just as a sanity check.
Third, if that doesn't work, I'd just try creating an empty Swift project and walk through the steps again using one of the techniques above. There might be something wrong with one or more settings in your project files or the file itself might be corrupt.
BTW - The product bundle identifier should probably be following the convention of com.yourdomain.YourProductName.
You should not use bridge header. Just use import AWSDK in the swift file where you use it.

Static Library public headers are not copied to Build/Products folder

I have 2 projects, which build one static library each, as well as another project which uses both static libraries.
One of the two static libraries builds fine. The other one does not and the reason for that is that its public headers are not copied to the build folder before the custom shell script runs.
Here is a demonstration of what happens:
SampleA (the one that works)
Compile MyClass1.cpp ...
Compile MyClass2.cpp ...
Libtool /Users/user/Library/Developer/XCode/DerivedData/.....
Libtool /Users/user/Library/Developer/XCode/DerivedData/.....
Libtool /Users/user/Library/Developer/XCode/DerivedData/.....
Create universal binary libSampleA.a ...
Copy SampleA.h ...in /Users/user/....
Copy MyClass1.h ...in /Users/user/....
Copy MyClass2.h ...in /Users/user/....
Run custom shell script 'Prepare Framework'
Stripping libSampleA.a
--- Build complete
SampleB (the one that does NOT work)
Compile OtherClass1.cpp ...
Compile OtherClass2.cpp ...
Libtool /Users/user/Library/Developer/XCode/DerivedData/.....
Libtool /Users/user/Library/Developer/XCode/DerivedData/.....
Libtool /Users/user/Library/Developer/XCode/DerivedData/.....
Create universal binary libSampleB.a ...
// >>>>> NO HEADER FILES ARE COPIED <<<<<<
Run custom shell script 'Prepare Framework'
--- Build fails (header folder does not exist)
My current setup is a workspace that includes all 3 projects, but I used to have 1 project with one nested project for each static library as well. The issue I have remains the same in both setups.
More info
I have tried virtually every instruction, tip and advice I found online for making this work.
The settings on both projects, as far as I can tell, are identical. Comparing the two project files wasn't very effective. I also haven't compared all the settings of both projects one by one to make sure they are the same (yet).
The problem is the same in both Debug and Archive builds (I know that archiving behaves differently)
I have added "$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/include" and/or "$(PROJECT_TEMP_DIR)/../UninstalledProducts/include"
I have added the headers to the "Build Phases > Copy Headers > Public" section - didn't work
I have deleted the "Build Phases > Copy Headers" phase completely, as Apple suggest and added the headers to the Copy Files phase - didn't work
I have added a new group in the static lib project and added all its headers, setting no target to "Add to Targets" - didn't work
I have quit XCode and tried again, or even restart the mac - didn't work
One difference between the two libraries is that SampleB (the one that does not build) includes various headers from SampleA. There are no compiler or linker errors, just the error at the end of the build that I describe.
Sources
Some of the sources I have used for the creation of the library, as well as for trying to solve the issue:
https://developer.apple.com/library/ios/technotes/iOSStaticLibraries/Articles/creating.html
Xcode 4 can't locate public header files from static library dependency
Compile, Build or Archive problems with Xcode 4 (and dependencies)
http://www.blog.montgomerie.net/easy-xcode-static-library-subprojects-and-submodules
So, do you know what triggers the headers to be copied or not be copied, during the build?
I would suspect that even if one header file is added to the Copy Headers phase or the Copy Files phase, at least this should be copied. But as it turns out, this is not what happens, at least in my case.
Following Apple's instructions, and just creating a CocoaTouch static library project from the template:
Ensure you have placed your custom "Run Script" phase after the "Copy Files" phase:
Then, when building, the transcript will look as this:
In my setup I have a parent project with a nested project, which emits a static library.
I tried with a Headers phase, I couldn't get Xcode to copy the headers in the right spot when archiving. Plus the headers and the static library were copied into the archive, which you don't want. So I ended up using a Copy Files phase and setting Skip Install to YES while pointing the Header Search Paths in tha parent project to BUILT_PRODUCTS_DIR.

Resources