Skip to content

Commit efc03c1

Browse files
committed
Use SMAppService to setup launch agent in macOS 13
1 parent 45007c8 commit efc03c1

File tree

3 files changed

+101
-41
lines changed

3 files changed

+101
-41
lines changed

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
C89E75C32A46FB32000DD64F /* AppDelegate+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89E75C22A46FB32000DD64F /* AppDelegate+Menu.swift */; };
4242
C8C8B60929AFA35F00034BEE /* CopilotForXcodeExtensionService.app in Embed XPCService */ = {isa = PBXBuildFile; fileRef = C861E60E2994F6070056CB02 /* CopilotForXcodeExtensionService.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4343
C8DCF00029CE11D500FDDDD7 /* ChatWithSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DCEFFF29CE11D500FDDDD7 /* ChatWithSelection.swift */; };
44+
C8F1032B2A7A39D700D28F4F /* launchAgent.plist in Copy Launch Agent */ = {isa = PBXBuildFile; fileRef = C8F103292A7A365000D28F4F /* launchAgent.plist */; };
4445
/* End PBXBuildFile section */
4546

4647
/* Begin PBXContainerItemProxy section */
@@ -121,6 +122,17 @@
121122
name = "Embed Service";
122123
runOnlyForDeploymentPostprocessing = 0;
123124
};
125+
C8F1032A2A7A38D200D28F4F /* Copy Launch Agent */ = {
126+
isa = PBXCopyFilesBuildPhase;
127+
buildActionMask = 12;
128+
dstPath = Contents/Library/LaunchAgents;
129+
dstSubfolderSpec = 1;
130+
files = (
131+
C8F1032B2A7A39D700D28F4F /* launchAgent.plist in Copy Launch Agent */,
132+
);
133+
name = "Copy Launch Agent";
134+
runOnlyForDeploymentPostprocessing = 0;
135+
};
124136
/* End PBXCopyFilesBuildPhase section */
125137

126138
/* Begin PBXFileReference section */
@@ -171,6 +183,7 @@
171183
C89E75C22A46FB32000DD64F /* AppDelegate+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Menu.swift"; sourceTree = "<group>"; };
172184
C8CD828229B88006008D044D /* TestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestPlan.xctestplan; sourceTree = "<group>"; };
173185
C8DCEFFF29CE11D500FDDDD7 /* ChatWithSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatWithSelection.swift; sourceTree = "<group>"; };
186+
C8F103292A7A365000D28F4F /* launchAgent.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = launchAgent.plist; sourceTree = "<group>"; };
174187
/* End PBXFileReference section */
175188

176189
/* Begin PBXFrameworksBuildPhase section */
@@ -250,6 +263,7 @@
250263
C8520308293D805800460097 /* README.md */,
251264
C82E38492A1F025F00D4EADF /* LICENSE */,
252265
C83E5DED2A38CD8C0071506D /* Makefile */,
266+
C8F103292A7A365000D28F4F /* launchAgent.plist */,
253267
C81E867D296FE4420026E908 /* Version.xcconfig */,
254268
C81458AD293A009600135263 /* Config.xcconfig */,
255269
C81458AE293A009800135263 /* Config.debug.xcconfig */,
@@ -354,6 +368,7 @@
354368
C814589F2939EFDC00135263 /* Embed Foundation Extensions */,
355369
C8520306293CF0EF00460097 /* Embed XPCService */,
356370
C8C8B60829AFA32800034BEE /* Embed Service */,
371+
C8F1032A2A7A38D200D28F4F /* Copy Launch Agent */,
357372
);
358373
buildRules = (
359374
);

Core/Sources/LaunchAgentManager/LaunchAgentManager.swift

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
2+
import ServiceManagement
23

