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

Log Types #163

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
12 changes: 9 additions & 3 deletions Sources/XCLogParser/commands/CommandHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ import Foundation

public struct CommandHandler {

let logFinder = LogFinder()
let activityLogParser = ActivityParser()
let logFinder: LogFinder
let activityLogParser: ActivityParser

public init() { }
public init(
logFinder: LogFinder = .init(),
activityLogParser: ActivityParser = .init()
) {
self.logFinder = logFinder
self.activityLogParser = activityLogParser
}

public func handle(command: Command) throws {
switch command.action {
Expand Down
18 changes: 16 additions & 2 deletions Sources/XCLogParser/commands/LogOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ public struct LogOptions {

/// The path to a LogManifest.plist file
let logManifestPath: String

/// Type of logs to search for
let logType: LogType

/// Use strict Xcode project naming.
let strictProjectName: Bool

/// Timestamp to check
let newerThan: Date?

/// Computed property, return the xcworkspacePath if not empty or
/// the xcodeprojPath if xcworkspacePath is empty
Expand All @@ -53,29 +59,37 @@ public struct LogOptions {
xcodeprojPath: String,
derivedDataPath: String,
xcactivitylogPath: String,
strictProjectName: Bool = false) {
logType: LogType,
strictProjectName: Bool = false,
newerThan: Date? = nil) {
self.projectName = projectName
self.xcworkspacePath = xcworkspacePath
self.xcodeprojPath = xcodeprojPath
self.derivedDataPath = derivedDataPath
self.xcactivitylogPath = xcactivitylogPath
self.logType = logType
self.logManifestPath = String()
self.strictProjectName = strictProjectName
self.newerThan = newerThan
}

public init(projectName: String,
xcworkspacePath: String,
xcodeprojPath: String,
derivedDataPath: String,
logType: LogType,
logManifestPath: String,
strictProjectName: Bool = false) {
strictProjectName: Bool = false,
newerThan: Date? = nil) {
self.projectName = projectName
self.xcworkspacePath = xcworkspacePath
self.xcodeprojPath = xcodeprojPath
self.derivedDataPath = derivedDataPath
self.logManifestPath = logManifestPath
self.logType = logType
self.xcactivitylogPath = String()
self.strictProjectName = strictProjectName
self.newerThan = newerThan
}

}
93 changes: 69 additions & 24 deletions Sources/XCLogParser/loglocation/LogFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@ public struct LogFinder {

let buildDirSettingsPrefix = "BUILD_DIR = "

let xcodebuildPath = "/usr/bin/xcodebuild"

let logsDir = "/Logs/Build/"
let xcodebuildPath: String

let logManifestFile = "LogStoreManifest.plist"

let emmptyDirResponseMessage = """
let emptyDirResponseMessage = """
Error. Couldn't find the derived data directory.
Please use the --filePath option to specify the path to the xcactivitylog file you want to parse.
"""
Expand All @@ -44,7 +42,11 @@ public struct LogFinder {
return homeDirURL.appendingPathComponent("Library/Developer/Xcode/DerivedData", isDirectory: true)
}

public init() {}
public init(
xcodebuildPath: String = "/usr/bin/xcodebuild"
) {
self.xcodebuildPath = xcodebuildPath
}

public func findLatestLogWithLogOptions(_ logOptions: LogOptions) throws -> URL {
guard logOptions.xcactivitylogPath.isEmpty else {
Expand All @@ -54,10 +56,17 @@ public struct LogFinder {
let projectDir = try getProjectDirWithLogOptions(logOptions)

// get latestLog

return try URL(fileURLWithPath: getLatestLogInDir(projectDir))

}

public func findLatestLogsWithLogOptions(_ logOptions: LogOptions) throws -> [URL] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this function is not used, was it supposed to be part of a refactor?

// get project dir
let projectDir = try getProjectDirWithLogOptions(logOptions)

// get latestLog
return try getLatestLogsInDir(projectDir, since: logOptions.newerThan)
}

public func findLogManifestWithLogOptions(_ logOptions: LogOptions) throws -> URL {
let logManifestURL = try findLogManifestURLWithLogOptions(logOptions)
Expand Down Expand Up @@ -111,17 +120,18 @@ public struct LogFinder {
// when xcodebuild is run with -derivedDataPath the logs are at the root level
if logOptions.derivedDataPath.isEmpty == false {
if FileManager.default.fileExists(atPath:
derivedData.appendingPathComponent(logsDir).path) {
return derivedData.appendingPathComponent(logsDir)
derivedData.appendingPathComponent(logOptions.logType.path).path) {
return derivedData.appendingPathComponent(logOptions.logType.path)
}
}
if logOptions.projectLocation.isEmpty == false {
let folderName = try getProjectFolderWithHash(logOptions.projectLocation)
let folderName = try getProjectFolderWithHash(logOptions.projectLocation, logType: logOptions.logType)
return derivedData.appendingPathComponent(folderName)
}
if logOptions.projectName.isEmpty == false {
return try findDerivedDataForProject(logOptions.projectName,
inDir: derivedData,
logType: logOptions.logType,
strictProjectName: logOptions.strictProjectName)
}
throw LogError.noLogFound(dir: derivedData.path)
Expand All @@ -143,9 +153,12 @@ public struct LogFinder {
/// - parameter name: Name of the project
/// - parameter inDir: URL of the derived data directory
/// - returns: The path to the derived data of the project or nil if it is not found.
public func findDerivedDataForProject(_ name: String,
inDir derivedDataDir: URL,
strictProjectName: Bool) throws -> URL {
public func findDerivedDataForProject(
_ name: String,
inDir derivedDataDir: URL,
logType: LogType,
strictProjectName: Bool
) throws -> URL {

let fileManager = FileManager.default

Expand Down Expand Up @@ -185,7 +198,7 @@ public struct LogFinder {
with --file or the right DerivedData folder with --derived_data
""")
}
return match.appendingPathComponent(logsDir)
return match.appendingPathComponent(logType.path)
}

/// Gets the full path of the Build/Logs directory for the given project
Expand All @@ -194,12 +207,12 @@ public struct LogFinder {
/// - parameter projectPath: The path to the .xcodeproj folder
/// - returns: The full path to the `Build/Logs` directory
/// - throws: An error if the derived data directory couldn't be found
public func logsDirectoryForXcodeProject(projectPath: String) throws -> String {
public func logsDirectoryForXcodeProject(projectPath: String, logType: LogType) throws -> String {
let arguments = ["-project", projectPath, "-showBuildSettings"]
if let result = try executeXcodeBuild(args: arguments) {
return try parseXcodeBuildDir(result)
return try parseXcodeBuildDir(result, logType: logType)
}
throw LogError.xcodeBuildError(emmptyDirResponseMessage)
throw LogError.xcodeBuildError(emptyDirResponseMessage)
}

/// Gets the latest xcactivitylog file path for the given projectFolder
Expand Down Expand Up @@ -243,12 +256,12 @@ public struct LogFinder {
/// - parameter andScheme: The name of the scheme
/// - returns: The full path to the `Build/Logs` directory
/// - throws: An error if the derived data directory can't be found.
public func logsDirectoryForWorkspace(_ workspace: String, andScheme scheme: String) throws -> String {
public func logsDirectoryForWorkspace(_ workspace: String, andScheme scheme: String, logType: LogType) throws -> String {
let arguments = ["-workspace", workspace, "-scheme", scheme, "-showBuildSettings"]
if let result = try executeXcodeBuild(args: arguments) {
return try parseXcodeBuildDir(result)
return try parseXcodeBuildDir(result, logType: logType)
}
throw LogError.xcodeBuildError(emmptyDirResponseMessage)
throw LogError.xcodeBuildError(emptyDirResponseMessage)
}

/// Returns the latest xcactivitylog file path in the given directory
Expand All @@ -273,18 +286,50 @@ public struct LogFinder {
}
return logPath.path
}

/// Returns the latest xcactivitylog file path in the given directory
/// - parameter dir: The full path for the directory
/// - returns: The paths of the latest xcactivitylog file in it since given date.
/// - throws: An `Error` if the directory doesn't exist or if there are no xcactivitylog files in it.
public func getLatestLogsInDir(_ dir: URL, since date: Date?) throws -> [URL] {
let fileManager = FileManager.default
let files = try fileManager.contentsOfDirectory(at: dir,
includingPropertiesForKeys: [.contentModificationDateKey],
options: .skipsHiddenFiles)
let sorted = try files
.filter { $0.path.hasSuffix(".xcactivitylog") }
.filter {
guard let timestamp = date else { return true }
guard
let lastModified = try? $0.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate
else { return false }
return lastModified > timestamp
}
.sorted {
let lhv = try $0.resourceValues(forKeys: [.contentModificationDateKey])
let rhv = try $1.resourceValues(forKeys: [.contentModificationDateKey])
guard let lhDate = lhv.contentModificationDate, let rhDate = rhv.contentModificationDate else {
return false
}
return lhDate.compare(rhDate) == .orderedDescending
}
guard !sorted.isEmpty else {
throw LogError.noLogFound(dir: dir.path)
}
return sorted
}

/// Generates the Derived Data Build Logs Folder name for the given project path
/// - parameter projectFilePath: A path (relative or absolut) to an .xcworkspace or an .xcodeproj directory
/// - returns The name of the folder with the same hash Xcode generates.
/// For instance MyApp-dtpdmwoqyxcbrmauwqvycvmftqah/Logs/Build
public func getProjectFolderWithHash(_ projectFilePath: String) throws -> String {
public func getProjectFolderWithHash(_ projectFilePath: String, logType: LogType) throws -> String {
let path = Path(projectFilePath).absolute()
let projectName = path.lastComponent
.replacingOccurrences(of: ".xcworkspace", with: "")
.replacingOccurrences(of: ".xcodeproj", with: "")
let hash = try XcodeHasher.hashString(for: path.string)
return "\(projectName)-\(hash)".appending(logsDir)
return "\(projectName)-\(hash)".appending(logType.path)
}

private func executeXcodeBuild(args: [String]) throws -> String? {
Expand All @@ -305,7 +350,7 @@ public struct LogFinder {
return String(data: data, encoding: .utf8)
}

private func parseXcodeBuildDir(_ response: String) throws -> String {
private func parseXcodeBuildDir(_ response: String, logType: LogType) throws -> String {
guard !response.starts(with: "xcodebuild: error: ") else {
throw LogError.xcodeBuildError(response.replacingOccurrences(of: "xcodebuild: ", with: ""))
}
Expand All @@ -315,8 +360,8 @@ public struct LogFinder {
if let settings = buildDirSettings.first {
return settings.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: buildDirSettingsPrefix, with: "")
.replacingOccurrences(of: "Build/Products", with: logsDir)
.replacingOccurrences(of: "Build/Products", with: logType.path)
}
throw LogError.xcodeBuildError(emmptyDirResponseMessage)
throw LogError.xcodeBuildError(emptyDirResponseMessage)
}
}
27 changes: 27 additions & 0 deletions Sources/XCLogParser/loglocation/LogType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// LogType.swift
//
//
// Created by Danny Gilbert on 2/2/22.
//

import Foundation

public enum LogType: String {
case build = "Build"
case indexBuild = "Index Build"
case install = "Install"
case issues = "Issues"
case package = "Package"
case run = "Run"
case test = "Test"
case updateSigning = "Update Signing"
}

// MARK: - Log Location
public extension LogType {

var path: String {
"/Logs/\(self.rawValue)/"
}
}
4 changes: 4 additions & 0 deletions Sources/XCLogParserApp/commands/DumpCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ struct DumpCommand: ParsableCommand {
commandName: "dump",
abstract: "Dumps the xcactivitylog file into a JSON document"
)

@Option(name: .long, help: "Type of .xactivitylog file to look for.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about printing the available option values here as well to make it easier for the user?

var logs: LogType = .build
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:
Rename this variable to logType . (Same for ManifestCommand.swift and ParseCommand.swift)


@Option(name: .long, help: "The path to a .xcactivitylog file.")
var file: String?
Expand Down Expand Up @@ -104,6 +107,7 @@ struct DumpCommand: ParsableCommand {
xcodeprojPath: xcodeproj ?? "",
derivedDataPath: derivedData ?? "",
xcactivitylogPath: file ?? "",
logType: logs,
strictProjectName: strictProjectName)
let actionOptions = ActionOptions(reporter: .json,
outputPath: output ?? "",
Expand Down
4 changes: 4 additions & 0 deletions Sources/XCLogParserApp/commands/ManifestCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ struct ManifestCommand: ParsableCommand {
commandName: "manifest",
abstract: "Shows the content of a LogManifest plist file as a JSON document."
)

@Option(name: .long, help: "Type of .xactivitylog file to look for.")
var logs: LogType = .build

@Option(name: .customLong("long_manifest"), help: "The path to an existing LogStoreManifest.plist.")
var logManifest: String?
Expand Down Expand Up @@ -89,6 +92,7 @@ struct ManifestCommand: ParsableCommand {
xcworkspacePath: workspace ?? "",
xcodeprojPath: xcodeproj ?? "",
derivedDataPath: derivedData ?? "",
logType: logs,
logManifestPath: logManifest ?? "",
strictProjectName: strictProjectName)

Expand Down
4 changes: 4 additions & 0 deletions Sources/XCLogParserApp/commands/ParseCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ struct ParseCommand: ParsableCommand {
commandName: "parse",
abstract: "Parses the content of an xcactivitylog file"
)

@Option(name: .long, help: "Type of .xactivitylog file to look for.")
var logs: LogType = .build

@Option(name: .long, help: "The path to a .xcactivitylog file.")
var file: String?
Expand Down Expand Up @@ -163,6 +166,7 @@ struct ParseCommand: ParsableCommand {
xcodeprojPath: xcodeproj ?? "",
derivedDataPath: derivedData ?? "",
xcactivitylogPath: file ?? "",
logType: logs,
strictProjectName: strictProjectName)
let actionOptions = ActionOptions(reporter: xclReporter,
outputPath: output ?? "",
Expand Down
11 changes: 11 additions & 0 deletions Sources/XCLogParserApp/extensions/LogType+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// LogType+.swift
//
//
// Created by Danny Gilbert on 2/2/22.
//

import XCLogParser
import ArgumentParser

extension LogType: ExpressibleByArgument { }
Loading