Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion GraphcodeKit/Sources/Sessions/ZmxLocator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import Foundation
public enum ZmxLocator {
public static var binaryURL: URL {
#if os(Windows)
SupportDirectory.binDirectory.appendingPathComponent("zmx.exe")
resolve(
candidates(
executableDirectory: Bundle.main.executableURL?.deletingLastPathComponent(),
supportBinDirectory: SupportDirectory.binDirectory,
executableName: "zmx.exe"),
isExecutable: FileManager.default.isExecutableFile(atPath:))
#else
SupportDirectory.binDirectory.appendingPathComponent("zmx")
#endif
Expand All @@ -16,4 +21,20 @@ public enum ZmxLocator {
public static var isInstalled: Bool {
FileManager.default.isExecutableFile(atPath: binaryURL.path)
}

/// The places `zmx` may live, most preferred first; the last is the support directory's
/// `bin`, which is also what is reported when none of them exists. The Windows package
/// installs `zmx.exe` beside `graphcoded.exe` and never copies it into the support
/// directory, so the running executable's own directory comes first.
static func candidates(
executableDirectory: URL?, supportBinDirectory: URL, executableName: String
) -> [URL] {
let supportBinary = supportBinDirectory.appendingPathComponent(executableName)
guard let executableDirectory else { return [supportBinary] }
return [executableDirectory.appendingPathComponent(executableName), supportBinary]
}

static func resolve(_ candidates: [URL], isExecutable: (String) -> Bool) -> URL {
candidates.first { isExecutable($0.path) } ?? candidates[candidates.count - 1]
}
}
20 changes: 20 additions & 0 deletions graphcode/Tests/SupportDirectoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ struct SupportDirectoryTests {
#expect(SupportDirectory.binDirectory.deletingLastPathComponent() == root)
}

@Test
func zmxResolutionPrefersAnExecutableSiblingThenTheSupportBin() {
// Windows packages zmx.exe beside graphcoded; macOS keeps using the support bin, and
// `binaryURL` there is unchanged (asserted above).
let install = URL(fileURLWithPath: "/opt/graphcode/bin", isDirectory: true)
let support = SupportDirectory.binDirectory
let candidates = ZmxLocator.candidates(
executableDirectory: install, supportBinDirectory: support, executableName: "zmx")
let sibling = install.appendingPathComponent("zmx")
let supportZmx = support.appendingPathComponent("zmx")

#expect(candidates == [sibling, supportZmx])
#expect(ZmxLocator.resolve(candidates) { $0 == sibling.path } == sibling)
#expect(ZmxLocator.resolve(candidates) { $0 == supportZmx.path } == supportZmx)
#expect(ZmxLocator.resolve(candidates) { _ in false } == supportZmx)
#if !os(Windows)
#expect(ZmxLocator.binaryURL == supportZmx)
#endif
}

@Test
func aBackslashOverrideRemainsRelativeOnDarwin() {
let home = URL(fileURLWithPath: "/Users/test-user", isDirectory: true)
Expand Down
28 changes: 28 additions & 0 deletions windows-tests/WindowsDaemonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,34 @@ final class WindowsDaemonTests: XCTestCase {
XCTAssertEqual(ZmxLocator.binaryURL.lastPathComponent, "zmx.exe")
}

// The packaged daemon ships zmx.exe beside itself under %LOCALAPPDATA%\GraphCode;
// nothing copies it into the support directory, so the support bin alone left the
// daemon unable to launch, message or kill any loop session (beta9 Dev Box).
func testZmxLocatorPrefersThePackagedSiblingOfTheRunningExecutable() {
let install = URL(fileURLWithPath: #"C:\Users\u\AppData\Local\GraphCode\current\bin"#)
let support = URL(fileURLWithPath: #"C:\Users\u\.graphcode\bin"#)
let candidates = ZmxLocator.candidates(
executableDirectory: install, supportBinDirectory: support, executableName: "zmx.exe")
let sibling = install.appendingPathComponent("zmx.exe")
let supportZmx = support.appendingPathComponent("zmx.exe")

XCTAssertEqual(candidates, [sibling, supportZmx])
XCTAssertEqual(ZmxLocator.resolve(candidates) { $0 == sibling.path }, sibling)
XCTAssertEqual(
ZmxLocator.resolve(candidates) { [sibling.path, supportZmx.path].contains($0) }, sibling)
XCTAssertEqual(ZmxLocator.resolve(candidates) { $0 == supportZmx.path }, supportZmx)
// Neither present: report the support bin, so isInstalled stays false.
XCTAssertEqual(ZmxLocator.resolve(candidates) { _ in false }, supportZmx)
}

func testZmxLocatorWithoutAnExecutableDirectoryUsesTheSupportBin() {
let support = URL(fileURLWithPath: #"C:\Users\u\.graphcode\bin"#)
XCTAssertEqual(
ZmxLocator.candidates(
executableDirectory: nil, supportBinDirectory: support, executableName: "zmx.exe"),
[support.appendingPathComponent("zmx.exe")])
}

func testWindowsProviderProbeUsesWhere() {
let invocation = ProviderPath.probeInvocation(for: "copilot")
XCTAssertEqual(URL(fileURLWithPath: invocation[0]).lastPathComponent, "where.exe")
Expand Down
Loading