Skip to content
Closed
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
5 changes: 2 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ jobs:
run: swift test -v

test-linux:
if: false
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: swift-actions/setup-swift@v2
- uses: SwiftyLab/setup-swift@latest
with:
swift-version: "6.1.0"
- name: Get swift version
Expand Down
21 changes: 11 additions & 10 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 6 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.6
// swift-tools-version: 6.0

import PackageDescription

Expand Down Expand Up @@ -28,19 +28,16 @@ let targets: [PackageDescription.Target] = [
"TestConfigurator",
"Git",
"PathKit",
"Rainbow",
"Yams",
.product(name: "ArgumentParser", package: "swift-argument-parser")]),
.target(name: "DependencyCalculator",
dependencies: ["Workspace", "PathKit", "SelectiveTestLogger", "Git"]),
dependencies: ["Workspace", "PathKit", "Git", .product(name: "Logging", package: "swift-log")]),
.target(name: "TestConfigurator",
dependencies: ["Workspace", "PathKit", "SelectiveTestLogger"]),
dependencies: ["Workspace", "PathKit", .product(name: "Logging", package: "swift-log")]),
.target(name: "Workspace",
dependencies: ["XcodeProj", "SelectiveTestLogger"]),
dependencies: ["XcodeProj", .product(name: "Logging", package: "swift-log")]),
.target(name: "Git",
dependencies: ["SelectiveTestShell", "SelectiveTestLogger", "PathKit"]),
.target(name: "SelectiveTestLogger",
dependencies: ["Rainbow"]),
dependencies: ["SelectiveTestShell", "PathKit", .product(name: "Logging", package: "swift-log")]),
.target(name: "SelectiveTestShell"),
.testTarget(
name: "SelectiveTestingTests",
Expand Down Expand Up @@ -77,8 +74,8 @@ let package = Package(
.package(url: "https://github.com/tuist/XcodeProj.git", .upToNextMajor(from: "9.0.2")),
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.2.0")),
.package(url: "https://github.com/kylef/PathKit.git", .upToNextMinor(from: "1.0.0")),
.package(url: "https://github.com/onevcat/Rainbow", .upToNextMajor(from: "4.0.0")),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.5"),
.package(url: "https://github.com/apple/swift-log", from: "1.6.0")
],
targets: targets
)
13 changes: 6 additions & 7 deletions Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import PackagePlugin

