"Application does not have a valid signature" in Xcode 8 because of Info.plist changing script phase

We encountered an odd situation.


Our project is being developed for more than 3 years now since Xcode 4, and it never was a problem. We migrated the project every September, and everything was fine. However, in Xcode 8 we started to get an odd error: “Application does not have a valid signature” when building to iOS 10 device.


The thing is, it runs perfectly first time. If you press Stop and Run again, it will produce this error. However, if you clean the project, delete DerivedData or simply add a symbol to code and remove it (forcing rebuilding), it will run again as well. BUT. Even if you uninstall an app and press Run, you will still get this error, and app won't be even installed to device.


At first time I thought it's a CocoaPods issue. I removed it from the project, but it didn't help. Then I started to remove build phases one by one and localized the error cause.


We have build phase that updates field BuildDate in Info.plist (since the beginning of the project), we read this value in the app itself. It looks like that:


infoplist="$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH"
builddate=`date -u +"%Y-%m-%dT%H:%M:%S.000Z"`
if [[ -n "$builddate" ]]; then
  /usr/libexec/PlistBuddy -c "Add :BuildDate $builddate" ${infoplist}
  /usr/libexec/PlistBuddy -c "Set :BuildDate $builddate" ${infoplist}
fi


It is being executed after Copy Bundle Resources phase. It seems that if we remove this phase, it will never produce the invalid signature error. For me it seems like Apple added an integrity check after code sign, and since the resource has changed without Xcode knowing about it, checksum doesn't change, and integrity check fails.


Of course, we can add ENV check to execute this only when archiving the project, but it is handy to have this feature in Debug too. Any ideas how this could be achieved?

Replies

I found a fix to this issue.


I created a separate plist file solely for this purpose, commited it and added it to .gitignore (so this file is just a placeholder with BuildDate key, and any changes won't be tracked). Then I've added a build phase that changes this value in plist (script is the same as above). I put it ABOVE the Copy Resources phase, so when the time comes for Copy Resources to be executed, Xcode knows that this file was changed, copies it to the bundle, and integrity check is passed successfully.


So I dunno if it's intended or a bug, I'd love to hear what's happening. But keep in mind.

Any chance you could show more specifically what you did? I'm having trouble reproducing your fix.


EDIT

Here are the more specific steps I took which solves the issue.


1. Added a new .plist file to the project named BuildDate.plist. Added a single "Do Not Delete" key with an explanation string value (I work within a team and the dummy plist might become a target for project cleanup).

-----


2. Commit this plist (Do NOT add to git ignore).

-----


3. Add this script immediately BEFORE the Copy Bundle Resources build phase

if [ "${ACTION}" = "clean" ]; then
#For Script Debug Purposes
#echo "Early exit for clean action."
exit 0;
fi

BDPlist="BuildDate.plist" # Or your path within the project directory
DATE=`date "+%Y-%m-%d %H:%M"`
/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Add :BuildDate string ${DATE}"
/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Set :BuildDate ${DATE}"

#For Script Debug Purposes
#/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Print"

-----


4. Add this script immeadiately AFTER the Copy Bundle Resources build phase

if [ "${ACTION}" = "clean" ]; then
#For Script Debug Purposes
#echo "Early exit for clean action."
exit 0;
fi

BDPlist="BuildDate.plist" # Or your path within the project directory
/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Delete :BuildDate"

#For Script Debug Purposes
#/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Print"

This allows you to not worry about git ignore when merging into the active work of other developers, after the build copies the plist during Copy Bundle Resource build phase, we reset it back to it's original state.

------


5. I then added a category to NSBundle for easy access to the build date.

NSBundle+MainBundleBuildDate.h

@interface NSBundle (MainBundleBuildDate)
/*
* Retrieves the build date from the main bundle after diverting to the BuildDate.plist.
*/
+ (NSString *)mainBundleBuildDate;
@end

NSBundle+MainBundleBuildDate.m

@implementation NSBundle (MainBundleBuildDate)
+ (NSString *)mainBundleBuildDate
{
    NSString *string = [[NSBundle mainBundle] pathForResource:@"BuildDate" ofType:@"plist"];
    NSDictionary *buildDateDictionary = [NSDictionary dictionaryWithContentsOfFile:string];
    return buildDateDictionary[@"BuildDate"];
}
@end

-----


Hope that helps anyone else with this issue, and thanks to @efpies for the basic fix.

If you do not wish to adjust API (step 5), you can also copy BuildDate to the info.plist after the Copy Bundle Resources build phase. This appears to work equally well. Whatever checksum is being used appears to be checking that the date stored in plist is before the timestamp of the Copy Bundle Resources build phase.


4. Add this script immeadiately AFTER the Copy Bundle Resources build phase

if [ "${ACTION}" = "clean" ]; then
#For Script Debug Purposes
#echo "Early exit for clean action."
exit 0;
fi

BDPlist="BuildDate.plist"
DATE=`/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c "Print :BuildDate"`
/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Delete :BuildDate"
/usr/libexec/PlistBuddy ${TARGET_BUILD_DIR}/${INFOPLIST_PATH}  -c  "Add :BuildDate string ${DATE}"
/usr/libexec/PlistBuddy ${TARGET_BUILD_DIR}/${INFOPLIST_PATH}  -c  "Set :BuildDate ${DATE}"

#For Script Debug Purposes
#/usr/libexec/PlistBuddy ${PROJECT_DIR}/${BDPlist}  -c  "Print"