Happy new year.
Here's something annoying. When you unarchive a
xip archive, like the ones
that Xcode releases come in, the unarchive happens to a temporary directory that
lives on the root volume, even if the archive doesn't, and even if the result of
the unarchive is ultimately going back to the secondary volume.
So, for example, if you are unarchiving
files get unarchived to
/private/var/folders/foo/bar/T/<some UUID>, and then
Xcode.app goes back to
This happens if you use Archive Utility. This happens if you use the
It's all because PackageKit, the framework that underlies both tools, is lazy
NSTemporaryDirectory() to generate a temporary working directory. It
should really be using
-[NSFileManager URLForDirectory:inDomain:appropriateForURL:create:error:] with
but to be fair that API only came around recently, you know, in 2009.
Anyway, this is dumb for several reasons. It results in extra copies, sure.
But the whole reason I put
Xcode.xip on an external disk is that I can't spare
the half a terabyte or whatever that out-of-box Xcode installs take up now. Not
on my internal disk.
This sort of thing happens all over the place in Apple's installer stack. I guess they consider external disks a corner case that doesn't merit testing.
Working around this is pretty easy. Just set a breakpoint on
NSTemporaryDirectory() and return a more appropriate working directory. In my
case, I set up a
/Volumes/ExternalHDD/tmp/ and then did this:
$ lldb -- xip --expand Xcode_13.4.1.xip (lldb) target create "xip" Current executable set to 'xip' (x86_64). (lldb) settings set -- target.run-args "--expand" "Xcode_13.4.1.xip" (lldb) b NSTemporaryDirectory Breakpoint 1: where = Foundation`NSTemporaryDirectory, address = 0x00007fff211727c7 (lldb) run Process 4370 launched: '/usr/bin/xip' (x86_64) xip: signing certificate was "Software Update" (validation not attempted) Process 4370 stopped * thread #3, queue = 'com.apple.root.default-qos', stop reason = breakpoint 1.1 frame #0: 0x00007fff211837c7 Foundation`NSTemporaryDirectory Foundation`NSTemporaryDirectory: -> 0x7fff211837c7 <+0>: pushq %rbp 0x7fff211837c8 <+1>: movq %rsp, %rbp 0x7fff211837cb <+4>: pushq %r15 0x7fff211837cd <+6>: pushq %r14 Target 0: (xip) stopped. (lldb) bt * thread #3, queue = 'com.apple.root.default-qos', stop reason = breakpoint 1.1 * frame #0: 0x00007fff211837c7 Foundation`NSTemporaryDirectory frame #1: 0x00007fff3b95dd83 PackageKit`-[PKSignedContainer _startUnarchivingAtPath:cancelHandler:notifyOnQueue:progress:finish:] + 92 frame #2: 0x00007fff3b95f657 PackageKit`__74-[PKSignedContainer startUnarchivingAtPath:notifyOnQueue:progress:finish:]_block_invoke + 69 frame #3: 0x00007fff20174623 libdispatch.dylib`_dispatch_call_block_and_release + 12 frame #4: 0x00007fff20175806 libdispatch.dylib`_dispatch_client_callout + 8 frame #5: 0x00007fff20177e37 libdispatch.dylib`_dispatch_queue_override_invoke + 775 frame #6: 0x00007fff20184818 libdispatch.dylib`_dispatch_root_queue_drain + 326 frame #7: 0x00007fff20184f70 libdispatch.dylib`_dispatch_worker_thread2 + 92 frame #8: 0x00007fff2031c417 libsystem_pthread.dylib`_pthread_wqthread + 244 frame #9: 0x00007fff2031b42f libsystem_pthread.dylib`start_wqthread + 15 (lldb) p (id)[@"/Volumes/ExternalHDD/tmp/" copyWithZone:0] (__NSCFString *) $0 = 0x000000010020a260 @"/Volumes/ExternalHDD/tmp/" (lldb) thread return 0x000000010020a260 * thread #3, queue = 'com.apple.root.default-qos', stop reason = breakpoint 1.1 frame #0: 0x00007fff3b95dd83 PackageKit`-[PKSignedContainer _startUnarchivingAtPath:cancelHandler:notifyOnQueue:progress:finish:] + 92 PackageKit`-[PKSignedContainer _startUnarchivingAtPath:cancelHandler:notifyOnQueue:progress:finish:]: -> 0x7fff3b95dd83 <+92>: testq %rax, %rax 0x7fff3b95dd86 <+95>: movq %r14, -0xae8(%rbp) 0x7fff3b95dd8d <+102>: je 0x7fff3b95e1db ; <+1204> 0x7fff3b95dd93 <+108>: movq %rax, %rbx (lldb) c Process 4370 resuming
It only ever asks once, and it seems always to be the first hit of the breakpoint. So pleasant! And then it does exactly what you would want.