Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚙️ Refactoring XcodeBuild #334

Merged
merged 3 commits into from
Feb 26, 2024
Merged
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
9 changes: 5 additions & 4 deletions Sources/RugbyFoundation/Core/Build/BuildError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
switch self {
case let .buildFailed(errors, buildLogPath, rawBuildLogPath):
return """
\("Build failed.".red)
\("Xcodebuild failed.".red)

Check warning on line 11 in Sources/RugbyFoundation/Core/Build/BuildError.swift

View check run for this annotation

Codecov / codecov/patch

Sources/RugbyFoundation/Core/Build/BuildError.swift#L11

Added line #L11 was not covered by tests
\(errors.map(formatBuildError).joined(separator: "\n").white)
\("🚑 More information in build logs:".yellow)
\("[Beautified]".yellow) \("cat \(buildLogPath.homeFinderRelativePath())".white)
\("[Raw]".yellow) \("open \(rawBuildLogPath.homeFinderRelativePath())".white)
\("[Beautified]".yellow) \("cat \(buildLogPath.homeFinderRelativePath())".white.applyingStyle(.default))
\("[Raw]".yellow) \("open \(rawBuildLogPath.homeFinderRelativePath())".white.applyingStyle(.default))

Check warning on line 15 in Sources/RugbyFoundation/Core/Build/BuildError.swift

View check run for this annotation

Codecov / codecov/patch

Sources/RugbyFoundation/Core/Build/BuildError.swift#L14-L15

Added lines #L14 - L15 were not covered by tests
"""
case .cantFindBuildTargets:
return "Couldn't find any build targets."
}
}

private func formatBuildError(_ errorText: String) -> String {
"\("\u{2716}\u{0000FE0E}".red) \(errorText)"
let errorText = errorText.raw.hasPrefix("✖") ? errorText : "\("✖".red) \(errorText)"
return errorText

Check warning on line 24 in Sources/RugbyFoundation/Core/Build/BuildError.swift

View check run for this annotation

Codecov / codecov/patch

Sources/RugbyFoundation/Core/Build/BuildError.swift#L23-L24

Added lines #L23 - L24 were not covered by tests
.components(separatedBy: "\n")
.map { " \($0)" }
.joined(separator: "\n")
Expand Down
2 changes: 1 addition & 1 deletion Sources/RugbyFoundation/Core/Build/BuildManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ extension BuildManager: IInternalBuildManager {
processInterruptionTask.cancel()
cleanup()
}
try self.xcodeBuild.build(target: target.name, options: options, paths: paths)
try await xcodeBuild.build(target: target.name, options: options, paths: paths)
})
}
}
Expand Down
52 changes: 34 additions & 18 deletions Sources/RugbyFoundation/Core/Build/XcodeBuild/XcodeBuild.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Fish
protocol IXcodeBuild: AnyObject {
func build(target: String,
options: XcodeBuildOptions,
paths: XcodeBuildPaths) throws
paths: XcodeBuildPaths) async throws
}

/// Xcode build options.
Expand Down Expand Up @@ -81,31 +81,47 @@ final class XcodeBuild {
init(xcodeBuildExecutor: IXcodeBuildExecutor) {
self.xcodeBuildExecutor = xcodeBuildExecutor
}
}

