Import xcode scheme from command line - ios

Is any way to import an xcode scheme into a xcode project or xcode workspace from command line?
I want to automatize the archive process with a shell script and I want to verify if the scheme exist in the xcode project or xcode workspace, if the scheme doesn't exit import it from a .xcscheme file.

That is actually pretty easy to achieve. As you have figured out already, Xcode schemes are just files that reside in the xcodeproj container "file".
If you want to check if a scheme exists you just need to check if the .xcscheme file is present in either of the following locations: <your_project>.xcodeproj/xcuserdata/<your_user>.xcuserdatad/xcschemes/
<your_project>.xcodeproj/xcshareddata/xcschemes/
If not you just need to copy it there (preferably to the xcshareddata/xcschemes/ folder).
The following code should do the trick:
#!/bin/bash
# Set to your project and scheme
export PROJECT_FILE="MyProject.xcodeproj"
export SCHEME="abc.xcscheme"
# Generate path to shared schemes folder
export SCHEMES=$PROJECT_FILE/xcshareddata/xcschemes
if [ ! -f $SCHEMES/$SCHEME ]; then
# Create folder if necessary
mkdir -p $SCHEMES
# Copy scheme
cp $SCHEME $SCHEMES/$SCHEME
fi

Related

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

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.

iOS and FirebaseCrashlytics

