How to avoid XCode framework weak-linking problems? - cocoa

I'm building an application that takes advantage of Mac OS X 10.6-only technologies, but without giving up backwards compatibility to 10.5 Leopard.
The way I do this is by setting the 10.6 SDK as the base SDK, weak-linking all frameworks and setting the deployment target to 10.5 as described in:
http://developer.apple.com/mac/library/DOCUMENTATION/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
This works fine; before making a call that is Snow Leopard-only I need to check that the selector or indeed the class actually exist. Or I can just check the OS version before making the call.
The problem is that this is incredibly fragile. If I make a single call that is 10.6 only I blow Leopard-compatibility. So using even the normal code code completion feature can be dangerous.
My question: is there any way of checking which calls are not defined on 10.5 before doing a release build? Some kind of static analysis, or even just a trick (a target set the other SDK?) would do.
I obviously should test on a Leopard machine before releasing anything, but even so I can't possibly go through all paths of the program before every release.
Any advice would be appreciated.
Best regards,
Frank

You could change the target SDK to 10.5. The compiler will then output warning: definition for '-snowLeopardOnlyMethod:' not found messages.

Related

What's the best way to set MACOSX_DEPLOYMENT_TARGET?

The Perl community has relied on MACOSX_DEPLOYMENT_TARGET=10.3 for all builds of Perl for, well, a long time. But now, with the El Capitan beta, it no longer works. It seems as though they should bump it up, but to what? Is there a way to programmatically set it to whatever is the earliest version supported by the release of OS X on which Perl is currently being built? Or is there some other way to set it dynamically? Or must it be static and updated periodically? If the latter, how does it affect builds on older versions of OS X?
This should be a fairly simple change in hints/darwin.sh, that checks what OSX version is running and sets MACOSX_DEPLOYMENT_TARGET accordingly.
Perl is an Open Source project, and as such always short on developer time. If you were to make a patch for this and submit it to p5p, I'm sure it would be greatly appreciated.
Accepted #Calle-Dybedahl's answer, as it's technically correct. Perl 5 Core Hacker Jarkko Hietaniemi pushed this solution, which proved to be a bit more complicated than you might expect at a glance. The basic recipe he came down on was:
For OS X 10.6 or above, do not any more use the MACOSX_DEPLOYMENT_TARGET,
the toolchains should work fine without. Until now the deployment target
was hardwired to 10.3. This logic comes from
RT#117433.
For OS X releases from 10.3 until 10.5, no change, still using
the MACOSX_DEPLOYMENT_TARGET=10.3 for linking.
For OS X releases before 10.3, no change, still not using
the MACOSX_DEPLOYMENT_TARGET=10.3.
New: always add -mmacosx-version-min to ccflags and ldflags from
the env var $MACOSX_DEPLOYMENT_TARGET, if set. If the var is not set,
set the min from the OS X version, from sw_vers(1). Setting the var
should become handy for people building and packaging Perl for earlier
OS X versions.

OSX Sandbox: Launch a different executable based on OS version

I have an application in the Mac App Store. I'm trying to support users going back to Snow Leopard but this is becoming increasingly difficult.
Recently I've hit a roadblock due to the iTunesLibrary.framework, this framework must be linked to the main executable and yet doing so will always trigger a crash on load when running in Snow Leopard.
To workaround this problem, I want to compile a version of my app that doesn't use features and frameworks from newer versions of OSX. The problem is, how can I launch the compatibility build automatically?
I'm considering trying to make the main executable point to a shell script, but I don't really like that idea. I've also thought of the main executable being a helper that simply launches the full app and then exits. I expect this would work, but I worry about it getting approved by Apple. Finally, I'm wondering if the app bundle format itself can support this kind of setup, maybe via an advanced used of CFBundleExecutable that I'm unaware of.
Has anyone been down this road, what would you suggest?
Try weak linking the frameworks, more information about Weak Linking and Apple Frameworks here. Then also check in your code for the OS version or - (BOOL)respondsToSelector:(SEL)aSelector of any NSObject to determine what you can call and what not.
To have Snow Leopard as Base SDK you'll need an old Xcode and will have troubles submitting to Mac App Store.

Xcode 5 Developing for OS X 10.6

