Skip to content

Commit

Permalink
Add option to exclude test targets, closes #777 (#779)
Browse files Browse the repository at this point in the history
  • Loading branch information
ileitch authored Aug 4, 2024
1 parent baf80ef commit 8e3e361
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 14 deletions.
15 changes: 14 additions & 1 deletion Sources/Frontend/Commands/ScanCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ struct ScanCommand: FrontendCommand {
@Option(help: "Output format (allowed: \(OutputFormat.allValueStrings.joined(separator: ", ")))")
var format: OutputFormat = defaultConfiguration.$outputFormat.defaultValue

@Option(parsing: .upToNextOption, help: "Source file globs to exclude from indexing. Declarations and references within these files will not be considered during analysis")
@Flag(help: "Exclude test targets from indexing")
var excludeTests: Bool = defaultConfiguration.$excludeTests.defaultValue

@Option(parsing: .upToNextOption, help: "Targets to exclude from indexing")
var excludeTargets: [String] = defaultConfiguration.$excludeTargets.defaultValue

@Option(parsing: .upToNextOption, help: "Source file globs to exclude from indexing")
var indexExclude: [String] = defaultConfiguration.$indexExclude.defaultValue

@Option(parsing: .upToNextOption, help: "Source file globs to exclude from the results. Note that this option is purely cosmetic, these files will still be indexed")
Expand Down Expand Up @@ -111,6 +117,9 @@ struct ScanCommand: FrontendCommand {
@Flag(help: "Only output results")
var quiet: Bool = defaultConfiguration.$quiet.defaultValue

@Option(help: "JSON package manifest path (obtained using `swift package describe --type json` or manually)")
var jsonPackageManifestPath: String?

@Option(help: "Baseline file path used to filter results")
var baseline: FilePath?

Expand Down Expand Up @@ -154,14 +163,18 @@ struct ScanCommand: FrontendCommand {
configuration.apply(\.$strict, strict)
configuration.apply(\.$indexStorePath, indexStorePath)
configuration.apply(\.$skipBuild, skipBuild)
configuration.apply(\.$excludeTests, excludeTests)
configuration.apply(\.$excludeTargets, excludeTargets)
configuration.apply(\.$skipSchemesValidation, skipSchemesValidation)
configuration.apply(\.$cleanBuild, cleanBuild)
configuration.apply(\.$buildArguments, buildArguments)
configuration.apply(\.$relativeResults, relativeResults)
configuration.apply(\.$retainCodableProperties, retainCodableProperties)
configuration.apply(\.$retainEncodableProperties, retainEncodableProperties)
configuration.apply(\.$jsonPackageManifestPath, jsonPackageManifestPath)
configuration.apply(\.$baseline, baseline)
configuration.apply(\.$writeBaseline, writeBaseline)

try scanBehavior.main { project in
try Scan().perform(project: project)
}.get()
Expand Down
1 change: 1 addition & 0 deletions Sources/PeripheryKit/Generic/GenericProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extension GenericProjectDriver: ProjectDriver {
public func collect(logger: ContextualLogger) throws -> [SourceFile : [IndexUnit]] {
try SourceFileCollector(
indexStorePaths: configuration.indexStorePath,
excludedTestTargets: [], // TODO
logger: logger
).collect()
}
Expand Down
13 changes: 12 additions & 1 deletion Sources/PeripheryKit/Indexer/SourceFileCollector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ import SourceGraph

public struct SourceFileCollector {
let indexStorePaths: [FilePath]
let excludedTargets: Set<String>
let logger: ContextualLogger
let configuration: Configuration

public init(indexStorePaths: [FilePath], logger: ContextualLogger, configuration: Configuration = .shared) {
public init(
indexStorePaths: [FilePath],
excludedTestTargets: Set<String>,
logger: ContextualLogger,
configuration: Configuration = .shared
) {
self.indexStorePaths = indexStorePaths
self.excludedTargets = excludedTestTargets.union(configuration.excludeTargets)
self.logger = logger
self.configuration = configuration
}
Expand All @@ -32,6 +39,10 @@ public struct SourceFileCollector {
if file.exists {
if !self.isExcluded(file) {
let module = try indexStore.moduleName(for: unit)
if let module, excludedTargets.contains(module) {
return nil
}

return (file, indexStore, unit, module)
}
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/PeripheryKit/Indexer/SwiftIndexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public final class SwiftIndexer: Indexer {
try job.phaseOne()
}

phaseOneLogger.debug("\(job.sourceFile.path.string) (\(elapsed)s)")
self.debug(logger: phaseOneLogger, sourceFile: job.sourceFile, elapsed: elapsed)
}

logger.endInterval(phaseOneInterval)
Expand All @@ -62,14 +62,20 @@ public final class SwiftIndexer: Indexer {
try job.phaseTwo()
}

phaseTwoLogger.debug("\(job.sourceFile.path.string) (\(elapsed)s)")
self.debug(logger: phaseTwoLogger, sourceFile: job.sourceFile, elapsed: elapsed)
}

logger.endInterval(phaseTwoInterval)
}

// MARK: - Private

private func debug(logger: ContextualLogger, sourceFile: SourceFile, elapsed: String) {
guard configuration.verbose else { return }
let modules = sourceFile.modules.joined(separator: ", ")
logger.debug("\(sourceFile.path.string) (\(modules)) (\(elapsed)s)")
}

private class Job {
let sourceFile: SourceFile

Expand Down
42 changes: 42 additions & 0 deletions Sources/PeripheryKit/SPM/SPM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public struct SPM {

public struct Package {
let path: FilePath = .current
let configuration: Configuration = .shared

var exists: Bool {
path.appending(packageFile).exists
Expand All @@ -23,6 +24,47 @@ public struct SPM {
func build(additionalArguments: [String]) throws {
try Shell.shared.exec(["swift", "build", "--build-tests"] + additionalArguments)
}

func testTargetNames() throws -> Set<String> {
let description = try load()
return description.targets.filter(\.isTestTarget).mapSet(\.name)
}

// MARK: - Private

private func load() throws -> PackageDescription {
Logger().contextualized(with: "spm:package").debug("Loading \(FilePath.current)")

let jsonData: Data

if let path = configuration.jsonPackageManifestPath {
jsonData = try Data(contentsOf: URL(fileURLWithPath: path))
} else {
let jsonString = try Shell.shared.exec(["swift", "package", "describe", "--type", "json"], stderr: false)

guard let data = jsonString.data(using: .utf8) else {
throw PeripheryError.packageError(message: "Failed to read swift package description.")
}

jsonData = data
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(PackageDescription.self, from: jsonData)
}
}
}

struct PackageDescription: Decodable {
let targets: [Target]
}

struct Target: Decodable {
let name: String
let type: String

var isTestTarget: Bool {
type == "test"
}
}
3 changes: 3 additions & 0 deletions Sources/PeripheryKit/SPM/SPMProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ extension SPMProjectDriver: ProjectDriver {
storePaths = [pkg.path.appending(".build/debug/index/store")]
}

let excludedTestTargets = configuration.excludeTests ? try pkg.testTargetNames() : []

return try SourceFileCollector(
indexStorePaths: storePaths,
excludedTestTargets: excludedTestTargets,
logger: logger
).collect()
}
Expand Down
11 changes: 10 additions & 1 deletion Sources/Shared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public final class Configuration {
@Setting(key: "schemes", defaultValue: [])
public var schemes: [String]

@Setting(key: "exclude_tests", defaultValue: false)
public var excludeTests: Bool

@Setting(key: "exclude_targets", defaultValue: [])
public var excludeTargets: [String]

@Setting(key: "index_exclude", defaultValue: ["**/*?.build/**/*"], requireDefaultValues: true)
public var indexExclude: [String]

Expand Down Expand Up @@ -110,6 +116,9 @@ public final class Configuration {
@Setting(key: "relative_results", defaultValue: false)
public var relativeResults: Bool

@Setting(key: "json_package_manifest_path", defaultValue: nil)
public var jsonPackageManifestPath: String?

@Setting(key: "baseline", defaultValue: nil)
public var baseline: FilePath?

Expand Down Expand Up @@ -209,7 +218,7 @@ public final class Configuration {

// MARK: - Private

lazy var settings: [any AbstractSetting] = [$project, $fileTargetsPath, $schemes, $indexExclude, $reportExclude, $reportInclude, $outputFormat, $retainPublic, $retainFiles, $retainAssignOnlyProperties, $retainAssignOnlyPropertyTypes, $retainObjcAccessible, $retainObjcAnnotated, $retainUnusedProtocolFuncParams, $retainSwiftUIPreviews, $disableRedundantPublicAnalysis, $disableUnusedImportAnalysis, $externalEncodableProtocols, $externalCodableProtocols, $externalTestCaseClasses, $verbose, $quiet, $disableUpdateCheck, $strict, $indexStorePath, $skipBuild, $skipSchemesValidation, $cleanBuild, $buildArguments, $xcodeListArguments, $relativeResults, $retainCodableProperties, $retainEncodableProperties, $baseline, $writeBaseline]
lazy var settings: [any AbstractSetting] = [$project, $fileTargetsPath, $schemes, $excludeTargets, $excludeTests, $indexExclude, $reportExclude, $reportInclude, $outputFormat, $retainPublic, $retainFiles, $retainAssignOnlyProperties, $retainAssignOnlyPropertyTypes, $retainObjcAccessible, $retainObjcAnnotated, $retainUnusedProtocolFuncParams, $retainSwiftUIPreviews, $disableRedundantPublicAnalysis, $disableUnusedImportAnalysis, $externalEncodableProtocols, $externalCodableProtocols, $externalTestCaseClasses, $verbose, $quiet, $disableUpdateCheck, $strict, $indexStorePath, $skipBuild, $skipSchemesValidation, $cleanBuild, $buildArguments, $xcodeListArguments, $relativeResults, $jsonPackageManifestPath, $retainCodableProperties, $retainEncodableProperties, $baseline, $writeBaseline]

private func buildFilenameMatchers(with patterns: [String]) -> [FilenameMatcher] {
// TODO: respect filesystem case sensitivity.
Expand Down
4 changes: 2 additions & 2 deletions Sources/Shared/SwiftVersionParser.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

public struct SwiftVersionParser {
public static func parse(_ fullVersion: String) throws -> VersionString {
struct SwiftVersionParser {
static func parse(_ fullVersion: String) throws -> VersionString {
guard let rawVersion = fullVersion.components(separatedBy: "Swift version").last?.split(separator: " ").first else {
throw PeripheryError.swiftVersionParseError(fullVersion: fullVersion)
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/XcodeSupport/XcodeProjectDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ extension XcodeProjectDriver: ProjectDriver {
try xcodebuild.build(project: project,
scheme: scheme,
allSchemes: Array(schemes),
additionalArguments: configuration.buildArguments,
buildForTesting: true)
additionalArguments: configuration.buildArguments)
}
}

Expand All @@ -109,8 +108,15 @@ extension XcodeProjectDriver: ProjectDriver {
storePaths = [try xcodebuild.indexStorePath(project: project, schemes: Array(schemes))]
}

var excludedTestTargets = Set<String>()

if configuration.excludeTests {
excludedTestTargets = project.targets.filter(\.isTestTarget).mapSet(\.name)
}

return try SourceFileCollector(
indexStorePaths: storePaths,
excludedTestTargets: excludedTestTargets,
logger: logger
).collect()
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/XcodeSupport/Xcodebuild.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ public final class Xcodebuild {
}

@discardableResult
func build(project: XcodeProjectlike, scheme: String, allSchemes: [String], additionalArguments: [String] = [], buildForTesting: Bool = false) throws -> String {
let cmd = buildForTesting ? "build-for-testing" : "build"
func build(project: XcodeProjectlike, scheme: String, allSchemes: [String], additionalArguments: [String] = []) throws -> String {
let args = [
"-\(project.type)", "'\(project.path.lexicallyNormalized().string)'",
"-scheme", "'\(scheme)'",
"-parallelizeTargets",
"-derivedDataPath", "'\(try derivedDataPath(for: project, schemes: allSchemes).string)'",
"-quiet"
"-quiet",
"build-for-testing"
]
let envs = [
"CODE_SIGNING_ALLOWED=\"NO\"",
Expand All @@ -40,7 +40,7 @@ public final class Xcodebuild {
]

let quotedArguments = quote(arguments: additionalArguments)
let xcodebuild = "xcodebuild \((args + [cmd] + envs + quotedArguments).joined(separator: " "))"
let xcodebuild = "xcodebuild \((args + envs + quotedArguments).joined(separator: " "))"
return try shell.exec(["/bin/sh", "-c", xcodebuild])
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/PeripheryTests/SwiftVersionParserTest.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import XCTest
import Shared
@testable import Shared

class SwiftVersionParserTest: XCTestCase {
func testParse() throws {
Expand Down

0 comments on commit 8e3e361

Please sign in to comment.