How to access my app’s derived data folder itself? - ios

I’m in the middle of moving my iOS app’s Firebase dependency from CocoaPods to Swift Package Manager.
Firebase’s Crashlytics requires a script to be executed while the app is building (using the Run Script build phase). Back in the CocoaPods days, I used to call the script the way documented by Google: "${PODS_ROOT}/FirebaseCrashlytics/run".
After I’ve switched to SPM, Firebase files are no longer in ${PODS_ROOT}, and there’s no such variable available at all. I know the file I need is now located in the DerivedData folder, specifically it’s at ~/Library/Developer/Xcode/DerivedData/MyApp-abcde/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run. The problem is, the MyApp-abcde folder is not easily referenced.
A Medium post I found suggested using the ${BUILD_DIR} build variable (in regard to the example above, it would resolve to <…>/MyApp-abcde/Build/Products), and calling the dirname command to move up the directory hierarchy.
This hack worked only for running the app. When I tried to archive it, ${BUILD_DIR} resolved to another path, namely <…>/MyApp-abcde/Build/Intermediates.noindex/ArchiveIntermediates/MyApp/BuildProductsPath. I tried a few more build variables, such as ${SYMROOT}, in place of ${BUILD_DIR}, but they also produce different paths depending on the operation.
So, here comes the question…
Is there a way to reliably reference my app’s derived data folder’s root (i.e. ~/Library/Developer/Xcode/DerivedData/MyApp-abcde) using Xcode’s build variables?

