Skip to content
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ output/
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.swiftpm
Expand Down
8 changes: 0 additions & 8 deletions PIF/.gitignore

This file was deleted.

12 changes: 6 additions & 6 deletions Sources/DependencyGraph/DependencyGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class DependencyGraph<Value: NodeValue> {
/// - Returns: the chain of nodes, starting with the 'bottom' of the dependency subgraph
public func chain(for value: Value) -> [Node] {
guard let node = findNode(for: value) else {
logger.debug("Couldn't find node for value: \(value.valueName)")
GenIRLogger.logger.debug("Couldn't find node for value: \(value.valueName)")
return []
}

Expand All @@ -49,26 +49,26 @@ public class DependencyGraph<Value: NodeValue> {
/// - Parameter node: the node whose children to search through
/// - Returns: an array of nodes ordered by a depth-first search approach
private func depthFirstSearch(startingAt node: Node) -> [Node] {
logger.debug("----\nSearching for: \(node.value.valueName)")
GenIRLogger.logger.debug("----\nSearching for: \(node.value.valueName)")
var visited = Set<Node>()
var chain = [Node]()

/// Visits node dependencies and adds them to the chain from the bottom up
/// - Parameter node: the node to search through
func depthFirst(node: Node) {
logger.debug("inserting node: \(node.value.valueName)")
GenIRLogger.logger.debug("inserting node: \(node.value.valueName)")
visited.insert(node)

for edge in node.edges where edge.relationship == .dependency {
if visited.insert(edge.to).inserted {
logger.debug("edge to: \(edge.to)")
GenIRLogger.logger.debug("edge to: \(edge.to)")
depthFirst(node: edge.to)
} else {
logger.debug("edge already in visited: \(visited)")
GenIRLogger.logger.debug("edge already in visited: \(visited)")
}
}

logger.debug("appending to chain: \(node.value.valueName)")
GenIRLogger.logger.debug("appending to chain: \(node.value.valueName)")
chain.append(node)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/DependencyGraph/DependencyGraphBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class DependencyGraphBuilder<Provider: DependencyProviding, Value: NodeVa
return existingNode
}

logger.debug("Adding value: \(value.valueName) to graph")
GenIRLogger.logger.debug("Adding value: \(value.valueName) to graph")

let dependencies = provider.dependencies(for: value)
let node = graph.addNode(for: value)
Expand Down
10 changes: 5 additions & 5 deletions Sources/GenIR/BuildCacheManipulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ struct BuildCacheManipulator {
}
.reduce(into: [String: URL]()) { $0[$1.lastPathComponent] = $1 }

logger.debug("symlinks to update: \(symlinksToUpdate)")
logger.debug("existing frameworks: \(existingFrameworks)")
GenIRLogger.logger.debug("symlinks to update: \(symlinksToUpdate)")
GenIRLogger.logger.debug("existing frameworks: \(existingFrameworks)")

try symlinksToUpdate.forEach { name, path in
guard let buildProductPath = existingFrameworks[name] else {
logger.error("Couldn't lookup \(name) in existing frameworks: \(existingFrameworks.keys)")
GenIRLogger.logger.error("Couldn't lookup \(name) in existing frameworks: \(existingFrameworks.keys)")
return
}

Expand Down Expand Up @@ -147,7 +147,7 @@ struct BuildCacheManipulator {
}

// Uh oh, there shouldn't be more than one folder here - was a clean performed?
logger.warning(
GenIRLogger.logger.warning(
"Expected one folder at path: \(path), but got \(folders.count): \(folders). Attempting to select a Debug or Veracode configuration folder"
)

Expand All @@ -161,7 +161,7 @@ struct BuildCacheManipulator {
}

if filtered.count > 1 {
logger.error(
GenIRLogger.logger.error(
"""
Found more than one possible folders matching 'debug' or 'veracode' configurations: \(filtered). Please ensure you build from a clean state.
"""
Expand Down
24 changes: 12 additions & 12 deletions Sources/GenIR/CompilerCommandRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,24 @@ struct CompilerCommandRunner {
let totalCommands = commands
.map { $0.value.count }
.reduce(0, +)
logger.info("Total commands to run: \(totalCommands)")
GenIRLogger.logger.info("Total commands to run: \(totalCommands)")

var totalModulesRun = 0

for target in targets {
// Continue to the next target if no commands are found for the current target
guard let targetCommands = commands[TargetKey(projectName: target.projectName, targetName: target.name)] else {
logger.debug("No commands found for target: \(target.name) in project: \(target.projectName)")
GenIRLogger.logger.debug("No commands found for target: \(target.name) in project: \(target.projectName)")
continue
}

logger.info("Operating on target: \(target.name). Total modules processed: \(totalModulesRun)")
GenIRLogger.logger.info("Operating on target: \(target.name). Total modules processed: \(totalModulesRun)")

totalModulesRun += try run(commands: targetCommands, for: target.productName, at: output)
}

let uniqueModules = Set(try fileManager.files(at: output, withSuffix: ".bc")).count
logger.info("Finished compiling all targets. Unique modules: \(uniqueModules)")
GenIRLogger.logger.info("Finished compiling all targets. Unique modules: \(uniqueModules)")
}

/// Runs all commands for a given target
Expand All @@ -85,12 +85,12 @@ struct CompilerCommandRunner {
let targetDirectory = directory.appendingPathComponent(name)

try fileManager.createDirectory(at: targetDirectory, withIntermediateDirectories: true)
logger.debug("Created target directory: \(targetDirectory)")
GenIRLogger.logger.debug("Created target directory: \(targetDirectory)")

var targetModulesRun = 0

for (index, command) in commands.enumerated() {
logger.info(
GenIRLogger.logger.info(
"""
\(dryRun ? "Dry run of" : "Running") command (\(command.compiler.rawValue)) \(index + 1) of \(commands.count). \
Target modules processed: \(targetModulesRun)
Expand All @@ -106,7 +106,7 @@ struct CompilerCommandRunner {
do {
result = try Process.runShell(executable, arguments: arguments, runInDirectory: directory)
} catch {
logger.error(
GenIRLogger.logger.error(
"""
Couldn't create process for executable: \(executable) with arguments: \(arguments.joined(separator: " ")). \
This is likely a bug in parsing the build log. Please raise it as an issue.
Expand All @@ -122,7 +122,7 @@ struct CompilerCommandRunner {
continue
}
}
logger.error(
GenIRLogger.logger.error(
"""
Command finished:
- code: \(result.code)
Expand All @@ -141,7 +141,7 @@ struct CompilerCommandRunner {
switch command.compiler {
case .swiftc:
guard let outputFileMap = try getOutputFileMap(from: arguments) else {
logger.error("Failed to find OutputFileMap for command \(command.command) ")
GenIRLogger.logger.error("Failed to find OutputFileMap for command \(command.command) ")
break
}

Expand All @@ -151,7 +151,7 @@ struct CompilerCommandRunner {
}

if clangAdditionalModules == 0 && swiftAdditionalModules == 0 {
logger.error(
GenIRLogger.logger.error(
"""
No modules were produced from compiler, potential failure. Results: \n\n \
executable: \(executable)\n\n \
Expand Down Expand Up @@ -290,8 +290,8 @@ extension CompilerCommandRunner {
let path = arguments[index + 1].fileURL

guard fileManager.fileExists(atPath: path.filePath) else {
logger.error("Found an OutputFileMap, but it doesn't exist on disk? Please report this issue.")
logger.debug("OutputFileMap path: \(path)")
GenIRLogger.logger.error("Found an OutputFileMap, but it doesn't exist on disk? Please report this issue.")
GenIRLogger.logger.debug("OutputFileMap path: \(path)")
return nil
}

Expand Down
206 changes: 206 additions & 0 deletions Sources/GenIR/DebugData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import Foundation
import ArgumentParser // To use ValidationError
import LogHandlers
import Logging

///
/// This file contains the DebugData struct, which is responsible for capturing debug data during the execution of the program.
/// It includes methods for initializing the capture path and logging relevant information. The data is captured in a sub-directory
/// of the xcarchive and therefore will be included with the submission to the Veracode Platform.
///
/// The struct is initialized with the xcarchive and a flag indicating whether debug data is to be captured.
/// The directory structure will be:
/// - xcarchive
/// - debug-data
/// - Gen-IR log output file.
/// - xcodebuild log which was input to Gen-IR
/// - PIF cache directory
/// - xcode-select ouput
/// - xcodebuild --version output
/// - swift --version output
/// - env | grep DEVELOPER_DIR output
/// - data.zip
///
struct DebugData: Decodable {

let capturePath: URL
let logDataPath: URL

init (xcodeArchivePath: URL) throws {
Comment thread
NinjaLikesCheez marked this conversation as resolved.

// Setup the capture path to hold the debug data. This path is a sub-directory of the xcarchive.
capturePath = xcodeArchivePath.appendingPathComponent("debug-data", isDirectory: true)

// Make sure the directory to hold debug data exists and is empty
if !FileManager.default.directoryExists(at: capturePath) {
// It doesn't exist, so create it
try FileManager.default.createDirectory(at: capturePath, withIntermediateDirectories: true)
}

// Create a subdirectory for the logs and add a file log handler to write the log there.
let zipLogPath = capturePath.appendingPathComponent("log")
try FileManager.default.createDirectory(at: zipLogPath, withIntermediateDirectories: true)

logDataPath = zipLogPath.appendingPathComponent("genir-capture.log", isDirectory: false)
}

///
/// Tell the user what information will be captured.
///
public func displayCaptureInfo() {
let captureInfo = Logger.Message(
"""
\n
\u{001B}[1m The Gen-IR capture option is enabled.\u{001B}[0m
The following data will be added to the xcarchive and sent to Veracode:
- Gen-IR log output file
- xcodebuild log which was input to Gen-IR
- PIF cache directory and its contents
- The location of the developer directory (e.g. /Applications/Xcode.app/Contents/Developer)
This is obtained via the xcode-select -p command and from the value of the DEVELOPER_DIR environment variable.
No other environment variables are captured.
- The xcodebuild version
- The swift-version
\n
""")
GenIRLogger.logger.info(captureInfo)
GenIRLogger.logger.info("Debug data will be captured to: \(capturePath)")
}

///
/// Capture the execution context:
/// This includes the xcodebuild log, the configured developer directory, the xcodebuild version, and the swift version.
///
public func captureExecutionContext(logPath: URL) throws {

// Capture the xcodebuild log
try FileManager.default.copyItem(at: logPath, to: capturePath.appendingPathComponent("xcodebuild.log"))

// Capture the configured developer directory
let developerDir = try execShellCommand(command: "xcode-select", args: ["-p"])

// Capture a possible override of the developer directory
let developerDirOverride = ProcessInfo.processInfo.environment["DEVELOPER_DIR"] ?? "Not set"

// Capture the xcodebuild version
let xcodeBuildVersion = try execShellCommand(command: "xcodebuild", args: ["-version"])

// Capture the swift version
let swiftVersion = try execShellCommand(command: "swift", args: ["-version"])

do {
let versionsUrl = capturePath.appendingPathComponent("versions.txt")
FileManager.default.createFile(atPath: versionsUrl.path, contents: nil)
let versionsFile = try FileHandle(forWritingTo: versionsUrl)
versionsFile.write(Data("""
DEVELOPER_DIR: \(developerDir)\n
DEVELOPER_DIR_OVERRIDE: \(developerDirOverride)\n
XCODEBUILD_VERSION: \(xcodeBuildVersion)\n
SWIFT_VERSION: \(swiftVersion)\n
""".utf8))
versionsFile.closeFile()
} catch {
GenIRLogger.logger.error("Debug data capture Error \(error) occurred creating the versions.txt file while capturing debug data.")
}
GenIRLogger.logger.info("Debug data capture execution context data captured.")
}

///
/// Capture the PIF cache:
/// This is a copy of the PIF cache from the location specified in the xcarchive.
/// The location is determined by the PIFCache.pifCachePath(in:) method.
///
public func capturePIFCache(pifLocation: URL) throws {

// Capture the PIF cache
let savedPif = capturePath.appendingPathComponent("pif-data")
do {
// Perform the copy operation and skip broken symlinks
try copyDirectorySkippingBrokenSymlinks(from: pifLocation, to: savedPif)
} catch {
GenIRLogger.logger.error("Debug data capture of PIF Cache error: \(error.localizedDescription)")
}
GenIRLogger.logger.info("Debug data capture PIF cache data captured.")
}

///
/// Do any final data captures and log the completion message.
///
public func captureComplete(xcarchive: URL) throws {
GenIRLogger.logger.info("Debug data capture complete.")
}

///
/// Copy a directory and skip broken symlinks.
/// This is used to copy the PIF cache from the location based on the build cache path parsed from the xcode build log.
/// The location is determined by the PIFCache.pifCachePath(in:) method.
///
private func copyDirectorySkippingBrokenSymlinks(from sourceURL: URL, to destinationURL: URL) throws {

let fileManager = FileManager.default
let contents = try fileManager.contentsOfDirectory(at: sourceURL, includingPropertiesForKeys: [.isSymbolicLinkKey], options: [])
let sourceBaseName = sourceURL.path.hasSuffix("/") ? sourceURL.path : sourceURL.path + "/"

for item in contents {
let resourceValues = try item.resourceValues(forKeys: [.isSymbolicLinkKey])

if resourceValues.isSymbolicLink == true {
// Check if the symlink target exists
let targetPath = try fileManager.destinationOfSymbolicLink(atPath: item.path)
if !fileManager.fileExists(atPath: targetPath) {
GenIRLogger.logger.info("Skipping broken symlink while copying PIFCache: \(item.path)")
continue
}
}
// Find the relative path of the item
let relativePart = item.path.replacingOccurrences(of: sourceBaseName, with: "")

// Define destination path
let destinationItemURL = destinationURL.appendingPathComponent(relativePart)
let parentDestinationURL = destinationItemURL.deletingLastPathComponent()
do {
// Make sure the destination directory exists
try fileManager.createDirectory(at: parentDestinationURL, withIntermediateDirectories: true)
// Copy item
try fileManager.copyItem(at: item, to: destinationItemURL)
} catch {
GenIRLogger.logger.error("Error while copying a PIFCache item \(item) : \(error.localizedDescription)")
continue
}
}
}

///
/// Given a command string and it's arguments, invoke a shell to execute the command and return the command output.
///
private func execShellCommand(command: String, args: [String]) throws -> String {
let result: Process.ReturnValue
do {
result = try Process.runShell(command, arguments: args, runInDirectory: FileManager.default.currentDirectoryPath.fileURL)
} catch {
GenIRLogger.logger.error(
"""
Debug data capture couldn't create process for command: \(command) with arguments: \(args.joined(separator: " ")). \
Output will not be captured.
"""
)
return ""
}

if result.code != 0 {
GenIRLogger.logger.error(
"""
Debug data capture command finished with non-zero exit code. Output will not be captured.
- code: \(result.code)
- command: \(command) \(args.joined(separator: " "))
- stdout: \(String(describing: result.stdout))
- stderr: \(String(describing: result.stderr))
"""
)

return ""
}

return result.stdout ?? ""
}
}
Loading