how to make xcode run a script before dependencies? - ios

Scenario:
I have TargetA, which is an iOS application. This app uses a static library, compiled by TargetB.
Obviously, TargetB is a dependency of TargetA, and TargetB is always built before TargetA. So far so good.
Now, I want a script to run TargetA is built, but even before TargetB.
What I tried:
Adding a "setup" aggregate/external-build target, and making it a dependency of TargetA. It half works: Xcode runs setup and TargetB at the same time, so TargetB may get built too soon. Not good enough.
The same, but specifying in the scheme not to build in parallel. This works, but is a waste, since there are other targets that can be built in parallel.
Run the script in a pre-action build step inside the scheme. This works, but seems wrong (why? output from that step doesn't go into the build log; so perhaps I'm not meant to use it this way?).
What I don't want to do:
Add the script as a dependency of TargetB. This would work; however, TargetB actually comes from another sub-project, and in the context of building it, parameters to the script are not yet known (mainly the root directory of TargetA's project).
Am I missing anything, or do I have to settle for #2 or #3 above?
EDIT: in option #2 above, changing the order of dependencies (in my case, between Setup and TargetB does not help -- Xcode still selects the order arbitrarily.

Running the script as a pre-action build step inside the scheme is the best way to accomplish what you want. A common use of this approach is to adjust the project's build number base on the source control state (example). As far as capturing the script output, unfortunately the only thing you can do is redirect stdout for the script to a log file. That log file could be part of your project, so it would be easy to view from Xcode.

Related

Xcode. How download files at the stage of project assembly?

I'm doing an iOS framework and now i need to implement something like this: At the stage of the project assembly, I want to make a request to the server, get the JSON in response and write it to the project file. Thus, the user who downloaded the application, and opened it without the Internet will already have these files. In Android, it is possible to implement this function using the Gradle plugin. What the right way to implement this in iOS? Thanks in advance for the answer.
You can use a Run Script phase in the Build Phases section of your project.
Step 1
Add the initial version of the file to your project. Make sure it's added to the target.
Step 2
Select your project and go the build phases tab. You should be able to see the file being copied to the bundle in the Copy Bundle Resources section.
Step 3
Add a new Run Script to your build. Click the plus button at the top-left of the editor.
Give the phase a sensible name by double clicking the label.
Reorder the phase as early as possible by dragging it up the list.
Step 4
Using your favourite scripting language/command line thing download the new file and replace the existing file.
e.g (untested demo bash sample, probably wont work)
MYFILE=${TEMP_DIR}/something.json
curl -o $MYFILE https://myserver.com/stuff/something-latest.json
cp -f $MYFILE ${SRCROOT}/Sploopy/Resources/something.json
You can examine the environment with the build inspector and see what values you can use to get the file in place.
Ones that will come in handy are:
TEMP_DIR is a good place to put files temporarily.
SRCROOT so you know where to copy the downloaded file to.
CONFIGURATION so you can choose when to do this action. Personally I would only do it on Release but YMMV. It will be a blocking action.
Step 5
Profit

Apple Watch pre-build action to change storyboard customModule references

I currently have a project with 3 different versions of the same app (different branding and such), which is working just fine. I've since then added 3 new Apple Watch targets (1 for each app "version"), where which 2 of them reference the files in the "master" Apple Watch target.
Everything works fine as long as i remember to change the module reference for each storyboard view, so that it maps to the correct interface controller in the corresponding watchkit extension target.
However, remembering to switch this every time i need to run/build a app version/target is a pain and not really a long term solution.
I've created the following command which I want to be run at the correct time, such that it changes the storyboard references before it is compiled.
perl -pi.bak -e 's/customModule=\"(.*?)\">/customModule=\"watchMyTarget_Extension\">/g' ${SRCROOT}/watch/Base.lproj/Interface.storyboard
I also concluded that I would probably want to reset the change after the app was compiled, since I don't want to have a file change for git to complain about. Which is why the aforementioned script creates a .bak file. So after the compilation is done and packed/run on device or whatever, I want to run
mv ${SRCROOT}/watch/Base.lproj/Interface.storyboard.bak ${SRCROOT}/watch/Base.lproj/Interface.storyboard
I've tried placing the scripts in the target's (watchTarget, not appTarget) build scheme, Build->Pre/Post Actions which didn't seem to have any effect. And I tried putting it in Run->Pre/Post Actions which worked to some degree, but it seemed like the post action kicked in before the app was pushed to the simulator and thus crashing the application ("could not run see device logs" or something like that).
So where on earth do I need to put these scripts to have them run at the correct time in the build process?
you should use add New Run Script Phase in your target's Build Phases, place it before the Compile Sources
Steps: (from Apple)
In the project editor, select the target to which you want to add a
run script build phase.
Click Build Phases at the top of the project editor.
Choose Editor > Add Build Phase > Add Run Script Build Phase.
Disclose the Run Script section in the project editor.
Configure the script in the Run Script template.
My solution is to go the Build settings of each watch extension target, setting the Product module name to the same value, for example, xxx_watch_extension. Then we should be able to choose this module for custom classes on the storyboard.
It works fine for me.

Dynamically change Bundle Loader of Unit Test

I have an Xcode project with multiple targets, but I want the same Unit Test to run across all of them.
I have created the Unit Test and attached it to each target in my project (using the Edit Scheme>Test menu). Xcode still uses the "Bundle Loader" project setting to determine which app to run when performing a Test though.
So I created an .xcconfig file which the Unit Test uses. This is what it looks like:
SO_BUILDING_PRODUCT_NAME = None
BUNDLE_LOADER = $BUILT_PRODUCTS_DIR/$(SO_BUILDING_PRODUCT_NAME).app/$SO_BUILDING_PRODUCT_NAME
As you can see, it's pretty straightforward. I then wrote a bash script which will change "None" to the actual name of the Xcode target that is being built. I then added this script to each target's Pre-Actions Build phase.
I can see as soon as I tell Xcode to Test, the .xcconfig file updates instantly and I can even see Xcode's UI for the Unit Test update automatically. Unfortunately though each time I change my target, I have to build twice for the change to take effect. It seems like I'm making my change too late for Xcode to notice or care.
Does anyone have some suggestions about how to force Xcode to take notice of the change I make at the start of the Build process?
As far as I have seen (although I haven't explored this since Xcode 5 was released), as soon as you hit Build, it's impossible to change any build settings via .xcconfig files, Xcode seems to take a snapshot before you get the chance to run any scripts or anything like that.
Unless those limitations of dynamic .xcconfig configuration have changed, you will need to try a different approach. I'm not familiar with BUNDLE_LOADER, but if you can change that variable via a script, rather than changing the .xcconfig file itself, you may have more luck.

StoryBoards are not in compile sources of my project

Why can't I locate my storyboards in compile sources in my project? Even a small change in the storyboard takes a lot of time in compiling, this makes me think that there is some Compiler Level Optimisation involved. Am I wrong? I can only find them in Copy Bundle Resources.
More clearly, I'm trying to understand how compiling, linking, and other actions happens when I'm running my project.
Check the Log Navigator:
It shows you plenty of information about what is happening when you perform clean, build or run, for example the storyboard processing looks like this:
From here you can research what this tool does from the man pages.
The nib and storyboard files used to be at the copy resources phase, during the build Xcode performs required operations based on their settings (you can see a set of arguments applied at the command line).

Xcode 4 Archive build not finding imports in precompiled headers

I have a project which won't Archive-build correctly in Xcode 4. It appears to me that the "Build For Archiving" and "Archive" commands build differently, and I cannot determine any reason why!
Details:
The workspace contains two projects: an app and a static library; the projects are arranged as peers.
"Build" and "Run" both work correctly; app executes in iOS simulator and on device.
"Build for Running", "Build for Testing", "Build for Profiling", and "Build for Archiving" all work correctly also.
Build for Running is debug, Build for Profiling is release, so both configuration have been tested.
"Archive" fails!
Failure 1 (Library):
When I try to execute the "Archive" command on the library, a custom resource script fails. The script is reading from the resources directory ($RESOURCES_DIR)
For release builds, this maps to:
...DerivedData/App-uniquecrap/Build/Products/Release-iphoneos/Resources/LibraryName
For the archive, this maps to:
...DerivedData/App-uniquecrap/ArchiveIntermediates/Build Frameworks/BuildProductsPath/Release-iphoneos/Resources/LibraryName
Except Resources is missing in the ArchiveIntermediates tree!
UPDATE: The first part of the problem is caused by poor quoting in the script file. Xcode4's paths now include a folder with an embedded space (imagine that!) and improperly quotes paths don't work as well as we would like. That fixed, I still have the problem that the resource files copied by the Gather Resources build phase are not going to the correct location; the script expects them in $(RESOURCE_DIR), but I can't seem to find the correct setting in Gather Resources to send them there. Suggestions?
Failure 2 (App):
When I have managed to kludge my way part the first failure (typically by copying files and bypassing the script), I run into another error building the App. "Archive" complains that it can't find imported files when building the precompiled headers.
Nothing unusual here either; headers are imported in pch file in the typical manner:
#import <LibraryName/Library.h>
As before, "Build", "Build for Archiving", etcetera succeed without problem. "Archive" fails. Either the include paths are set up correctly and "Archive" is ignoring them; or the include paths are set up incorrectly and Xcode is auto-magically fixing them in the other builds. (Either way, I'm very frustrated).
How can I (A) make Archive builds work; or (B) figure out what is going on, so I can accomplish (A)?
Thanks.

Resources