From 9a1aeddb171734bc3d125c7215237ba61afc3ddd Mon Sep 17 00:00:00 2001 From: gsdali <51393997+gsdali@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:28:05 +0900 Subject: [PATCH 1/3] fix(deps): cap OCCTSwiftIO to its 1.0.x minor line (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit occtDep("OCCTSwiftIO", from: "1.0.0") only bounds by major version, so a lean consumer with no root-level override (e.g. OCCTMCP's execute_script workspace) floats OCCTSwiftIO to whatever 1.x is newest — now v1.5.0, which drags in the heavy mesh-IO stack (SwiftX/SwiftDXF/SwiftJWW/SwiftPMX/ThreeMF/ SwiftGLTF/...) and makes the graph unsolvable. This repo only uses OCCTSwiftIO for TopologyGraph.exportForML/exportJSON (GraphML/graphml verbs), a surface that has needed nothing past 1.0.x since it graduated. Adds occtDepUpToNextMinor (same helper OCCTMCP#53 already uses at its own root) and caps OCCTSwiftIO through it. Verified: swift package resolve now pins OCCTSwiftIO at 1.0.1 (was floating to 1.5.0) with none of the heavy mesh-IO checkouts pulled in; swift build succeeds. Co-Authored-By: Claude Opus 4.8 (1M context) --- Package.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index f7f6961..7fab4ec 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,19 @@ func occtDep(_ name: String, from version: String) -> Package.Dependency { return .package(url: "https://github.com/SecondMouseAU/\(name).git", from: Version(version)!) } +// As occtDep, but pins to the package's minor line instead of an open major range. Used to cap a +// transitive dependency whose newer minors pull deps we don't want in the graph — OCCTSwiftIO 1.1.0+ +// pulls in the heavy mesh-IO stack (SwiftX/SwiftDXF/SwiftJWW/SwiftPMX/ThreeMF/SwiftGLTF/…), which the +// narrow GraphML/graphml usage here doesn't need and which breaks resolution for lean consumers that +// have no root package to override from (ecosystem issue: OCCTSwiftScripts#69). +func occtDepUpToNextMinor(_ name: String, from version: String) -> Package.Dependency { + let manifestDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent().path + if FileManager.default.fileExists(atPath: manifestDir + "/../\(name)/Package.swift") { + return .package(path: "../\(name)") + } + return .package(url: "https://github.com/SecondMouseAU/\(name).git", .upToNextMinor(from: Version(version)!)) +} + let package = Package( name: "OCCTSwiftScripts", platforms: [ @@ -92,7 +105,9 @@ let package = Package( // v0.171.0 hoisted them out of the kernel. Pulled into GraphML and // graphml verbs only — the rest of the package keeps its existing // ScriptManifest type (with the `graphs` field) from ScriptHarness. - occtDep("OCCTSwiftIO", from: "1.0.0"), + // Capped to the 1.0.x minor line (occtDepUpToNextMinor): 1.1.0+ pulls in the + // heavy mesh-IO stack this narrow usage doesn't need (#69). + occtDepUpToNextMinor("OCCTSwiftIO", from: "1.0.0"), ], targets: [ .target( From bc07902addf71d5229df2625021c0d0e2913745e Mon Sep 17 00:00:00 2001 From: gsdali <51393997+gsdali@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:33:28 +0900 Subject: [PATCH 2/3] fix(deps): guard sibling-path detection against SwiftPM's own checkout layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The occtDep sibling probe (..//Package.swift) is filesystem-based and fires whenever a same-named directory happens to exist next to this manifest — including when THIS manifest is itself a transitively-resolved SwiftPM checkout under .build/checkouts/OCCTSwiftScripts, where SwiftPM lays every other dependency out as a flat sibling under the same checkouts/ directory. Once .build/checkouts/OCCTSwiftIO exists (as a side effect of resolving it), ../OCCTSwiftIO spuriously matches, flipping this manifest's own dependency declaration from url to path mid-resolution. SwiftPM then reports the whole graph as unresolvable ('exhausted attempts to resolve the dependencies graph') for any lean consumer that pulls OCCTSwiftScripts in transitively — verified this is the actual remaining failure mode of #69 even after the OCCTSwiftIO minor cap, via a standalone repro package depending only on OCCTSwiftScripts's ScriptHarness product (mirroring OCCTMCP's execute_script workspace). Guards the sibling check on the manifest's own directory NOT being inside a '/checkouts/' path — real local dev clones (~/Projects/) never are. Co-Authored-By: Claude Opus 4.8 (1M context) --- Package.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 7fab4ec..a1fe458 100644 --- a/Package.swift +++ b/Package.swift @@ -6,9 +6,23 @@ import Foundation // OCCT ecosystem SHARES the single OCCTSwift/Libraries/OCCT.xcframework instead of each repo // extracting its own 1.3 GB copy. CI / fresh clones (no sibling) use the URL pin. `#filePath`-relative // so it's independent of build CWD. +// Only trust a sibling checkout when THIS manifest is a real local dev clone — never when this +// manifest is itself a transitively-resolved SwiftPM checkout (.build/checkouts//Package.swift). +// SwiftPM lays every dependency's checkout out flat under one shared `checkouts/` directory, so once +// e.g. `.build/checkouts/OCCTSwiftIO` exists, `../OCCTSwiftIO` relative to +// `.build/checkouts/OCCTSwiftScripts` spuriously "exists" too — flipping this manifest's own +// declaration from url to path *during* the resolution process that created that checkout. SwiftPM +// then sees a non-deterministic manifest and reports the whole graph unresolvable ("exhausted +// attempts to resolve the dependencies graph") for every lean consumer that pulls this package in +// transitively — the actual mechanism behind ecosystem issue #69, beyond the OCCTSwiftIO version cap. +private func isRealLocalSibling(_ manifestDir: String, _ name: String) -> Bool { + guard !manifestDir.contains("/checkouts/") else { return false } + return FileManager.default.fileExists(atPath: manifestDir + "/../\(name)/Package.swift") +} + func occtDep(_ name: String, from version: String) -> Package.Dependency { let manifestDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent().path - if FileManager.default.fileExists(atPath: manifestDir + "/../\(name)/Package.swift") { + if isRealLocalSibling(manifestDir, name) { return .package(path: "../\(name)") } return .package(url: "https://github.com/SecondMouseAU/\(name).git", from: Version(version)!) @@ -21,7 +35,7 @@ func occtDep(_ name: String, from version: String) -> Package.Dependency { // have no root package to override from (ecosystem issue: OCCTSwiftScripts#69). func occtDepUpToNextMinor(_ name: String, from version: String) -> Package.Dependency { let manifestDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent().path - if FileManager.default.fileExists(atPath: manifestDir + "/../\(name)/Package.swift") { + if isRealLocalSibling(manifestDir, name) { return .package(path: "../\(name)") } return .package(url: "https://github.com/SecondMouseAU/\(name).git", .upToNextMinor(from: Version(version)!)) From 054a6daa74deaed704f29d42d63257ed8ed3554c Mon Sep 17 00:00:00 2001 From: gsdali <51393997+gsdali@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:08:49 +0900 Subject: [PATCH 3/3] chore(deps): align checkout-guard with OCCTSwiftIO's established /.build/ convention Same fix, just matching the guard string OCCTSwiftIO already adopted (2026-06-23) for consistency across the fleet being swept with this guard. Co-Authored-By: Claude Opus 4.8 (1M context) --- Package.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index a1fe458..bc6fa1d 100644 --- a/Package.swift +++ b/Package.swift @@ -7,16 +7,18 @@ import Foundation // extracting its own 1.3 GB copy. CI / fresh clones (no sibling) use the URL pin. `#filePath`-relative // so it's independent of build CWD. // Only trust a sibling checkout when THIS manifest is a real local dev clone — never when this -// manifest is itself a transitively-resolved SwiftPM checkout (.build/checkouts//Package.swift). -// SwiftPM lays every dependency's checkout out flat under one shared `checkouts/` directory, so once +// manifest is itself a transitively-resolved SwiftPM checkout under a consumer's `.build/`. SwiftPM +// lays every dependency's checkout out flat under one shared `.build/checkouts/` directory, so once // e.g. `.build/checkouts/OCCTSwiftIO` exists, `../OCCTSwiftIO` relative to // `.build/checkouts/OCCTSwiftScripts` spuriously "exists" too — flipping this manifest's own // declaration from url to path *during* the resolution process that created that checkout. SwiftPM // then sees a non-deterministic manifest and reports the whole graph unresolvable ("exhausted // attempts to resolve the dependencies graph") for every lean consumer that pulls this package in // transitively — the actual mechanism behind ecosystem issue #69, beyond the OCCTSwiftIO version cap. +// Same guard OCCTSwiftIO adopted (2026-06-23, "don't path-link siblings when resolved under a +// consumer's .build") — kept as `/.build/` here for consistency across the fleet. private func isRealLocalSibling(_ manifestDir: String, _ name: String) -> Bool { - guard !manifestDir.contains("/checkouts/") else { return false } + guard !manifestDir.contains("/.build/") else { return false } return FileManager.default.fileExists(atPath: manifestDir + "/../\(name)/Package.swift") }