3-
#warning("TODO: Migrate to SMAppService")
44
public struct LaunchAgentManager {
55
let lastLaunchAgentVersionKey = "LastLaunchAgentVersion"
66
let serviceIdentifier: String
@@ -23,67 +23,96 @@ public struct LaunchAgentManager {
2323
}
2424

2525
public func setupLaunchAgentForTheFirstTimeIfNeeded() async throws {
26-
if UserDefaults.standard.integer(forKey: lastLaunchAgentVersionKey) < 40 {
26+
if #available(macOS 13, *) {
27+
await removeObsoleteLaunchAgent()
2728
try await setupLaunchAgent()
28-
return
29+
} else {
30+
if UserDefaults.standard.integer(forKey: lastLaunchAgentVersionKey) < 40 {
31+
try await setupLaunchAgent()
32+
return
33+
}
34+
guard !FileManager.default.fileExists(atPath: launchAgentPath) else { return }
35+
try await setupLaunchAgent()
36+
await removeObsoleteLaunchAgent()
2937
}
30-
guard !FileManager.default.fileExists(atPath: launchAgentPath) else { return }
31-
try await setupLaunchAgent()
32-
await removeObsoleteLaunchAgent()
3338
}
3439

3540
public func setupLaunchAgent() async throws {
36-
let content = """
37-
<?xml version="1.0" encoding="UTF-8"?>
38-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39-
<plist version="1.0">
40-
<dict>
41-
<key>Label</key>
42-
<string>\(serviceIdentifier)</string>
43-
<key>Program</key>
44-
<string>\(executablePath)</string>
45-
<key>MachServices</key>
41+
if #available(macOS 13, *) {
42+
let launchAgent = SMAppService.agent(plistName: "launchAgent.plist")
43+
try launchAgent.register()
44+
} else {
45+
let content = """
46+
<?xml version="1.0" encoding="UTF-8"?>
47+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
48+
<plist version="1.0">
4649
<dict>
47-
<key>\(serviceIdentifier)</key>
48-
<true/>
49-
</dict>
50-
<key>AssociatedBundleIdentifiers</key>
51-
<array>
52-
<string>\(bundleIdentifier)</string>
50+
<key>Label</key>
5351
<string>\(serviceIdentifier)</string>
54-
</array>
55-
</dict>
56-
</plist>
57-
"""
58-
if !FileManager.default.fileExists(atPath: launchAgentDirURL.path) {
59-
try FileManager.default.createDirectory(
60-
at: launchAgentDirURL,
61-
withIntermediateDirectories: false
52+
<key>Program</key>
53+
<string>\(executablePath)</string>
54+
<key>MachServices</key>
55+
<dict>
56+
<key>\(serviceIdentifier)</key>
57+
<true/>
58+
</dict>
59+
<key>AssociatedBundleIdentifiers</key>
60+
<array>
61+
<string>\(bundleIdentifier)</string>
62+
<string>\(serviceIdentifier)</string>
63+
</array>
64+
</dict>
65+
</plist>
66+
"""
67+
if !FileManager.default.fileExists(atPath: launchAgentDirURL.path) {
68+
try FileManager.default.createDirectory(
69+
at: launchAgentDirURL,
70+
withIntermediateDirectories: false
71+
)
72+
}
73+
FileManager.default.createFile(
74+
atPath: launchAgentPath,
75+
contents: content.data(using: .utf8)
6276
)
77+
try await launchctl("load", launchAgentPath)
6378
}
64-
FileManager.default.createFile(
65-
atPath: launchAgentPath,
66-
contents: content.data(using: .utf8)
67-
)
79+
6880
let buildNumber = (Bundle.main.infoDictionary?["CFBundleVersion"] as? String)
6981
.flatMap(Int.init)
7082
UserDefaults.standard.set(buildNumber, forKey: lastLaunchAgentVersionKey)
71-
try await launchctl("load", launchAgentPath)
7283
}
7384

7485
public func removeLaunchAgent() async throws {
75-
try await launchctl("unload", launchAgentPath)
76-
try FileManager.default.removeItem(atPath: launchAgentPath)
86+
if #available(macOS 13, *) {
87+
let launchAgent = SMAppService.agent(plistName: "launchAgent.plist")
88+
try await launchAgent.unregister()
89+
} else {
90+
try await launchctl("unload", launchAgentPath)
91+
try FileManager.default.removeItem(atPath: launchAgentPath)
92+
}
7793
}
7894

7995
public func reloadLaunchAgent() async throws {
80-
try await helper("reload-launch-agent", "--service-identifier", serviceIdentifier)
96+
if #unavailable(macOS 13) {
97+
try await helper("reload-launch-agent", "--service-identifier", serviceIdentifier)
98+
}
8199
}
82100

83101
public func removeObsoleteLaunchAgent() async {
84-
let path = launchAgentPath.replacingOccurrences(of: "ExtensionService", with: "XPCService")
85-
if FileManager.default.fileExists(atPath: path) {
86-
try? FileManager.default.removeItem(atPath: path)
102+
if #available(macOS 13, *) {
103+
let path = launchAgentPath
104+
if FileManager.default.fileExists(atPath: path) {
105+
try? await launchctl("unload", path)
106+
try? FileManager.default.removeItem(atPath: path)
107+
}
108+
} else {
109+
let path = launchAgentPath.replacingOccurrences(
110+
of: "ExtensionService",
111+
with: "XPCService"
112+
)
113+
if FileManager.default.fileExists(atPath: path) {
114+
try? FileManager.default.removeItem(atPath: path)
115+
}
87116
}
88117
}
89118
}
@@ -144,3 +173,4 @@ private func launchctl(_ args: String...) async throws {
144173
struct E: Error, LocalizedError {
145174
var errorDescription: String?
146175
}
176+

launchAgent.plist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>Label</key>
6+
<string>com.intii.CopilotForXcode.ExtensionService</string>
7+
<key>Program</key>
8+
<string>/Applications/Copilot for Xcode.app/Contents/Applications/CopilotForXcodeExtensionService.app/Contents/MacOS/CopilotForXcodeExtensionService</string>
9+
<key>MachServices</key>
10+
<dict>
11+
<key>com.intii.CopilotForXcode.ExtensionService</key>
12+
<true/>
13+
</dict>
14+
</dict>
15+
</plist>

0 commit comments

Comments
 (0)