You were close. This should work:
${BUILD_DIR%Build/*}SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run

Crashlytics run script
To build&run from Xcode:
${BUILD_DIR%Build/*}SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run
To archive for distribution from Xcode Server at CI/CD:
${XCS_DERIVED_DATA_DIR%/*}/Dependencies/checkouts/firebase-ios-sdk/Crashlytics/run
To work for both at the same time:
if [[ "${XCS_DERIVED_DATA_DIR}" == "" ]]; then
${BUILD_DIR%Build/*}SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run
else
${XCS_DERIVED_DATA_DIR%/*}/Dependencies/checkouts/firebase-ios-sdk/Crashlytics/run
fi

I'm reworking my answer as I realized I haven't answered your question..
I have a run script that copies the build targets into a new folder. This works for both Build and Archive. Note that you need the -L on copy to follow the symlink
# Copy the built framework into a sibling to the project folder
if [[ "$CONFIGURATION" == "Debug" ]]; then
mkdir -p "$PROJECT_DIR/../../iwins-ios-sdk/debug"
rm -rf "$PROJECT_DIR/../../iwins-ios-sdk/debug/IWiNS_SDK.framework"
cp -LR "$BUILD_DIR/Debug-iphoneos/IWiNS_SDK.framework" "$PROJECT_DIR/../../iwins-ios-sdk/debug"
else
mkdir -p "$PROJECT_DIR/../../iwins-ios-sdk/release"
rm -rf "$PROJECT_DIR/../../iwins-ios-sdk/release/IWiNS_SDK.framework"
cp -LR "$BUILD_DIR/Release-iphoneos/IWiNS_SDK.framework" "$PROJECT_DIR/../../iwins-ios-sdk/release"
fi
Also, you can find all of the build envvars here:
https://gist.github.com/gdavis/6670468
So, it appears that $BUILD_DIR is different for build/release, but by copying the files to a known location at build time, you'll know where to find them.

Related

Cyclic dependency error J2OBJC archive build?

I am having cyclic dependency issues with my Ionic/iOS project using J2OBJC as a internal Xcode settings built in.
I've set my build rules and settings in my ionic project as stated in the J2OBJC documentations. I have added the Library, User Header and framework search paths as required (firstly only app target, and also tried it with both app target and workspace) and created user defined $J2OBJC_HOME and (this probably isn't part of the requirements) I added a $PROJECT_DIR too. I also added the java build rule as such
"if [ ! -f "${J2OBJC_HOME}/j2objc" ]; then echo "J2OBJC_HOME not
correctly defined in Settings.xcconfig, currently set to
'${J2OBJC_HOME}'"; exit 1; fi; "${J2OBJC_HOME}/j2objc" -d
${DERIVED_FILE_DIR} -sourcepath "${PROJECT_DIR}/App"
--no-package-directories -use-arc --prefix Flowers=JOE -g ${INPUT_FILE_PATH};"
I have build both simulator and iPhone and ran them on both and they seem to be working. However, when I go to archive (so I can create the .ipa) for the project. I get a cyclic dependency error. I have tried numerous things, such as, fresh start of project, using different URLs for the search paths, using recursive and non-recursive, checked the documentations a few times, looked at forums and still have no solution. Any ideas would be greatly appreciated!
Xcode can no longer handle the same header search path in Header Search Paths and User Header Search Paths. You probably have "$(J2OBJC_HOME)/include" in both of these. So remove it from Header Search Paths and leave it in User Header Search Paths.
I've had this cyclic dependency issue for a good month and a half. I've tried almost everything.
I've looked everywhere.. and got 2 replies from Tom Ball. When it comes to a big project converted from java to Objc using the tool, xcode won't recognize the sub-directories made. I figured out the best fix is to create your own script using bash. I did some research and figured out how to recursively find all java files and apply the j2objc script to them and it will output all of your Objc files in the same directory layout. (Keeping a nice layout for you instead of the messy-everything-in-one folder like j2objc script rule on Xcode works).
My build.sh file is as follows, feel free to copy it-
!/bin/bash
Clean out Objective C folder
rm -rf /ObjC_Output
Move into Java Folder
cd Java
Run j2objc and output everything into ObjC
j2objc -d ../ObjC_Output $(find . -name "*.java")
Jump out of Java folder
cd ..
Delete previous org
rm -rf /Users/me/Projects/project-name/mobile-app-front-end/app/ios/app/app/org
cd to ObjC_Output
Copy new ObjC Project files over to the XCode project..
cp -R ./ObjC_Output/* /Users/me/Projects/project-name/mobile-app-front-end/app/ios/app/app/org
I automatically copy all my files back to my Xcode project.. feel free to do it anyway you want. Also don't forget to add j2objc to your path. and run your script as such ./builder.sh in terminal..
Also for Xcode to recognize your objC project folder you must add the top directory of your Xcode project and make it recursive.
In this case, you don't need to include the j2objc script in Xcode.. Just make sure you include all the default Linker paths, Library search paths and User Header search path as usual (shown in the j2objc documentation).
I've had to pull my hair out for this solution.. So feel free to ask for help if needed.

Xcode framework Versions/Current/Headers/ error

I am confused about adding a third party framework into Xcode project.
Here are the steps:
1) I copy the framework into project subfolder called "Frameworks". So framework is added to repository.
Everything works fine, app compiles, here is the screenshot:
2) I push the changes into repository (I use mercurial)
3) If I pull the changes on other machines or just switch between branches then project doesn't recognize this framework any more.
Here is the screenshot:
The question is why it happens and how to solve this problem? I there any specific way to add a framework in this case?
Thanks in advance!
I had this problem today and it was because the 3rd party framework was delivered as a .zip file and symbolic links inside of the framework were lost at some point.
Here is Apple's documentation on the anatomy of a framework:
https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
The document explains that multiple framework versions might exist inside of the framework bundle, so the bundle contains some symlinks pointing to the current version of some directories.
MyFramework.framework/
MyFramework -> Versions/Current/MyFramework
Resources -> Versions/Current/Resources
Versions/
A/
MyFramework
Resources/
English.lproj/
InfoPlist.strings
Info.plist
B/
MyFramework
Resources/
English.lproj/
InfoPlist.strings
Info.plist
Current -> B
The problem you have is exactly like mine. Confirm that the three symlinks that Apple documented (MyFramework, Resources, Versions/Current) are not symlinks, but are tiny files. Then delete the tiny files and replace them with symlinks to the current framework version. For example, if the current version is 'A':
cd NewRelicAgent.framework
rm Headers
rm NewRelicAgent
rm Versions/Current
ln -s Versions/Current Versions/A
ln -s Headers Versions/A/Headers
ln -s NewRelicAgent Versions/A/NewRelicAgent
If there are multiple versions in your framework, then the text in Versions/Current might contain the current version. This would be an artifact where the compression tool is storing the symlink as text instead of an actual link.
We are facing a similar kind of issue. The result we get is the same, but the setup is different.
Posting our findings here, incase someone else finds it useful.
Our apps high level setup
React Native app
Git repo
npm for package management (npm install)
lots of .xcodeproj under one .workspace
one of those .xcodeproj have a .framework that shows the issue mentioned above
Our findings (so far)
it's not the actually git checkout thats causing this stripping out of symlinks in .framework behaviour
it's actually how our package manager (npm) installs the said sub repo. It might be npm is doing something to strip out symlinks in install or post install phase.
If we download or git checkout the said sub repo manually, framework symlinks are in tact.
Our initial approach to fix the issue
Include a symlinks intact version of the framework (Valid.framework) in the main project.
use npm install to install the packages as normal (this would strip out the symlinks in the framework Broken.framework file thats in the sub repo)
have a post install script copy and/or override Broken.framework with Valid.framework
cp -Rf <path_to_working_framework>/Valid.framework/* node_modules/<path_to_broken_framework>/Broken.framework/

CMake: How can I copy a directory into the ios application bundle

I have an ios project that builds with CMake 3.1.1, but I am stuck on the last problem - getting CMake to copy the asset directory into the right location for the app.
I have this, which works when running the app in the simulator. However, it does not work, and in fact causes a build failure when archiving because the destination directory it constructs does not exist.
get_target_property(APPLICATION_BIN ${APPLICATION_NAME} LOCATION)
get_dotapp_dir(${APPLICATION_BIN} APPLICATION_APP_LOCATION)
add_custom_command(TARGET ${PROJECT_NAME}
POST_BUILD
COMMAND cp -r "${CMAKE_SOURCE_DIR}/assets" "${APPLICATION_APP_LOCATION}")
The directory I am copying has many subdirectories and I need them all preserved. Essentially I want to accomplish with cmake the same thing that happens when I drag the "assets" folder into the ios project underneath "Resources".
Use ${CMAKE_COMMAND} -E copy_directory instead of calling cp.
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LOCATION_FOR_THE_APP} DEPENDS "${PROJECT_SOURCE_DIR}/assets" COMMAND -E copy_directory ${PROJECT_SOURCE_DIR}/assets ${CMAKE_CURRENT_BINARY_DIR}/${LOCATION_FOR_THE_APP})
add_custom_target(${CMAKE_CURRENT_BINARY_DIR}/${LOCATION_FOR_THE_APP} DEPENDS
My solution to this is similar to this answer. The goal of my project was to get googletests running on a cross-platform library, in which I had to wrap googletests into an XCTest Bundle. The build tool we are converting to is CMake for obvious reasons.
I ended up doing this as a pre-build step:
add_custom_command(TARGET ExampleTestTarget PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_LIST_DIR}/resources $<TARGET_FILE_DIR:ExampleTestTarget>
)
Essentially, at prebuild, CMake will copy everything in the resources folder into the root of the target's bundle. For some reason (probably a CMake thing), XCode picked these resource files up with no issue, so I didn't have to set the files to be RESOURCE files explicitly.
If you're using MacOS Bundles/Packages/etc., you'll have to copy-paste these files into $<TARGET_FILE_DIR:ExampleTestTarget>/Resources instead since this is the default resource location for MacOS xcodeproj's generated by CMake. (There are so many loopholes to get XCTests working in CMake that I advise everyone doing cross-platform gtests to create an executable instead for MacOS so you won't get into any trouble like this anyway).
Note, you won't see the files in XCode unless you add the files in target_sources(), but who cares... it works anyway! Who develops C++ exclusively in iOS? ....

When added an embedded framework in an Xcode project, how do you differentiate between Debug and Release?

When I add a framework to my Xcode project, to be embedded in my app bundle, how do I make two different options for whether it's Debug or Release (I have two versions of the framework, one compiled for release and one for debug).
This is what I'm referring to:
As you see, with that configuration, it'll just copy the one on CEF/Debug regardless of whether it's in being compiled in Release or Debug mode.
Ideally I want something like you have for setting:
You can manage frameworks to embed with your custom Run Script in Build Phases:
#!/bin/bash
# Your frameworks to embed
FRAMEWORK="Debug.framework"
if [ $CONFIGURATION == "Release" ]; then
FRAMEWORK="Release.framework"
fi
# Destination to copy inside the app's frameworks folder
NAME=$(basename $FRAMEWORK)
DESTINATION=${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$NAME
# Don't copy if it's already copied
if [ ! -d $DESTINATION ]; then
# Copy the framework to the app's frameworks folder
cp -r $FRAMEWORK $DESTINATION
# Sign (if needed)
codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements $DESTINATION
fi
Note that you can bring in different shared libraries (such as frameworks) by setting the environment var DYLD_IMAGE_SUFFIX for your app. So if inside your (one) framwork you had CEF.framework/Versions/Curent/CEF and CEF.framework/Versions/Curent/CEF_debug and you set the env var DYLD_IMAGE_SUFFIX=_debug it will load the second for that run.
You don't need to. Embedded frameworks will build according to the build of the consumer app.
You can test it by adding a build schema on the app (like Debug2) and then compiling. You will receive an error from the embedded because it tried to compile it with the Debug2 schema, which is not available in the embedded framework. Now duplicate some schema and call it Debug2. It will build.

Xcode Project File Reference Paths for Frameworks for Different Build Configurations

I am having a problem with the "Link Binary with Libraries" and "Copy Files" paths in my workspace when I try to build for different configurations - "Debug" and "Release" Those paths are stored in the pbxproj file of the xcodeproj package and do not vary with build configuration.
I have a workspace, Application.xcworkspace, that builds two frameworks and an executable that uses those frameworks. My Derived Data Location is set to "Workspace-relative" Here is roughly how they are laid out:
Framework_1 - Built and output to the Derived Data path.
Framework_2 - Depends on Framework_1, which is included in the Frameworks area of the project and specified in the "Link Binary with Libraries" Build Phase, and output to Derived Data path.
Application - Depends on Framework_1 and Framework_2, which are included in the Frameworks area of the project and specified in the "Link Binary with Libraries" Build Phase. Output directory is again the Derived Data path.
The paths in the project file for the referenced frameworks are the Derived Data path - they are like
Workspace Path/DerivedData/Workspace Name/Build/Products/Debug/Framework_1.framework
That path is the same regardless of what I build for - running (debug), testing (debug) or profiling (release).
My problem is when I build for profiling (release), the paths to the frameworks are still the debug paths, and thus the release version of the app links to debug frameworks and the debug frameworks get bundled into app->Contents->Frameworks (via a Copy Files build phase). Even worse, if I do a Clean, or delete the Derived Data directory, and try to build for profiling (release), it will not build or run because the referenced files (debug) do not exist.
I know one way to do make this work is to have two workspaces - one for debug and the other for release. That would be a big hassle.
Would another way to do it be to have another target?
What is the correct way to resolve this issue? Any help appreciated.
I think I figured out a fairly clean way to get this problem solved.
My first stab at it was two targets - one set up to link to and copy the debug framework, the other to do the same with the release framework. Very much a hack and the output from the second target had a different name, and each of the targets still had the original problem. However, I could get the same app functionality with a separate configuration build of the framework (Debug with Debug, Release with Release).
I then stepped back to think about the problem, and realized I just needed a single directory to house the link-to and copy-file version of the framework, but I needed to populate it differently depending on configuration. That made me think of scripts, and that worked out. I ended up adding a pre-action Run Script Build Phase to clean away any old copies of the framework and copy in the correct one. Here is the script for a framework called Simple.framework where the DerivedData location is Workspace-relative:
rm -R "${SRCROOT}/../Frameworks/Simple.framework"
cp -R "${SRCROOT}/../DerivedData/TestTarget/Build/Products/${CONFIGURATION}/Simple.framework" "${SRCROOT}/../Frameworks/Simple.framework"
So, for my project outlined above, I would put a phase like that for each of the frameworks in the app Build Phases.
I made a script via your idea.
You even don't need to change DerivedData location( set to Default Location ).
Edit scheme... > Build (expand) > Post-actions
Change 'Provide build settings from 'None' to 'SCHEME_NAME'
Paste command below
if [ -n "${SRCROOT}" ] && [ -n "${FULL_PRODUCT_NAME}" ] && [ -d "${SRCROOT}/../Frameworks/${FULL_PRODUCT_NAME}" ]; then rm -rf "${SRCROOT}/../Frameworks/${FULL_PRODUCT_NAME}"; fi
if [ -n "${BUILT_PRODUCTS_DIR}" ] && [ -n "${SRCROOT}" ] && [ -n "${FULL_PRODUCT_NAME}" ]; then cp -aR "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}" "${SRCROOT}/../Frameworks/"; fi
close & enjoy

Resources