I want to ensure that the main & test source of my macOS-only SwiftPM project (on GitHub at mas) builds via swift, xcodebuild & Xcode.
For builds of clean clones of the main branch (i.e. no locally edited files, no existing .build or .swiftpm folders, etc.):
The swift command line below builds main & test fine:
swift build --build-tests
The xcodebuild command line below doesn't seem to run the SwiftPM MASBuildToolPlugin (which generates a Swift file necessary for the build), which is setup for the project in Package.swift, so neither main nor test build:
xcodebuild -scheme MAS -destination "platform=macOS,arch=$(arch),variant=macos"
How can I get xcodebuild to run my MASBuildToolPlugin, or to run an equivalent?
In Xcode, building main works fine, so Xcode must run the SwiftPM MASBuildToolPlugin. Building test, however, fails with the following error:
No such module 'MAS'
If I capitalize the name of the executable in the following line in Package.swift from:
products: [.executable(name: "mas", targets: ["MAS"])],
to:
products: [.executable(name: "MAS", targets: ["MAS"])],
Then Xcode can compile the tests. That, however, sets the generated executable file's name to MAS, but I want it to be mas.
How can I use MAS for the package/module/target/etc. names in the source, but generate an executable file named mas?
I obviously can rename the executable after it has been generated by running mv MAS mas, but would the upper-case name be incorrectly used anywhere inside the executable? I assume not. Also, I'd prefer to setup my project properly, instead of using a file renaming hack.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
On my M4 Mac running macOS 15.5 using Xcode 16.4 & Xcode CLT 16.4, Swift code in my Swift Package Manager 5.9 project (https://github.com/mas-cli/mas) builds fine against some included Objective-C headers via the following command line:
swift build -c release
But cannot find modules for the included Objective-C headers when building inside Xcode 16.4 or with the following command line on the same Mac:
xcodebuild -scheme mas -configuration Release -destination platform=macOS,arch=arm64,variant=macos
The error is:
Sources/mas/AppStore/AppleAccount.swift:9:16: error: no such module 'StoreFoundation'
How can I get Xcode / xcodebuild to work?
Note that the project is normally built by running:
Scripts/build
which runs:
swift build -c release
after running the following script, which must be run before any build (swift, Xcode, or xcodebuild) because it generates a necessary file (Sources/mas/Package.swift):
Scripts/generate_package_swift
I've tried moving the Objective-C headers into include subfolders of their existing module folders, using double quotes instead of angle brackets for the #import statements, having module.modulemap files in the include
subfolders or their parent module folder, and moving the module folders one level up the file hierarchy, to no avail.
I've also tried various changes to the root-level Package.swift (not the generated one deeper in the hierarchy, which isn't inclined in the build configuration), like making separate library targets for each of the Objective-C modules, various swiftSettings & linkerSettings, etc.
Maybe some of those changes would have helped, but maybe they were in incorrect combinations.
Thanks for any help.
Topic:
Developer Tools & Services
SubTopic:
Xcode
Tags:
Swift Packages
Developer Tools
Xcode
Objective-C
How can I get the region region currently used in the macOS App Store? Preferably via Swift libraries, but any command / function will suffice.
The following StoreKit property seems to always return the region for the Apple Account associated with my macOS user.
await Storefront.current?.countryCode
See the Apple docs.
My macOS Apple Account region is US; in the App Store, when I sign into a different Apple Account whose region is GB (UK), Storefront.current?.countryCode continues to return US, not GB (or UK).
I correctly see prices in pounds instead of in dollars, British spelling instead of American spelling, apps listed in my purchased tab for the UK (not the US) Apple Account, and, in the Account Settings dialog, the UK Apple Account email address, billing address & Country/Region set to United Kingdom.
I didn't get any relevant results from the following command lines:
defaults find GB
defaults find UK
defaults find uk-apple-id@example.com
defaults find uk-apple-id
The following didn't change after I signed into the UK Apple Account in the App Store:
$ defaults read com.apple.AppStoreComponents
{
ASCLocaleID = "en-US@calendar=gregorian";
}
Maybe Storefront.current?.countryCode only specifies the country code for the Storefront that will be used for in-app purchases, instead of for purchasing new apps from the App Store; maybe the former is tied to the Apple Account for the macOS user, instead of to the Apple Account for the App Store. If that's the case, what other mechanism can I use to obtain the country code for the App Store storefront?
How can I return the results of a Spotlight query synchronously from a Swift function?
I want to return a [String] that contains the items that match the query, one item per array element.
I specifically want to find all data for Spotlight items in the /Applications folder that have a kMDItemAppStoreAdamID (if there is a better predicate than kMDItemAppStoreAdamID > 0, please let me know).
The following should be the correct query:
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "kMDItemAppStoreAdamID > 0")
query.searchScopes = ["/Applications"]
I would like to do this for code that can run on macOS 10.13+, which precludes using Swift Concurrency. My project already uses the latest PromiseKit, so I assume that the solution should use that. A bonus solution using Swift Concurrency wouldn't hurt as I will probably switch over sometime in the future, but won't be able to switch soon.
I have written code that can retrieve the Spotlight data as the [String], but I don't know how to return it synchronously from a function; whatever I tried, the query hangs, presumably because I've called various run loop functions at the wrong places.
In case it matters, the app is a macOS command-line app using Swift 5.7 & Swift Argument Parser 1.5.0. The Spotlight data will be output only as text to stdout & stderr, not to any Apple UI elements.
In the Swift function at the end of this post, I use Scripting Bridge to have Finder delete a path. The variable result is a SBObject returned by the delete() function. I know that result somehow contains the new path of the deleted item in the trash folder, but I don't know how to nicely extract it as a single String.
If I print(String(describing: result)), I get output like:
<SBObject @0x0123456789ab: <class 'appf'> "AppName.app" of <class 'cfol'> ".Trash" of <class 'cfol'> "user" of <class 'cfol'> "Users" of startupDisk of application "Finder" (822)>
Is there any way to obtain the String "/Users/user/.Trash/AppName.app" from result without having to perform string parsing on the above output?
The Finder* types in the code below are from https://github.com/tingraldi/SwiftScripting/blob/master/Frameworks/FinderScripting/FinderScripting/Finder.swift
func trash(path: String) throws {
guard let finder: FinderApplication = SBApplication(bundleIdentifier: "com.apple.finder") else {
throw runtimeError("Failed to obtain Finder access: com.apple.finder does not exist")
}
guard let items = finder.items else {
throw runtimeError("Failed to obtain Finder access: finder.items does not exist")
}
let object = items().object(atLocation: URL(fileURLWithPath: path))
guard let item = object as? FinderItem else {
throw runtimeError(
"""
Failed to obtain Finder access: finder.items().object(atLocation: URL(fileURLWithPath: \
\"\(path)\") is a '\(type(of: object))' that does not conform to 'FinderItem'
"""
)
}
guard let delete = item.delete else {
throw runtimeError("Failed to obtain Finder access: FinderItem.delete does not exist")
}
let result = delete()
}
The code below is a simplified form of part of my code for my Swift Package Manager, Swift 5.6.1, PromiseKit 6.22.1, macOS command-line executable.
It accepts a Mac App Store app ID as the sole argument. If the argument corresponds to an app ID for an app that was installed from the Mac App Store onto your computer, the executable obtains some information from Spotlight via a NSMetadataQuery, then prints it to stdout.
I was only able to get the threading to work by calling RunLoop.main.run(). The only way I was able to allow the executable to return instead of being stuck forever on RunLoop.main.run() was to call exit(0) in the closure passed to Promise.done().
The exit(0) causes problems for testing. How can I allow the executable to exit without explicitly calling exit(0), and how can I improve the threading overall?
I cannot currently use Swift Concurrency (await/async/TaskGroup) because the executable must support macOS versions that don't support Swift Concurrency. A Swift Concurrency solution variant would be useful as additional info, though, because, sometime in the future, I might be able to drop support for macOS versions older than 10.15.
Thanks for any help.
import Foundation
import PromiseKit
guard CommandLine.arguments.count > 1 else {
print("Missing adamID argument")
exit(1)
}
guard let adamID = UInt64(CommandLine.arguments[1]) else {
print("adamID argument must be a UInt64")
exit(2)
}
_ = appInfo(forAdamID: adamID)
.done { appInfo in
if let jsonData = try? JSONSerialization.data(withJSONObject: appInfo),
let jsonString = String(data: jsonData, encoding: .utf8)
{
print(jsonString.replacingOccurrences(of: "\\/", with: "/"))
}
exit(0)
}
RunLoop.main.run()
func appInfo(forAdamID adamID: UInt64) -> Promise<[String: Any]> {
Promise { seal in
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "kMDItemAppStoreAdamID == %d", adamID)
query.searchScopes = ["/Applications"]
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
forName: NSNotification.Name.NSMetadataQueryDidFinishGathering,
object: query,
queue: .main
) { _ in
query.stop()
defer {
if let observer {
NotificationCenter.default.removeObserver(observer)
}
}
var appInfo: [String: Any] = [:]
for result in query.results {
if let result = result as? NSMetadataItem {
var attributes = ["kMDItemPath"]
attributes.append(contentsOf: result.attributes)
for attribute in attributes {
let value = result.value(forAttribute: attribute)
switch value {
case let date as Date:
appInfo[attribute] = ISO8601DateFormatter().string(from: date)
default:
appInfo[attribute] = value
}
}
}
}
seal.fulfill(appInfo)
}
DispatchQueue.main.async {
query.start()
}
}
}
If you "uninstall" an app from your Mac using the standard "uninstall" methodologies, does macOS ever run an uninstaller in the background to clean up files and/or folders outside the app's .app folder?
The standard "uninstall" methodologies that I know about are:
trashing an application's .app folder, then emptying the trash
long-clicking on an app in Launchpad, then clicking on its X while it jiggles
Are there any other standard methodologies to "uninstall" a macOS app?
Does any of the methodologies run any uninstall routine other than just deleting the .app folder from the file system (like maybe removing a folder under ~/Library/Caches)?
Are there any problems with running rm -rf <path-to-.app-folder> instead of trashing the app folder/Lauchpad jiggling to death the app?
Are there any differences between uninstall routines for apps installed from the Mac App Store versus apps installed by other means?
Topic:
App & System Services
SubTopic:
General
I want to use the Shortcuts app on macOS Monterey to modify M4A/AAC audio file metadata for files that I have ripped from CD to my local Music app library.
How can I use a regular expression on track titles to replace text that matches a certain regex pattern?
I have already written the regex, I just need help with the Shortcuts app.
I've started my shortcut with a "Find Music" action connected to a "Repeat with Each" loop action.
Within the loop, I use a "Get Details of Music" action to get the title, which is passed to a "Replace Text" action using my regex.
I just don't know how to write the correct new title into the music file's title/name metadata field. I tried a "Rename File" action with type "iTunes media" to try to set the "Title" field, but the field wasn't updated either in the Music app or in the M4A/AAC file itself. I couldn't find any other actions that seemed like they might be able to modify the field.
I'd prefer a solution that goes through the Music app / its APIs, rather than directly modifying the M4A on the file system, because the latter would force me to have the Music app scan for changed files after the files we're modified.
Topic:
App & System Services
SubTopic:
Automation & Scripting
Tags:
Apple Music API
Automator
Scripting
Shortcuts