From c668f922ea819e26926b67e748bfd9685ad8d347 Mon Sep 17 00:00:00 2001 From: Robert Connell Date: Fri, 7 Nov 2025 19:18:37 -0500 Subject: [PATCH 1/3] Avoid expensive string array operations when performing isDescendent checks on AbsolutePath --- Sources/Basics/FileSystem/AbsolutePath.swift | 28 +++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Sources/Basics/FileSystem/AbsolutePath.swift b/Sources/Basics/FileSystem/AbsolutePath.swift index c472e5c0608..260b81d4028 100644 --- a/Sources/Basics/FileSystem/AbsolutePath.swift +++ b/Sources/Basics/FileSystem/AbsolutePath.swift @@ -220,7 +220,7 @@ extension AbsolutePath { /// This method is strictly syntactic and does not access the file system /// in any way. public func isAncestor(of descendant: AbsolutePath) -> Bool { - self.underlying.isAncestor(of: descendant.underlying) + descendant.isDescendant(of: self) } /// Returns true if the path is an ancestor of or equal to the given path. @@ -228,7 +228,7 @@ extension AbsolutePath { /// This method is strictly syntactic and does not access the file system /// in any way. public func isAncestorOfOrEqual(to descendant: AbsolutePath) -> Bool { - self.underlying.isAncestorOfOrEqual(to: descendant.underlying) + descendant.isDescendantOfOrEqual(to: self) } /// Returns true if the path is a descendant of the given path. @@ -236,7 +236,24 @@ extension AbsolutePath { /// This method is strictly syntactic and does not access the file system /// in any way. public func isDescendant(of ancestor: AbsolutePath) -> Bool { - self.underlying.isDescendant(of: ancestor.underlying) + let selfPath = self.pathString + let ancestorPath = ancestor.pathString + + guard selfPath.count > ancestorPath.count else { + return false + } + + guard selfPath.hasPrefix(ancestorPath) else { + return false + } + + if ancestorPath.hasSuffix("/") { + return true + } + + // Handle non-root paths by ensuring the character after the prefix is a separator + let indexAfterPrefix = selfPath.index(selfPath.startIndex, offsetBy: ancestorPath.count) + return selfPath[indexAfterPrefix] == "/" } /// Returns true if the path is a descendant of or equal to the given path. @@ -244,7 +261,10 @@ extension AbsolutePath { /// This method is strictly syntactic and does not access the file system /// in any way. public func isDescendantOfOrEqual(to ancestor: AbsolutePath) -> Bool { - self.underlying.isDescendantOfOrEqual(to: ancestor.underlying) + if self == ancestor { + return true + } + return self.isDescendant(of: ancestor) } } From 9fff80ae906ed03f17ea4fdc56c42a57367f5c85 Mon Sep 17 00:00:00 2001 From: Robert Connell Date: Sat, 8 Nov 2025 16:02:26 -0500 Subject: [PATCH 2/3] Augment error description --- Sources/PackageLoading/PackageBuilder.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 77169042aa0..5d544f4324a 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -992,7 +992,11 @@ public final class PackageBuilder { let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangModule.defaultPublicHeadersComponent let publicHeadersPath = try potentialModule.path.appending(RelativePath(validating: publicHeaderComponent)) guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { - throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) + let publicHeadersPathString = publicHeadersPath.pathString + let potentialModulePathString = potentialModule.path.pathString + let potentialModuleName = potentialModule.name + let errorMessage = "\(publicHeadersPathString) isDescendantOfOrEqual \(potentialModulePathString) - potentialModule.name(\(potentialModuleName))" + throw ModuleError.invalidPublicHeadersDirectory(errorMessage) } let sourcesBuilder = TargetSourcesBuilder( From 68fe3c8614d226a985730a912e61a370ccfca958 Mon Sep 17 00:00:00 2001 From: Robert Connell Date: Sun, 9 Nov 2025 22:56:49 -0500 Subject: [PATCH 3/3] Check for windows path separators. --- Sources/Basics/FileSystem/AbsolutePath.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/Basics/FileSystem/AbsolutePath.swift b/Sources/Basics/FileSystem/AbsolutePath.swift index 260b81d4028..08c1237f49a 100644 --- a/Sources/Basics/FileSystem/AbsolutePath.swift +++ b/Sources/Basics/FileSystem/AbsolutePath.swift @@ -247,13 +247,19 @@ extension AbsolutePath { return false } - if ancestorPath.hasSuffix("/") { +#if os(Windows) + let pathSeparator: Character = "\\" +#else + let pathSeparator: Character = "/" +#endif + + if ancestorPath.hasSuffix(String(pathSeparator)) { return true } // Handle non-root paths by ensuring the character after the prefix is a separator let indexAfterPrefix = selfPath.index(selfPath.startIndex, offsetBy: ancestorPath.count) - return selfPath[indexAfterPrefix] == "/" + return selfPath[indexAfterPrefix] == pathSeparator } /// Returns true if the path is a descendant of or equal to the given path.