I am trying to follow the instructions on Firebase Docs to upload missing required dSYMs. However I am stuck on running the uploader script.
In my build phases I have
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols -gsp${SRCROOT}/GoogleService-Info.plist -p ios ${SRCROOT}/appDsyms"
When I try building the iOS app with this, I get the error:
line 4: /path/to/Pods/FirebaseCrashlytics/upload-symbols -gsp/path/to/GoogleService-Info.plist -p ios /path/to/appDsyms: No such file or directory
Command PhaseScriptExecution failed with a nonzero exit code
When I try running the script from the terminal I get the error:
No Google App ID or Google Services file provided
I have verified that I have a Google Services file and am able to run my project using other firebase services that rely on it. I used to be able to upload Dysm files directly into the Firebase Console, but that changes on March 1.
Should this command be run as an XCode script or a command from the terminal? And, more importantly, does anyone understand how to resolve this issue?
As of May 2020:
After Fabrics shut down, many developers faced such issues because Fabric was automatically creating the script to upload dSYM files from Xcode and we never pay attention to it.
Now as Fabric is replaced with FirebaseCrashlytics, in order to achieve this automatic mechanism, you can create a new run script and paste this script there:
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp "${PROJECT_DIR}/GoogleService-Info.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
This script will get the dSYM files and upload them to firebase servers so that you can see the crashes.
For multiple Schemes:
If your project has multiple schemes, you can simply create multiple such scripts by changing the path to the Google Plist file.
NOTE: You can also manually upload the dSYM files using upload-symbols tool [Check here], but it's always better to automate the process wherever we can.
EDIT: July 2020:
When you see missing dSYM files for the crash in the Crashlytics dashboard, instead of getting the email for it, you can upload the dSYM file for the build as soon as you submit it for Apple review or for testing via Test Flight.
Missing dSYM is shown because when bitCode is enabled, the App Store Connect process the binary post uploading it and generates a new dSYM file.
You can find the dSYM file from the Activity section in the App Store Connect.
2020 FirebaseCrashlytics solution
You have two solutions :
1) From the command line
Go to your project folder and run :
./Pods/FirebaseCrashlytics/upload-symbols -gsp GoogleService-Info.plist -p ios <path_to_your_dsyms_zip>
You can get your dsym in Xcode organizer > right click on the archive > show in Finder -> Show content -> go to dsymm folder and compress it
2) From Xcode Build Phases
As described here (Firebase doc), you can add a Run Script phase in Xcode with this content :
"${PODS_ROOT}/FirebaseCrashlytics/run"
You also have to add these two input files under the run script :
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}
and
$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)
After hours of struggling with this problem finally solved it using this approach:
use this command in Terminal: /path/to/pods/directory/FirebaseCrashlytics/upload-symbols -gsp /path/to/GoogleService-Info.plist -p ios /path/to/dSYMs
Important thing is instead of /path/to/pods/directory you should enter the path to your pods folder in your application folder, and instead of /path/to you should enter the path to the GoogleService-Info.plist which is in your project folder too. And the last one is, instead of /path/to/dSYMs you should enter the path to your archive which has the format of .xcarchive.
For finding the .xcarchive path, you should first archive your application, then go to XCode -> Organizer and select your build, then right click on it and select "Show in finder" and then right click on your .xcarchive file and select "Show package contents". This is it, you can copy this path and paste it instead of /path/to/dSYMs and then hit enter to start uploading to Firebase Crashlytics.
Check out this link for more information:
Firebase Docs
While implementing FirebaseCrashlytics(Currently is in beta) for Crashlytics
Add new run script from Build Phases and add the following :
"${PODS_ROOT}/FirebaseCrashlytics/run"
In Input Files sections add
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}
and
$(SRCROOT)/path to/GoogleService-Info.plist
If you still get dSYM missing error then try to run from terminal
/path/to/pods/FirebaseCrashlytics/upload-symbols -gsp /path/to/GoogleService-Info.plist -p ios /path/to/dSYMs
For path to FirebaseCrashlytics and GoogleService-Info.plist drag and drop from the actual location
For dSYMs path will be ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}
To get that hit the command with your project .xcodeproj and target xcodebuild -project YourProject.xcodeproj -target YourTarget -showBuildSettings
and replace your specific path
Please take note of the following while implementing Crashlytics
1. Run application in release mode
2. While testing disconnect device from mac
3. Set correct GoogleService-Info.plist
4. If you rename it, make sure you set the correct file name whenever required.
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp
"${PROJECT_DIR}/additional_folder/GoogleService-Info.plist" -p ios
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
I replaced my GoogleService-Info.plist in additional folder and made directory changes in shell script. Probably you should do the same
For terminal command you better drag and drop necessary file in terminal then copy selected path
/path_to_pods/FirebaseCrashlytics/upload-symbols -gsp
/path_to_google_service/GoogleService-Info.plist -p iOS /path_to_dSYMs
Believe me, I spent one day but nothing worked,
Surprisingly few solutions are working for a few projects for my colleagues but I'm using Big Sur & Xcode 12.2 nothing worked for me.
Tried 1: Each step mentioned in firebase doc.
Tried 2: Tried to upload symbol from terminal by passing path_to_pod_firebasecrshlytics/uploadsybol -gsp path_to/GoogleService-Info.plist -p ios path_to/dSYMs
But, No luck,
Following trick works for me,
Step 1: make sure you are on the latest firebase crashlytics version for it, give a path to project & fire cmd pod update
I was using Firebase Crashlytics version 4.0.0-beta.1 but after pod update it is 8.2.0
Step 2: Go to build phase add a run script bellow compile bundle resources "${PODS_ROOT}/FirebaseCrashlytics/run"
Step 3: Add DYSM Script, "${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp "${PROJECT_DIR}/your_path/GoogleService-Info.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
Step 4: Build project;
Step 5: Run project & Stop or disconnect from storyboard
Step 6: Make crash 2-3 times & wait for 2-5 mins.
Cheers, All Set!
We can do:
PATH_TO_GOOGLE_PLISTS="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
To get a reference to the plist
Then use it:
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp "${PATH_TO_GOOGLE_PLISTS}" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
Your Google Services path seems to be off. Here is an example of what my build phase looks like, which is able to successfully upload dSYM's. I suggest following these instructions one more time https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports-new-sdk?platform=ios&authuser=0.
find /Users/okodysh/Library/Developer/Xcode/DerivedData/myApp-ftqksfkpdvisbtaozpqzzjiizpfk/Build/Products/Debug-iphonesimulator -name "myApp.app.dSYM" | xargs -I {} $PODS_ROOT/FirebaseCrashlytics/upload-symbols -gsp /Users/okodysh/Desktop/iOSApps/myApp/myApp/GoogleService-Info.plist -p ios {}
Finally, I've figured it out and wrote a shell script to handle all this for me
Feel free to to use it: https://github.com/cs4alhaider/firebase-upload-symbols
Hmm nothing worked for me but changing this:
"${PODS_ROOT}/FirebaseCrashlytics/run"
to this:
"${PODS_ROOT}/FirebaseCrashlytics/run" -gsp "${PROJECT_DIR}/project_main_dir/google-services-files/iOS-GoogleService-Info.plist"
EDIT:
for those of you trying to complie to iOS's Catalyst:
you don't have to download two different Google JSON files. You should use only one (cause you have only 1 target). IF you want to upload a mac version of your app, just go to App Store Connect and create a new release for OSX (in the same page of your app)
In my case none were working until I added this:
"${PODS_ROOT}/FirebaseCrashlytics/run" -gsp "${PROJECT_DIR}/intermediate_folders/GoogleService-Info.plist"
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp "${PROJECT_DIR}/intermediate_folders/GoogleService-Info.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
If I didn't add the path to the run command, the build phase would be running forever...
Check you FULL project folder path whether it contains space. I solve it by moving my project into another folder path that doesn't have space.

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.