extension XcodeBuild: IXcodeBuild {
func build(target: String,
options: XcodeBuildOptions,
paths: XcodeBuildPaths) throws {
private func run(
arguments: [String],
options: XcodeBuildOptions,
paths: XcodeBuildPaths
) async throws {
let command = "NSUnbufferedIO=YES xcodebuild"
var arguments = [
var arguments = arguments
arguments.append(contentsOf: [
"-project \(paths.project.shellFriendly)",
"-target \(target)",
"-sdk \(options.sdk.xcodebuild)",
"-config \(options.config.shellFriendly)",
"ARCHS=\(options.arch)",
"SYMROOT=\(paths.symroot.shellFriendly)",
"-parallelizeTargets"
]
"SYMROOT=\(paths.symroot.shellFriendly)"
])
options.resultBundlePath.map {
arguments.append("-resultBundlePath \($0.shellFriendly)")
}
arguments.append(contentsOf: options.xcargs)

try Folder.create(at: paths.symroot)
try xcodeBuildExecutor.run(command,
rawLogPath: paths.rawLog,
logPath: paths.beautifiedLog,
args: arguments)
try await xcodeBuildExecutor.run(command,
rawLogPath: paths.rawLog,
logPath: paths.beautifiedLog,
args: arguments)
}
}

// MARK: - IXcodeBuild

extension XcodeBuild: IXcodeBuild {
func build(target: String,
options: XcodeBuildOptions,
paths: XcodeBuildPaths) async throws {
try await run(
arguments: [
"-target \(target)",
"-sdk \(options.sdk.xcodebuild)",
"-config \(options.config.shellFriendly)",
"ARCHS=\(options.arch)",
"-parallelizeTargets"
],
options: options,
paths: paths
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,47 @@
rawLogPath: String,
logPath: String,
args: Any...
) throws
) async throws
}

// MARK: - Implementation

final class XcodeBuildExecutor: IXcodeBuildExecutor {
final class XcodeBuildExecutor: IXcodeBuildExecutor, Loggable {
let logger: ILogger
private let shellExecutor: IShellExecutor
private let logFormatter: IBuildLogFormatter

init(shellExecutor: IShellExecutor,
init(logger: ILogger,
shellExecutor: IShellExecutor,
logFormatter: IBuildLogFormatter) {
self.logger = logger
self.shellExecutor = shellExecutor
self.logFormatter = logFormatter
}

func run(_ command: String, rawLogPath: String, logPath: String, args: Any...) throws {
func run(_ command: String, rawLogPath: String, logPath: String, args: Any...) async throws {
try Folder.create(at: URL(fileURLWithPath: rawLogPath).deletingLastPathComponent().path)
try shellExecutor.throwingShell(command, args: args, "| tee '\(rawLogPath)'")
if let errors = try? beautifyLog(rawLogPath: rawLogPath, logPath: logPath), errors.isNotEmpty {
if let errors = try? await beautifyLog(rawLogPath: rawLogPath, logPath: logPath), errors.isNotEmpty {
throw BuildError.buildFailed(errors: errors, buildLogPath: logPath, rawBuildLogPath: rawLogPath)
}
}

// MARK: - Private

private func beautifyLog(rawLogPath: String, logPath: String) throws -> [String] {
private func beautifyLog(rawLogPath: String, logPath: String) async throws -> [String] {
var tests: [String] = []
var errors: [String] = []
let log = try File.create(at: logPath)
let output: (String, OutputType) throws -> Void = { formattedLine, type in
if type == .error { errors.append(formattedLine) }
switch type {
case .error:
errors.append(formattedLine)
case .test, .testCase:
tests.append(formattedLine)

Check warning on line 51 in Sources/RugbyFoundation/Core/Build/XcodeBuild/XcodeBuildExecutor.swift

View check run for this annotation

Codecov / codecov/patch

Sources/RugbyFoundation/Core/Build/XcodeBuild/XcodeBuildExecutor.swift#L51

Added line #L51 was not covered by tests
case .undefined, .task, .nonContextualError, .warning, .result:
break
}
try log.append("\(formattedLine)\n")
}

Expand All @@ -49,6 +60,9 @@
try logFormatter.format(line: line, output: output)
}
try logFormatter.finish(output: output)
for line in tests {
await logPlain(line)

Check warning on line 64 in Sources/RugbyFoundation/Core/Build/XcodeBuild/XcodeBuildExecutor.swift

View check run for this annotation

Codecov / codecov/patch

Sources/RugbyFoundation/Core/Build/XcodeBuild/XcodeBuildExecutor.swift#L64

Added line #L64 was not covered by tests
}
return errors
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ extension Vault {
// MARK: - Internal

func internalBuildManager() -> IInternalBuildManager {
let logFormatter = BuildLogFormatter(workingDirectory: router.workingDirectory,
colored: Rainbow.enabled)
let xcodeBuildExecutor = XcodeBuildExecutor(
shellExecutor: shellExecutor,
logFormatter: logFormatter
)
let xcodeProject = xcode.project(projectPath: router.podsProjectPath)
let buildTargetsManager = BuildTargetsManager(xcodeProject: xcodeProject)
let useBinariesManager = useBinariesManager(xcodeProject: xcodeProject,
Expand All @@ -28,15 +22,14 @@ extension Vault {
localRugbyFolderPath: router.rugbyPath,
buildFolderPath: router.buildPath
)
let xcodeBuild = XcodeBuild(xcodeBuildExecutor: xcodeBuildExecutor)
return BuildManager(logger: logger,
buildTargetsManager: buildTargetsManager,
librariesPatcher: LibrariesPatcher(logger: logger),
xcodeProject: xcodeProject,
rugbyXcodeProject: RugbyXcodeProject(xcodeProject: xcodeProject),
backupManager: backupManager(),
processMonitor: processMonitor,
xcodeBuild: xcodeBuild,
xcodeBuild: xcodeBuild(),
binariesStorage: binariesStorage,
targetsHasher: targetsHasher(),
useBinariesManager: useBinariesManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import Fish
public extension Vault {
/// The manager to prebuild CocoaPods project.
func prebuildManager() -> IPrebuildManager {
PrebuildManager(
let xcodeProject = xcode.project(projectPath: router.podsProjectPath)
return PrebuildManager(
logger: logger,
xcodePhaseEditor: XcodePhaseEditor(),
buildManager: internalBuildManager(),
xcodeProject: xcode.project(projectPath: router.podsProjectPath),
xcodeProject: xcodeProject,
binariesStorage: binariesStorage
)
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/RugbyFoundation/Vault/Vault.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Fish
import Rainbow

/// The main container of Rugby stuff.
public final class Vault {
Expand Down Expand Up @@ -135,6 +136,17 @@ public final class Vault {
logsRotator: logsRotator,
router: router
)

func xcodeBuild() -> XcodeBuild {
let logFormatter = BuildLogFormatter(workingDirectory: router.workingDirectory,
colored: Rainbow.enabled)
let xcodeBuildExecutor = XcodeBuildExecutor(
logger: logger,
shellExecutor: shellExecutor,
logFormatter: logFormatter
)
return XcodeBuild(xcodeBuildExecutor: xcodeBuildExecutor)
}
}

// MARK: - Keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import XCTest

final class XcodeBuildExecutorTests: XCTestCase {
private var sut: XcodeBuildExecutor!
private var logger: ILoggerMock!
private var shellExecutor: IShellExecutorMock!
private var buildLogFormatter: IBuildLogFormatterMock!
private var fishSharedStorage: IFilesManagerMock!
Expand All @@ -20,9 +21,11 @@ final class XcodeBuildExecutorTests: XCTestCase {
Fish.sharedStorage = backupFishSharedStorage
}

logger = ILoggerMock()
shellExecutor = IShellExecutorMock()
buildLogFormatter = IBuildLogFormatterMock()
sut = XcodeBuildExecutor(
logger: logger,
shellExecutor: shellExecutor,
logFormatter: buildLogFormatter
)
Expand All @@ -31,14 +34,15 @@ final class XcodeBuildExecutorTests: XCTestCase {
override func tearDown() {
super.tearDown()
sut = nil
logger = nil
shellExecutor = nil
buildLogFormatter = nil
fishSharedStorage = nil
}
}

extension XcodeBuildExecutorTests {
func test_taskInLog() throws {
func test_taskInLog() async throws {
let createFileMock = IFileMock()
fishSharedStorage.createFileAtContentsReturnValue = createFileMock
let createFolderMock = IFolderMock()
Expand All @@ -64,7 +68,7 @@ extension XcodeBuildExecutorTests {
}

// Act
try sut.run(
try await sut.run(
"test_command",
rawLogPath: test_rawLogPath,
logPath: test_logPath,
Expand Down Expand Up @@ -94,7 +98,7 @@ extension XcodeBuildExecutorTests {
XCTAssertEqual(createFileMock.appendReceivedInvocations, [expectedContent + "\n"])
}

func test_errorsInLog() throws {
func test_errorsInLog() async throws {
let createFileMock = IFileMock()
fishSharedStorage.createFileAtContentsReturnValue = createFileMock
let createFolderMock = IFolderMock()
Expand Down Expand Up @@ -125,14 +129,16 @@ extension XcodeBuildExecutorTests {

// Act
var resultError: Error?
try XCTAssertThrowsError(
sut.run(
do {
try await sut.run(
"test_command",
rawLogPath: test_rawLogPath,
logPath: test_logPath,
args: "arg0", "arg1"
)
) { resultError = $0 }
} catch {
resultError = error
}

// Assert
XCTAssertEqual(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ final class XcodeBuildTests: XCTestCase {
}

extension XcodeBuildTests {
func test_general() throws {
func test_general() async throws {
fishSharedStorage.createFolderAtReturnValue = IFolderMock()
try sut.build(
try await sut.build(
target: "Rugby",
options: XcodeBuildOptions(
sdk: .ios,
Expand Down Expand Up @@ -61,13 +61,13 @@ extension XcodeBuildTests {
XCTAssertEqual(
arguments.args as? [[String]],
[[
"-project /Users/swiftyfinch/Developer/Repos/Rugby/Example/Pods/Pods.xcodeproj",
"-target Rugby",
"-sdk iphoneos",
"-config Debug",
"ARCHS=x86_64",
"SYMROOT=/Users/swiftyfinch/Developer/Repos/Rugby/Example/.rugby/build",
"-parallelizeTargets",
"-project /Users/swiftyfinch/Developer/Repos/Rugby/Example/Pods/Pods.xcodeproj",
"SYMROOT=/Users/swiftyfinch/Developer/Repos/Rugby/Example/.rugby/build",
"-resultBundlePath build.xcresult",
"COMPILER_INDEX_STORE_ENABLE=NO",
"SWIFT_COMPILATION_MODE=wholemodule",
Expand All @@ -79,9 +79,9 @@ extension XcodeBuildTests {
)
}

func test_spaceInPaths() throws {
func test_spaceInPaths() async throws {
fishSharedStorage.createFolderAtReturnValue = IFolderMock()
try sut.build(
try await sut.build(
target: "RugbyPods",
options: XcodeBuildOptions(
sdk: .sim,
Expand Down Expand Up @@ -111,13 +111,13 @@ extension XcodeBuildTests {
XCTAssertEqual(
arguments.args as? [[String]],
[[
"-project /Users/swiftyfinch/Developer/Repos/Rugby/Exa\\ mple/Pods/Pods.xcodeproj",
"-target RugbyPods",
"-sdk iphonesimulator",
"-config QA\\ Release",
"ARCHS=arm64",
"SYMROOT=/Users/swiftyfinch/Developer/Repos/Rugby/Exa\\ mple/.rugby/build",
"-parallelizeTargets",
"-project /Users/swiftyfinch/Developer/Repos/Rugby/Exa\\ mple/Pods/Pods.xcodeproj",
"SYMROOT=/Users/swiftyfinch/Developer/Repos/Rugby/Exa\\ mple/.rugby/build",
"COMPILER_INDEX_STORE_ENABLE=NO",
"SWIFT_COMPILATION_MODE=wholemodule"
]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Fish
@testable import RugbyFoundation
import XCTest

final class СleanerTests: XCTestCase {
final class CleanerTests: XCTestCase {
private var sut: ICleaner!
private var sharedRugbyFolder: IFolder!
private var buildFolder: IFolder!
Expand All @@ -28,7 +28,7 @@ final class СleanerTests: XCTestCase {
}
}

extension СleanerTests {
extension CleanerTests {
func test_deleteSharedBinaries() async throws {
try sharedRugbyFolder.createFolder(named: "Alamofire")
.createFile(named: "Content", contents: "test_alamofire_content")
Expand Down
Loading
Loading