@main
struct SelectiveTestingPlugin: CommandPlugin {
private func run(_ executable: String, arguments: [String] = []) throws {
let executableURL = URL(fileURLWithPath: executable)
private func run(_ executable: URL, arguments: [String] = []) throws {

let process = Process()
process.executableURL = executableURL
process.executableURL = executable
process.arguments = arguments

try process.run()
Expand All @@ -24,10 +23,10 @@ struct SelectiveTestingPlugin: CommandPlugin {
}

func performCommand(context: PluginContext, arguments: [String]) async throws {
FileManager().changeCurrentDirectoryPath(context.package.directory.string)
FileManager.default.changeCurrentDirectoryPath(context.package.directoryURL.path)
let tool = try context.tool(named: "xcode-selective-test")

try run(tool.path.string, arguments: arguments)
try run(tool.url, arguments: arguments)
}
}

Expand All @@ -36,7 +35,7 @@ struct SelectiveTestingPlugin: CommandPlugin {

extension SelectiveTestingPlugin: XcodeCommandPlugin {
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
FileManager().changeCurrentDirectoryPath(context.xcodeProject.directory.string)
FileManager.default.changeCurrentDirectoryPath(context.xcodeProject.directoryURL.path)

let tool = try context.tool(named: "xcode-selective-test")

Expand Down Expand Up @@ -66,7 +65,7 @@ struct SelectiveTestingPlugin: CommandPlugin {
}
}

try run(tool.path.string, arguments: toolArguments)
try run(tool.url, arguments: toolArguments)
}
}
#endif
Expand Down
6 changes: 3 additions & 3 deletions Sources/DependencyCalculator/ConcurrentMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Foundation

final class ThreadSafe<A> {
final class ThreadSafe<A: Sendable>: @unchecked Sendable {
private var _value: A
private let queue = DispatchQueue(label: "ThreadSafe")
init(_ value: A) {
Expand All @@ -22,8 +22,8 @@ final class ThreadSafe<A> {
}
}

extension Array {
func concurrentMap<B>(_ transform: @escaping (Element) -> B) -> [B] {
extension Array where Element: Sendable {
func concurrentMap<B: Sendable>(_ transform: @escaping @Sendable (Element) -> B) -> [B] {
let result = ThreadSafe([B?](repeating: nil, count: count))
DispatchQueue.concurrentPerform(iterations: count) { idx in
let element = self[idx]
Expand Down
4 changes: 2 additions & 2 deletions Sources/DependencyCalculator/DependencyCalculator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Foundation
import PathKit
import SelectiveTestLogger
import Logging
import Workspace

public extension WorkspaceInfo {
Expand All @@ -18,7 +18,7 @@ public extension WorkspaceInfo {
} else if let targetFromFolder = targetForFolder(path) {
result.insert(targetFromFolder)
} else {
Logger.message("Changed file at \(path) appears not to belong to any target")
logger.info("Changed file at \(path) appears not to belong to any target")
}
}
if incldueIndirectlyAffected {
Expand Down
41 changes: 25 additions & 16 deletions Sources/DependencyCalculator/DependencyGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@

import Foundation
import Git
import PathKit
import SelectiveTestLogger
@preconcurrency import PathKit
import Logging
import SelectiveTestShell
import Workspace
import XcodeProj

let logger = Logger(label: "cx.gera.XcodeSelectiveTesting")

extension PBXBuildFile {
func paths(projectFolder: Path) -> [Path] {
guard let file else {
Logger.warning("PBXBuildFile without file: self=\(self), \n self.product=\(String(describing: product))")
logger.warning("PBXBuildFile without file: self=\(self), \n self.product=\(String(describing: product))")
return []
}

Expand All @@ -29,7 +31,7 @@ extension PBXBuildFile {
}

guard paths.count > 0 else {
Logger.warning("File without paths: self=\(self), \n self.file=\(String(describing: file)), \n self.product=\(String(describing: product))")
logger.warning("File without paths: self=\(self), \n self.file=\(String(describing: file)), \n self.product=\(String(describing: product))")
return []
}

Expand Down Expand Up @@ -152,15 +154,22 @@ extension WorkspaceInfo {
dependencyStructure: resultDependencies,
candidateTestPlans: candidateTestPlans)
if let config {
let additionalBasePath: Path
if path.extension == "xcworkspace" || path.extension == "xcodeproj" {
additionalBasePath = path.parent()
} else {
additionalBasePath = path
}
// Process additional config
return processAdditional(config: config, workspaceInfo: workspaceInfo)
return processAdditional(config: config, workspaceInfo: workspaceInfo, basePath: additionalBasePath)
} else {
return workspaceInfo
}
}

static func processAdditional(config: WorkspaceInfo.AdditionalConfig,
workspaceInfo: WorkspaceInfo) -> WorkspaceInfo
workspaceInfo: WorkspaceInfo,
basePath: Path) -> WorkspaceInfo
{
var files = workspaceInfo.files
var folders = workspaceInfo.folders
Expand All @@ -169,12 +178,12 @@ extension WorkspaceInfo {

for (targetName, dependOnTargets) in config.dependencies {
guard let target = allTargets[targetName] else {
Logger.error("Config: Cannot resolve \(targetName) to any known target")
logger.error("Config: Cannot resolve \(targetName) to any known target")
continue
}
for dependOnTargetName in dependOnTargets {
guard let targetDependOn = allTargets[dependOnTargetName] else {
Logger.error("Config: Cannot resolve \(dependOnTargetName) to any known target")
logger.error("Config: Cannot resolve \(dependOnTargetName) to any known target")
continue
}

Expand All @@ -186,15 +195,15 @@ extension WorkspaceInfo {

for (targetName, filesToAdd) in config.targetsFiles {
guard let target = allTargets[targetName] else {
Logger.error("Config: Cannot resolve \(targetName) to any known target")
logger.error("Config: Cannot resolve \(targetName) to any known target")
continue
}

for filePath in filesToAdd {
let path = Path(filePath).absolute()
let path = (basePath + filePath).absolute()

guard path.exists else {
Logger.error("Config: Path \(path) does not exist")
logger.error("Config: Path \(path) does not exist")
continue
}

Expand Down Expand Up @@ -265,7 +274,7 @@ extension WorkspaceInfo {

for affectedByPath in metadata.affectedBy {
guard affectedByPath.exists else {
Logger.warning("Path \(affectedByPath) is mentioned from package at \(metadata.path) but does not exist")
logger.warning("Path \(affectedByPath) is mentioned from package at \(metadata.path) but does not exist")
continue
}

Expand Down Expand Up @@ -302,7 +311,7 @@ extension WorkspaceInfo {
let absolutePath = path.parent() + localPackage.relativePath

guard let newPackages = try? PackageTargetMetadata.parse(at: absolutePath) else {
Logger.warning("Cannot find local package at \(absolutePath)")
logger.warning("Cannot find local package at \(absolutePath)")
return
}
for package in newPackages {
Expand All @@ -316,15 +325,15 @@ extension WorkspaceInfo {
// Target dependencies
for dependency in target.dependencies {
guard let name = dependency.target?.name else {
Logger.warning("Target without name: \(dependency)")
logger.warning("Target without name: \(dependency)")
continue
}

if let dependencyTarget = targetsByName[name] {
dependsOn.insert(targetIdentity,
dependOn: TargetIdentity.project(path: path, target: dependencyTarget))
} else {
Logger.warning("Unknown target: \(name)")
logger.warning("Unknown target: \(name)")
dependsOn.insert(targetIdentity,
dependOn: TargetIdentity.project(path: path, targetName: name, testTarget: false))
}
Expand All @@ -334,7 +343,7 @@ extension WorkspaceInfo {
for packageDependency in (target.packageProductDependencies ?? []) {
let package = packageDependency.productName
guard let packageMetadata = packagesByName[package] else {
Logger.warning("Package \(package) not found")
logger.warning("Package \(package) not found")
continue
}
dependsOn.insert(targetIdentity,
Expand Down
8 changes: 4 additions & 4 deletions Sources/DependencyCalculator/PackageMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
//

import Foundation
import PathKit
import SelectiveTestLogger
@preconcurrency import PathKit
import Logging
import SelectiveTestShell
import Workspace

struct PackageTargetMetadata {
struct PackageTargetMetadata: Sendable {
let path: Path
let affectedBy: Set<Path>
let name: String
Expand All @@ -25,7 +25,7 @@ struct PackageTargetMetadata {
flags.append("--ignore-lock")
}

let manifest = try Shell.execOrFail("cd \(path) && swift package dump-package \(flags.joined(separator: " "))")
let manifest = try Shell.execOrFail("(cd \(path) && swift package dump-package \(flags.joined(separator: " ")))")
.trimmingCharacters(in: .newlines)
guard let manifestData = manifest.data(using: .utf8),
let manifestJson = try JSONSerialization.jsonObject(with: manifestData, options: []) as? [String: Any],
Expand Down
16 changes: 9 additions & 7 deletions Sources/Git/Git+Changeset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@

import Foundation
import PathKit
import SelectiveTestLogger
import Logging
import SelectiveTestShell

let logger = Logger(label: "cx.gera.XcodeSelectiveTesting")

public extension Git {
func changeset(baseBranch: String, verbose: Bool = false) throws -> Set<Path> {
let gitRoot = try repoRoot()

var currentBranch = try Shell.execOrFail("cd \(gitRoot) && git branch --show-current").trimmingCharacters(in: .newlines)
var currentBranch = try Shell.execOrFail("(cd \(gitRoot) && git branch --show-current)").trimmingCharacters(in: .newlines)
if verbose {
Logger.message("Current branch: \(currentBranch)")
Logger.message("Base branch: \(baseBranch)")
logger.info("Current branch: \(currentBranch)")
logger.info("Base branch: \(baseBranch)")
}

if currentBranch.isEmpty {
Logger.warning("Missing current branch at \(path)")
logger.warning("Missing current branch at \(path)")

currentBranch = "HEAD"
}

let changes = try Shell.execOrFail("cd \(gitRoot) && git diff '\(baseBranch)'..'\(currentBranch)' --name-only")
let changes = try Shell.execOrFail("(cd \(gitRoot) && git diff '\(baseBranch)'..'\(currentBranch)' --name-only)")
let changesTrimmed = changes.trimmingCharacters(in: .whitespacesAndNewlines)

guard !changesTrimmed.isEmpty else {
Expand All @@ -36,7 +38,7 @@ public extension Git {
func localChangeset() throws -> Set<Path> {
let gitRoot = try repoRoot()

let changes = try Shell.execOrFail("cd \(gitRoot) && git diff HEAD --name-only")
let changes = try Shell.execOrFail("(cd \(gitRoot) && git diff HEAD --name-only)")

let changesTrimmed = changes.trimmingCharacters(in: .whitespacesAndNewlines)

Expand Down
Loading
Loading