I'm using py2app to bundle a Mac application and am trying to figure out how it works. Based on reading the Bundle Programming Guide it seems that CFBundleExecutable is a required key and that this is the key OSX uses to figure out which file in the MacOS subfolder to run. However, I stripped my Info.plist file to the following, and the app loads just fine:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PyMainFileNames</key>
<array>
<string>__boot__</string>
</array>
<key>PyRuntimeLocations</key>
<array>
<string>#executable_path/../Frameworks/Python.framework/Versions/2.7/Python</string>
</array>
</dict>
</plist>
How can this be so? Given that exact plist file, how can OSX load my application?
The CFBundleExecutable key is required in the sense that you really should have it, but CoreFoundation will do its best to deal with its absence. By looking at the source of CFBundle we can get an idea how it deals with this key:
static CFStringRef _CFBundleCopyExecutableName(CFBundleRef bundle, CFURLRef url, CFDictionaryRef infoDict) {
CFStringRef executableName = NULL;
// …
if (infoDict) {
// Figure out the name of the executable.
// First try for the new key in the plist.
executableName = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleExecutableKey);
// Second try for the old key in the plist.
if (!executableName) executableName = (CFStringRef)CFDictionaryGetValue(infoDict, _kCFBundleOldExecutableKey);
if (executableName && CFGetTypeID(executableName) == CFStringGetTypeID() && CFStringGetLength(executableName) > 0) {
CFRetain(executableName);
} else {
executableName = NULL;
}
}
if (!executableName && url) {
// Third, take the name of the bundle itself (with path extension stripped)
So you can see that they look for the following in order:
The CFBundleExecutable key.
The NSExecutable key, a legacy name predating OS X public beta.
If neither is present, fall back to using the name of the bundle with the path extension removed.
Having determined the executable name, the manner in which the directory that it lives in is found is equally full of quirks. I'll leave discovering those as an exercise for interested parties.
Related
When I create a new file in my project in the file header, I find this:
//
// NameFile.swift
// NameProject
//
// Created by Name Surname on dd/mm/yy.
//
I would like to change it, but in the settings I don't find where to do it.
I would like to change it for all possible future projects.
I would like to achieve such a thing.
//
// NameFile.swift
// NameProject
//
//
Edit:
I would like to try to remove the comment, but I can't find solutions.
Say you want to modify (or get rid of) the XCode Header comment.
First open XCode, Use File > New File... (⌘N) and choose Property List from the file templates.
Name it file IDETemplateMacros.plist
On the navigator, select the file as right-click Open as source code. Xcode will show us the property file as text. Property files are really just XML files.
Copy paste the following content:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>FILEHEADER</key>
<string>Created for ___PROJECTNAME___ in ___YEAR___
// Using Swift ___DEFAULTTOOLCHAINSWIFTVERSION___</string>
</dict>
</plist>
On the root dict we have added an entry with key FILEHEADER and a two-lines string as a value:
Created for ___PROJECTNAME___ in ___YEAR___
// Using Swift ___DEFAULTTOOLCHAINSWIFTVERSION___
Save the file IDETemplateMacros.plist on the folder:
~/Library/Developer/Xcode/UserData/
That's it, now when creating a new project called MyProject the header will be:
//Created for MyProject in 2022
// Using Swift 5.0
Note1. There is a list of macros on https://help.apple.com/xcode/mac/9.0/index.html?localePath=en.lproj#/dev7fe737ce0
Note 2. As an example you can write:
Created ___DATE___
// ___COPYRIGHT___
Note that there is a leading space but you do not include the // for the comment on the first line.
Note 3. For a more list of options see:
https://useyourloaf.com/blog/changing-xcode-header-comment/
From https://developer.apple.com/forums/thread/711305?answerId=722311022#722311022
Add ~/Library/Developer/Xcode/UserData/IDETemplateMacros.plist with an empty FILEHEADER entry to generate an empty comment.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>FILEHEADER</key>
<string></string>
</dict>
</plist>
Xcode help:
Customize text macros:
https://help.apple.com/xcode/mac/?localePath=en.lproj#/dev91a7a31fc
Text macros reference:
https://help.apple.com/xcode/mac/index.html?localePath=en.lproj#/dev7fe737ce0
If you really want to remove that empty comment line then you will need to start adding custom file templates. eg for a No Comment Swift File template create the following:
~/Library/Developer/Xcode/Templates/File Templates/MultiPlatform/Source/No Comment Swift File.xctemplate/___FILEBASENAME___.swift with import Foundation
followed by an empty line
~/Library/Developer/Xcode/Templates/File Templates/MultiPlatform/Source/No Comment Swift File.xctemplate/TemplateInfo.plist with something like
TemplateInfo.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SupportsSwiftPackage</key>
<true/>
<key>Kind</key>
<string>Xcode.IDEFoundation.TextSubstitutionFileTemplateKind</string>
<key>Description</key>
<string>A no comment Swift file.</string>
<key>Summary</key>
<string>A no comment Swift file</string>
<key>SortOrder</key>
<string>1</string>
<key>Image</key>
<dict>
<key>FileTypeIcon</key>
<string>swift</string>
</dict>
<key>AllowedTypes</key>
<array>
<string>public.swift-source</string>
</array>
<key>Platforms</key>
<array/>
<key>DefaultCompletionName</key>
<string>File</string>
<key>MainTemplateFile</key>
<string>___FILEBASENAME___.swift</string>
</dict>
</plist>
The default empty Swift File template in the Xcode 14 beta is: /Applications/Xcode-beta.app/Contents/Developer/Library/Xcode/Templates/File Templates/MultiPlatform/Source/Swift File.xctemplate/ (do not edit this directly).
I'm trying to programmatically capture GPU frames using MTLCaptureManager in a command line application.
So far, the capture manager fails to support the MTLCaptureDestinationGPUTraceDocument destination.
I tried to create a very minimal repro case using XCode :
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager];
if (![captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument])
{
NSLog(#"********** captureManager does not support MTLCaptureDestinationGPUTraceDocument ************");
}
else
{
NSLog(#"captureManager support is fine");
}
}
return 0;
}
When run with XCode, it seems to be willing to work : the output is :
2020-09-02 16:25:59.712217+0200 testMetalCapture[20095:416447] Metal GPU Frame Capture Enabled
2020-09-02 16:25:59.712503+0200 testMetalCapture[20095:416447] Metal API Validation Enabled
2020-09-02 16:26:00.669092+0200 testMetalCapture[20095:416447] captureManager support is fine
Program ended with exit code: 0
But when I archive the build result, and run from a terminal, it fails :
2020-09-02 16:32:57.607 testMetalCapture[20126:419837] ********** captureManager does not support MTLCaptureDestinationGPUTraceDocument ************
Is there any runtime environment I could reproduce in terminal to get the MTLCaptureManager working ?
(Environment is XCode 11.6 + MacOS 10.15 Catalina)
From what I understood (I could not find any official documentation) :
MTLCaptureManager needs an authorization from the Info.plist: the MetalCaptureEnabled should set to YES.
The proper way is to bundle the command line application with such a plist
This is quite involved, see for example Building OSX App Bundle
I found by accident that MTLCaptureManager also works if there's an Info.plist in the same directory as the command line application.
This Info.plist can be almost empty, like this :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MetalCaptureEnabled</key>
<true/>
</dict>
</plist>
With this simple setup, I can run my test program (and my real one too)
% ./testMetalCapture
2020-10-02 15:53:08.507 testMetalCapture[28559:686864] Metal GPU Frame Capture Enabled
2020-10-02 15:53:08.523 testMetalCapture[28559:686864] captureManager support is fine
(I'm currently using MacOSX 10.15.6, it may break in the future)
The trick from #rotoglup was working for me, until it wasn't anymore: some buffers were full of zeros, and no image appeared in the 'screenshots' inside the metal debugger. I don't know if this was because of an update of the OS or of XCode or something else.
I still needed an Info.plist with this content, as #rotoglup suggested:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MetalCaptureEnabled</key>
<true/>
</dict>
</plist>
but I also needed to set the METAL_DEVICE_WRAPPER_TYPE env variable to 1 in the terminal that launches the command line app:
export METAL_DEVICE_WRAPPER_TYPE=1
With this, buffers are filled up correctly, and I get non-empty screenshots.
Based on productbuild's Distribution XML structure, pkg-ref's version attribute is auto-filled by productbuild itself. You can also specify the package version using a --version parameter to productbuild.
I made two packages: package A with version 1.0 and package B with the same binaries with version 2.0. These versions were given in three ways:
As --version parameter
As version of the binary being packed
version values inside the Distribution.xml file
However, it seems Installer doesn't bother checking version and just installs any package being run. If you install version 2.0 first and then run the version 1.0 package next, the app is overwritten.
How do I enforce Installer to check the versions? Is there a key/attribute/parameter I need to specify somewhere to make the package version sensitive?
In your Distribution.xml code, add this function:
function dontDowngrade(prefix) {
if (typeof(my.result) != 'undefined') my.result.message = system.localizedString('ERROR_2');
var bundle = system.files.bundleAtPath(prefix + '/Applications/YOURAPPNAMEHERE');
if (!bundle) {
return true;
}
var bundleKeyValue = bundle['CFBundleShortVersionString'];
if (!bundleKeyValue) {
return true;
}
if (system.compareVersions(bundleKeyValue, '$packageVersion') > 0) {
return false;
}
return true;
}
The error string ERROR_2 is in Localizable.strings:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ERROR_0</key>
<string>This update requires Mac OS X version %# or later.</string>
<key>ERROR_1</key>
<string>This software is not supported on your system.</string>
<key>ERROR_2</key>
<string>A newer version of this software is already installed. </string>
<key>SU_TITLE</key>
<string>YOURAPPNAMEHERE</string>
</dict>
</plist>
I put all this in a bash script and use a here document to replace text with shell variables. For example, $packageVersion is the version of my app, e.g. "2.0.0.0". The string YOURAPPNAMEHERE could also be replaced with a shell variable.
cat <<EOF >"Distribution.xml"
<?xml version="1.0" encoding="utf-8"?>
...other text...
EOF
You can learn a lot by examining the iTunes installer. Download the installer, mount it, drag the .pkg file out and expand it:
$ /usr/sbin/pkgutil --expand Install\ iTunes.pkg iTunesExpanded
Then you can see the code and poke around
I have been setting up a launchd.plist XML that is run every time a specific USB device is mounted. I followed the instructions on the xpc_events(3) man page and it is running the application whenever the device is mounted.
The problem I'm having is that the application is run again and again every 10 seconds as long as the device is still mounted. How can I set it up so it only runs once when the device is inserted in the USB port?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myapp.agent</string>
<key>Program</key>
<string>/Applications/MyApp.app/Contents/MacOS/MyAgent</string>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>2316</integer>
<key>idProduct</key>
<integer>4096</integer>
<key>IOProviderClass</key>
<string>IOUSBDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
</dict>
</dict>
<key>com.apple.notifyd.matching</key>
<dict>
<key>com.apple.interesting-notification</key>
<dict>
<key>Notification</key>
<string>com.apple.interesting-notification</string>
</dict>
</dict>
</dict>
</dict>
</plist>
I wrote a tutorial on this with detailed instructions and example files for triggering an arbitrary executable or shell script by the connection of an external device (usb/thunderbolt) to a Mac computer, without the respawning problem.
Like the authors approach, it relies on Apple's IOKit library for device detection and a daemon for running the desired executable. For the daemon to not be triggered repeatedly after connecting the device, a special stream handler (xpc_set_event_stream_handler) is used to "consume" the com.apple.iokit.matching event, as explained in the post by #ford and in his github repo.
In particular, the tutorial describes how to compile the xpc stream handler and how to reference it together with the executable in the daemon plist file and where to place all the relevant files with correct permissions.
For the files, please go here. For completeness, I have also pasted their content below.
Run shell script or executable triggered by device detection on a mac
Here I use the example of spoofing the MAC address of an ethernet adapter when it is connected to the Mac. This can be generalized to arbitrary executables and devices.
Put your shell script or executable into place
Adapt the shell script spoof-mac.sh
#!/bin/bash
ifconfig en12 ether 12:34:56:78:9A:BC
to your needs and
make it executable:
sudo chmod 755 spoof-mac.sh
Then move it into /usr/local/bin, or some other directory:
cp spoof-mac.sh /usr/local/bin/
Building the stream handler
The stream handler xpc_set_event_stream_handler.m
// Created by Ford Parsons on 10/23/17.
// Copyright © 2017 Ford Parsons. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <xpc/xpc.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
NSLog(#"%s", event);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
is universal (no need to adapt) and can be built on a mac command line (with xcode installed):
gcc -framework Foundation -o xpc_set_event_stream_handler xpc_set_event_stream_handler.m
Let's place it into /usr/local/bin, like the main executable for the daemon.
cp xpc_set_event_stream_handler /usr/local/bin/
Setup the daemon
The plist file com.spoofmac.plist contains the properties of the daemon that will run the executable on device connect trigger.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UserName</key>
<string>root</string>
<key>StandardErrorPath</key>
<string>/tmp/spoofmac.stderr</string>
<key>StandardOutPath</key>
<string>/tmp/spoofmac.stdout</string>
<key>Label</key>
<string>com.spoofmac.program</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/xpc_set_event_stream_handler</string>
<string>/usr/local/bin/spoofmac.sh</string>
</array>
<key>LaunchEvents</key>
<dict>
<key>com.apple.iokit.matching</key>
<dict>
<key>com.apple.device-attach</key>
<dict>
<key>idVendor</key>
<integer>32902</integer>
<key>idProduct</key>
<integer>5427</integer>
<key>IOProviderClass</key>
<string>IOPCIDevice</string>
<key>IOMatchLaunchStream</key>
<true/>
<key>IOMatchStream</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>
It contains information for identifying the device you want to base your trigger on, like idVendor, idProduct, IOProviderClass. These can be figured out in the System Information App on your mac.
Convert the hex identifiers to integers before inserting into the plist file (for example using int(0x8086) in python).
IOProviderClass should be either IOPCIDevice (Thunderbolt) or IOUSBDevice (USB).
The other relevant entry in the plist file is the location of xpc_set_event_stream_handler and the executable.
Other entries include the location of standard output (log) files and the executing user.
Since MAC spoofing requires root privileges, we put com.spoofmac.plist into /Library/LaunchDaemons:
cp com.spoofmac.plist /Library/LaunchDaemons/
not into a LaunchAgents folder. Launch agents ignore the UserName argument.
Insure that the owner of the file is root:
sudo chown root:wheel /Library/LaunchDaemons/com.spoofmac.plist
Launch the daemon
Activate the daemon:
launchctl load /Library/LaunchDaemons/com.spoofmac.plist
and you are good to go.
Unloading is done using launchctl unload.
AIUI your application must call xpc_set_event_stream_handler to remove the event from the queue. You might also have to add <key>KeepAlive</key><false/> to the .plist, but I'm not sure about that.
I am trying to use something like this:
#include <xpc/xpc.h>
#include <unistd.h>
#include <asl.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
return 1;
}
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: starting");
xpc_set_event_stream_handler("com.apple.iokit.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t event) {
const char *name = xpc_dictionary_get_string(event, XPC_EVENT_KEY_NAME);
uint64_t id = xpc_dictionary_get_uint64(event, "IOMatchLaunchServiceID");
asl_log(NULL, NULL, ASL_LEVEL_DEBUG, "event_stream_handler: received event: %s: %llu", name, id);
execv(argv[1], argv + 1);
});
dispatch_main();
return 0;
}
So a script which consumes the event and runs the script passed as an argument.
This works for me:
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
xpc_set_event_stream_handler("com.apple.iokit.matching", NULL, ^(xpc_object_t _Nonnull object) {
const char *event = xpc_dictionary_get_string(object, XPC_EVENT_KEY_NAME);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if(argc >= 2) {
execv(argv[1], (char **)argv+1);
}
}
}
full source code here
I built the Command Line tool (Foundation) template in Xcode. It just logs "Hello World" to the console.There is only one class in it main.m. Here's the code:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
#autoreleasepool {
// insert code here...
NSLog(#"Hello, World!");
}
return 0;
}
Now I want to run it as a daemon and log "Hello World" to the console every 10 seconds. So I moved the product/binary to /tmp on my Mac. I created the following plist for launchd:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>helloDaemon</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/helloDaemon</string>
</array>
<key>StartInterval</key>
<integer>10</integer>
</dict>
</plist>
I loaded the plist using launchctl, but I do not see any "Hello World"s in the console. Instead, I get this:
11/03/2012 00:55:35.141 com.apple.launchd: (helloDaemon) Throttling respawn: Will start in 1 seconds
11/03/2012 00:55:45.141 com.apple.launchd: (helloDaemon) Throttling respawn: Will start in 2 seconds
11/03/2012 00:55:55.140 com.apple.launchd: (helloDaemon) Throttling respawn: Will start in 3 seconds
So what's going wrong?
Just add to your launchd plist
<key>StandardOutPath</key>
<string>/yourpath/sample.log</string>
You can tail -f it afterwards.
More info is here:
http://developer.apple.com/library/mac/technotes/tn2083/_index.html#//apple_ref/doc/uid/DTS10003794-CH1-SUBSECTION39
NSLog is not working because when you launch a demon process it does not have any standard io sockets or file handles attached to it. They have to be specifically allocated. A good resource for how to create a proper daemon and how to write the console and syslog is provided in the book "Advanced Mac OS X Programming (chapter 20) by Dalrymple & Hillegass.
They have define a skeleton program that addresses the io issues you highlight. I remembered reading it a while ago and thought that maybe someday I'd need it. The authors show a sample using the syslog.h lib using openlog() and syslog() for simple communications. They also show some other lower level methods for communication to files and even sockets (for servers, etc).
I always appreciate when someone can tell me how to do something rather than reference something, but in this case, that is the best I can do. good luck.
With the following keys in the plist file I get most of the logs. But sometimes I am confused if I get all the NSLog output in the log file. To me it seems sometime some logs line are missing.
I am not sure if all the NSLog output of a LaunchDaemon is logged or some are skipped due to system priority.
<key>StandardOutPath</key>
<string>/var/log/mydaemon.log</string>
<key>StandardErrorPath</key>
<string>/var/log/mydaemon.log</string>