From 041bd50257f59970a9511d214f82505f34651f75 Mon Sep 17 00:00:00 2001 From: Michael Gerasymenko Date: Thu, 26 Jun 2025 14:35:17 +0200 Subject: [PATCH] Allow for workspaces containing a package --- .../DependencyGraph.swift | 25 +++++++++++++-- .../PackageAndWorkspace/Package.swift | 26 +++++++++++++++ .../contents.xcworkspacedata | 10 ++++++ .../PackageMetadataTests.swift | 32 +++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Package.swift create mode 100644 Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Workspace.xcworkspace/contents.xcworkspacedata diff --git a/Sources/DependencyCalculator/DependencyGraph.swift b/Sources/DependencyCalculator/DependencyGraph.swift index ad0ad3d..e50c93e 100644 --- a/Sources/DependencyCalculator/DependencyGraph.swift +++ b/Sources/DependencyCalculator/DependencyGraph.swift @@ -66,12 +66,33 @@ extension PBXNativeTarget { } extension WorkspaceInfo { + /// Checks if the root Package.swift should be evaluated. It depends if there is a project file already. Project file is considered a higher level definition and should be parsed first. + private static func shouldIncludeRootPackage(at path: Path) throws -> Bool { + switch path.extension { + case "xcodeproj": + return false + case "xcworkspace": + let workspace = try XCWorkspace(path: path) + let projects = try workspace.allProjects(basePath: path.parent()) + + if projects.contains(where: { (_, path) in + path.contains("xcodeproj") + }) { + return false + } + else { + return true + } + default: + return true + } + } + public static func parseWorkspace(at path: Path, config: WorkspaceInfo.AdditionalConfig? = nil, exclude: [String]) throws -> WorkspaceInfo { - let includeRootPackage = !Set(["xcworkspace", "xcodeproj"]).contains(path.extension) - + let includeRootPackage = try shouldIncludeRootPackage(at: path) var (packageWorkspaceInfo, packages) = try parsePackages(in: path, includeRootPackage: includeRootPackage, exclude: exclude) var resultDependencies = packageWorkspaceInfo.dependencyStructure diff --git a/Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Package.swift b/Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Package.swift new file mode 100644 index 0000000..6c26ded --- /dev/null +++ b/Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "APackage", + platforms: [.iOS(.v15)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "APackage", + targets: ["APackage"]), + ], + dependencies: [], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "APackage"), + .testTarget( + name: "APackageTests", + dependencies: ["APackage"] + ), + ] +) diff --git a/Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Workspace.xcworkspace/contents.xcworkspacedata b/Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Workspace.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e4dc10b --- /dev/null +++ b/Tests/DependencyCalculatorTests/ExamplePackages/PackageAndWorkspace/Workspace.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Tests/DependencyCalculatorTests/PackageMetadataTests.swift b/Tests/DependencyCalculatorTests/PackageMetadataTests.swift index 2bbd346..dc6fdd6 100644 --- a/Tests/DependencyCalculatorTests/PackageMetadataTests.swift +++ b/Tests/DependencyCalculatorTests/PackageMetadataTests.swift @@ -80,4 +80,36 @@ final class PackageMetadataTests: XCTestCase { basePath + "Sources" + "SelectiveTestingCore" ])) } + + func testPackageAndWorkspace() async throws { + // given + guard let exampleInBundle = Bundle.module.path(forResource: "ExamplePackages", ofType: "") else { + fatalError("Missing ExamplePackages in TestBundle") + } + // when + let basePath = Path(exampleInBundle) + "PackageAndWorkspace" + let metadata = try PackageTargetMetadata.parse(at: basePath) + + // then + XCTAssertEqual(metadata.count, 2) + let first = metadata[0] + XCTAssertEqual(first.name, "APackage") + XCTAssertEqual(first.path, basePath) + XCTAssertEqual(first.dependsOn, Set([])) + XCTAssertEqual(first.affectedBy, Set([ + basePath + "Package.swift", + basePath + "Package.resolved", + basePath + "Sources" + "APackage" + ])) + + let second = metadata[1] + XCTAssertEqual(second.name, "APackageTests") + XCTAssertEqual(second.path, basePath) + XCTAssertEqual(second.dependsOn.count, 1) + XCTAssertEqual(second.affectedBy, Set([ + basePath + "Package.swift", + basePath + "Package.resolved", + basePath + "Tests" + "APackageTests" + ])) + } }