I have 10.9/Xcode 5 and I'm trying to write a Cocoa app for Snow Leopard. I deactivated auto layout and set the deployment target to 10.6, but the archived application won't launch on 10.6 because it can't load the nib (console output). From what I've read, I need to use garbage collection instead of ARC. If this is true, how to I modify my project to build and run for 10.6? Thanks for your advice.
First, make sure the XIB itself is configured to build for 10.6. In the right-hand pane of Interface Builder, you should see this:
If you're targeting 64-bit 10.6, then you can use ARCLite, which is just fine and I strongly recommend it. The only thing you really give up is auto-zeroing weak references. You only have to give up ARC if you go 32-bit. I recommend sicking to 64-bit if you can possibly help it. You give up a lot if you target 32-bit. To use ARCLite, you don't have to change anything. It's automatically selected when you compile an ARC program for 10.6.
As a note: yes, what you're doing is totally possible. I still target 10.5 with a 10.9/Xcode5 setup.

confusion of how to make osx app backward compatible & how to test them

after reading the apple SDK guide
https://developer.apple.com/library/mac/#documentation/developertools/conceptual/cross_development/Overview/overview.html
I'm still confused of how to make the mac app backward compatible & how to test them properly
I have an app, I run it and tested it on Mountain Lion 10.8 without any problem, however I want to make this app backward compatible so that other users can run it on a mac 10.6 - 10.7 machine.
I have an apple developer id and I can download the old versions of 10.7 and 10.6, but the problem is, I have a 2011 macbook air which is currently running 10.8, and that's the only apple machine that I have. Can I test the 10.7 and 10.6 by using vmware or parallels?
in my project settings, I set the target deployment to 10.6 (as I want 10.6 users to run my app), but should I set my SDK to 10.8 or 10.7? if I set the SDK to 10.8 but having the target deployment set to 10.6, if I fix all the xcode warnings will it run successfully on 10.6??
from the SDK drop down, I can only set to 10.8 or 10.7, but 10.6 is missing, how do I fix that?
thanks in advance
I develop on a 10.8 box and support back to 10.5. Just a couple of months ago we dropped 10.4 PPC support, and I'm still cleaning out some of the 10.2-specific code. This may get a little rant-y, but I've been doing old versions for a long time. I have some opinions on the matter.
No matter what Apple says in their docs, if you want to support 10.6, then build with the 10.6 SDK. Do not rely on distribution target.
I have had this discussion with the Xcode engineers, and while they hold to Apple's party line that you should always build with the latest SDK, they also acknowledge that it's generally insane to do so. If you build against the 10.8 SDK and mark your deployment target at 10.6, you will get no warnings for using methods that do not exist on 10.6. The only way you will discover that you've used a nonexistent method is that it might give you strange bugs when run on 10.6. That's insane.
Remember, OS X doesn't crash when you send an unknown selector. It just aborts the current runloop. So the bugs are even harder to track down then on iOS, where it crashes the app.
Sure, you can do weak linking. Talk about dangerous.... Yes, there are a few times this is useful, but the compiler gives you no warning if you don't do it correctly. If I'm going to do weak linking like this, I go the other way, linking against the old SDK and copying the new function's prototype into my implementation. That way I have documentation of every function I think I'm going to weak-link.
Download the old SDKs and symlink them into your Xcode distribution.
Guard them jealously. Apple will try to delete them every time you upgrade Xcode. Make your own copies and stick them in /SDKs or somewhere else away from Xcode. I provide a script called fix-xcode to manage the symlinks automatically. Am I bitter at Apple for their relentless insistance on deleting my old SDKs? Yes, I am.
You can run 10.6 Server in a VM legally. You can run 10.7+ Desktop in a VM legally. These are good ways to test your code.
Or you can do what I do and have a small pile of old MacBooks each with two or three partitions on them that you reboot all the time.
Now that 10.7 comes from App Store, it's a little harder to make VMs. My strong recommendation is to snapshot your image immediately after install, and make a clean backup copy of it. You'll want to be able to clone that image from time to time when you need to get back to a "raw" machine.
Get in the habit of squirreling away SDKs as they come out. 10.8 will be old some day. You might as well make a copy now while it's easy.
Whether you support individual dot-releases or not, it can be very helpful to keep around the upgrade packages for individual dot releases. When you encounter customers running non-current releases, it's nice to be able to check whether an "unreproducible" bug in fact is easily reproducible on their specific version. Whether this is worth it or not depends heavily on your product and customers. It was a life-saver for me when 10.4.11 made major changes to WebKit during a dot release...
Invest in a small NAS or a big external USB drive (though I've had trouble with those failing when used extensively, so I prefer a RAID). You'll need the space. You want to hold onto lots of VMs and lots of SDKs and sometimes even old versions of Xcode.
Adding to Rob Napier's great in-depth answer:
To use an old SDK, put the SDK (or a symlink) to it here:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
With XCode 7.3 or later, you need you to open this file and change "MinimumSDKVersion" (otherwise XCode will refuse to use the old SDK):
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Info.plist
You can install multiple versions of Mac OS on a single machine, booting between each.
The SDK should be the latest (10.8).
See 2.
One alternative to 1 that I've considered (I am in the same boat) is to create a Snow Leopard Hackintosh using an old PC and just installing Lion and Mountain Lion on my MBP.
You need to do these settings :
1.Set the Base SDK to Current version of Mac (ex. 10.7)
2.Set the Deployment SDK to older version (ex.1.4)

Mac SDK: using latest SDK but ensuring backwards compatibility with earlier deployment target

As always when Apple updates OS X, the latest XCode 4.4 dumps the older (10.6) SDK and I find myself needing to use the 10.7 SDK (or 10.8 I suppose) and setting my deployment target to 10.6 to maintain compatibility.
I prefer linking to the older SDK because I know that I cannot by mistake introduce calls to APIs that do not yet exist. Something that I found myself doing regularly when I last tried the inverse approach.
What I find myself doing is that I use the code completion feature in XCode to choose the "right" call for a simple class like NSWorkspace, then everything works fine during development, I forget about it and when I release a new version: Kaboum! The whole application explodes on earlier OS X releases at run-time; often in those hard-to-reach places :-)
Or at least this was the situation for me a few years back.
Surely, by now there's a way to either:
making sure you don't introduce API calls that are not yet available in your deployment target even if though they are defined in the SDK
detecting such calls during build or static analysis time
I'm sure I've missed something, somewhere along the line.. Please enlighten me!
Best regards,
Frank
Surely, by now there's a way to either:
making sure you don't introduce API calls that are not yet available
in your deployment target even if though they are defined in the SDK
detecting such calls during build or static analysis time
No there is not. Yes, you should open a radar (bugreport.apple.com) against it. If you like, you can dupe mine: rdar://11985733
Yes, the only viable solution, despite Apple's recommendation, is to copy the old SDKs and link against them.
I spent quite some time talking with the Xcode team about exactly this issue at WWDC 2012. They agreed that it's broken. There is not currently a plan to fix it. Escalating radar's is how we influence Apple on these things.
I'm generally copy SDK from older versions to the newer one so that compiler will blain me if i use something not supported.
Also you can simply look at Quick Help when calling some methods that you are not sure about, like in screenshot you can see that launchApplicationAtURL method is only available from 10.6
I've had this annoying problem on iOS too. It's actually even more annoying on iOS as the user has to sync their device with iTunes and enable crash report sending before the crash report gets sent unlike Mac OS X where you don't need to do all that. Recently, I managed to add a compile-time check for checking APIs against older versions of the SDK. I'll first explain how I did it for iOS first and then try and help you to adapt this technique for Mac OS X. I don't code much for Mac atm so I can only really guide you in the right direction from my experience with iOS but I'll test my suggestions later today once I get back from work and give a definite answer.
So here's what I did for iOS:
I first had to get the older Simulator SDK I wanted to get. I could easily get this by downloading older Xcode 3 (not 4) versions which included the SDK needed.
I next had to install the SDK. This wasn't too hard, so I won't explain much here. But the SDKs are stored in the Packages folder. This folder is clearly visible in earlier Xcode 3 versions but is hidden in later versions. You can easily open it anyway through Terminal. Also, after the change in Xcode 4.3 where the Developer folder moved to within Xcode.app, so I had to install the SDK into a tmp folder and move the SDK into Xcode.app yourself. I would then need to restart Xcode if I had it open.
After that, I duplicated my debug configuration in your project and named it, in my case, something like iOS 4.3 API Check or something like that - doesn't really matter. Then I changed the Base SDK of this new configuration to the old SDK which I installed. The SDK I installed was not listed though so I had to select other and enter, again in my case, iphonesimulator4.3.
Finally, when I needed to check against older versions of the SDK, I changed the configuration for the Run <appname>.app in my project scheme to my iOS 4.3 API Check configuration. And there we go, a compile-time check against iOS 4.3.
As for Mac OS X, I'm sure you can achieve the same goal with this same method. There isn't Simulators for the Mac SDK so I think the regular SDK will work for this. As for getting the older SDK, if you have Xcode 4.2 still installed (after Xcode 4.3 changed it so the Developer folder is within Xcode.app) then you should find the 10.6 SDK there. If you don't, I'd imagine that Apple has a similar thing to iOS where the SDK downloads are available in the Dev Center or somewhere on the internet...
As for setting the Base SDK, if it's not listed then I think the name is MacOSX10.6 or whatever version you are after.
Everything else should be the same, but as mentioned earlier, I'll test this method later today and edit my answer to give a more definite answer but I would imagine this method would work for the Mac SDK.
i also assumed that the compiler will warn me about "too new" API usage for the deployment target OS version. but it turned out that the compiler doesn't warn you about it by default. one of the reasons might be you could still use the new API by checking the availability during runtime with "respondsToSelector:", for example, on a newer OS version even when the deployment target version was older. you would need to add the compiler option -Wpartial-availability which is available on Xcode 7.3+ (to be confirmed) in order to get the "warning: 'something' is partial: introduced in macOS 10.x" warning message.
on macOS 10.12.3 with Xcode 8.2.1:
$ cat foo.m
#include <Foundation/Foundation.h>
BOOL foo()
{
return [#"foo" containsString:#"bar"];
}
$ cc -mmacosx-version-min=10.9 -Wpartial-availability foo.m -c -o foo.o
foo.m:5:20: warning: 'containsString:' is partial: introduced in macOS 10.10 [-Wpartial-availability]
return [#"foo" containsString:#"bar"];
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSString.h:132:1: note:
'containsString:' has been explicitly marked partial here
- (BOOL)containsString:(NSString *)str NS_AVAILABLE(10_10, 8_0);
^
foo.m:5:20: note: explicitly redeclare 'containsString:' to silence this warning
return [#"foo" containsString:#"bar"];
^
1 warning generated.
see also: Is there a way for XCode to warn about new API calls?
I check my code by hacking around Availability.h to get the compiler to flag weak-linked symbols as warnings/errors. In my current (Xcode 5/llvm) incarnation, I'm using the code below. It warns whenever I use a symbol introduced in iOS 6.0 or later. I think it's fairly self-explanatory. The macros seem to need updating at each and every SDK update, so tread carefully. Oh, and you loose deprecation warnings too, so I only use this once in a while to double-check my conditional code.
#undef __NSi_6_0
#define __NSi_6_0 deprecated=1.0
#undef __NSi_6_1
#define __NSi_6_1 deprecated=1.0
#undef __NSi_7_0
#define __NSi_7_0 deprecated=1.0
#undef __NSd_6_0
#define __NSd_6_0
#undef __NSd_6_1
#define __NSd_6_1
#undef __NSd_7_0
#define __NSd_7_0
See also http://iphone.m20.nl/wp/2013/10/xcode-5-and-flagging-weak-linked-unavailable-symbols-from-a-newer-sdk/
Since Xcode 9 there is a build setting doing exactly this, turning on the warning -Wunguarded-availability and/or -Wunguarded-availability-new.
The former warns when an API newer than the deployment target is used. The latter only warns when an API introduced newer than macOS 10.13 or iOS 11 is used in a similar manner.
For an existing project, the former is off by default, and the latter is on by default.
This setting is called “unguarded availability” within Xcode’s build settings pane, and you can choose one of Yes, Yes for all versions, or No from the GUI.
For more details, see the WWDC17 session 411, “What's New in LLVM”, https://developer.apple.com/videos/play/wwdc2017/411/ .

Resources