Jenkins succeeds without producing IPA file

I have setup an mac mini slave on jenkins. The build process succeeds and my .app file and .dysm files etc are all created successfully.
I have Pack application and build .ipa? checked and defined a ipa filename pattern and an output directory.
When I run the build process, it actually creates the full path of my output directory, but at the end of the path, there is nothing in the final folder. No IPA. Again, the .app and other files are created successfully in the workspace build folder.
Also doesn't work if I leave the output directory blank or change it to other locations.
Jenkins succeeds with this message:
** BUILD SUCCEEDED **
Cleaning up previously generated .ipa files
Cleaning up previously generated .dSYM.zip files
Packaging IPA
Finished: SUCCESS
But alas, no IPA. Any ideas?
I was not able to solve the issue directly, so I am still interested in answers from someone who might have a more direct solution using the Xcode plug in with the pack and build ipa option.
Instead, I removed this option (unchecked it) and added another build phase for execute shell script.
I then added the script from this SO answer (modified for my use) and was able to export the ipa successfully.
/usr/bin/xcrun -sdk iphoneos PackageApplication -v "${RELEASE_BUILDDIR}/${APPLICATION_NAME}.app" -o "${BUILD_HISTORY_DIR}/${APPLICATION_NAME}.ipa" --sign "${DEVELOPER_NAME}" --embed "${PROVISONING_PROFILE}"
RELEASE_BUILDDIR and BUILD_HISTORY_DIR were changed to my own paths, and -sign and -embed were not needed because i'm using the same profile as the one that created the original .app file
I did have to use mkdir -p to make the path or it wouldn't succeed for me
So I was having the same problem and this is how I solved it:
In the Jenkins job, in the configure interface, under Advanced Xcode build options, I specified a Build output directory: ${WORKSPACE}/builds.
I also added a shell script to execute prior to the Xcode build phase:
mkdir -p "${WORKSPACE}/builds"
Now when I click the check box for "Pack application and build .ipa?", specify a .ipa filename pattern and use the output directory: ipa , the job runs, succeeds, and gives me a .ipa I can see in the Workspace at /builds/ipa/{name}.ipa
I hope this helps.
Had exactly the same problem as you.
Besides "Pack application and build .ipa", you also have to check "Unlock Keychain?" which can be found at "Code signing & OS X keychain options".
Furthermore, set the keychain path to:
${HOME}/Library/Keychains/login.keychain
The keychain password for me was the same as my user password. If you leave that field empty, you will see that your build freezes at a certain point. Inside the terminal where you started jenkins, you will see that it waits for you to type the password.
Hope it helped.

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