Skip to content

Commit

Permalink
Add support for CMake builds
Browse files Browse the repository at this point in the history
These have different PIF cache locations in the build cache
  • Loading branch information
NinjaLikesCheez committed May 27, 2024
1 parent 07c0b96 commit 6cf282c
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 32 deletions.
1 change: 1 addition & 0 deletions Sources/GenIR/BuildCacheManipulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct BuildCacheManipulator {
func manipulate() throws {
if shouldDeploySkipInstallHack {
let intermediatesPath = buildCachePath
.appendingPathComponent("Build")
.appendingPathComponent("Intermediates.noindex")
.appendingPathComponent("ArchiveIntermediates")

Expand Down
13 changes: 11 additions & 2 deletions Sources/GenIR/Extensions/Process+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,24 @@ extension Process {

let process = Process()

let executable = command.replacingOccurrences(of: "\\", with: "")
let executable: String
let args: [String]

if command.starts(with: ".") || command.starts(with: "/") {
executable = command.replacingOccurrences(of: "\\", with: "")
args = arguments
} else {
executable = "/usr/bin/env"
args = [command] + arguments
}

if #available(macOS 10.13, *) {
process.executableURL = executable.fileURL
} else {
process.launchPath = executable
}

process.arguments = arguments.map { $0.replacingOccurrences(of: "\\", with: "") }
process.arguments = args.map { $0.replacingOccurrences(of: "\\", with: "") }
process.standardOutput = stdoutPipe
process.standardError = stderrPipe
process.standardInput = FileHandle.nullDevice
Expand Down
25 changes: 8 additions & 17 deletions Sources/GenIR/PIFCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Foundation
import PIFSupport

class PIFCache {
private let buildCache: URL
private let pifCachePath: URL
private let workspace: PIF.Workspace

Expand All @@ -22,7 +21,6 @@ class PIFCache {
}

init(buildCache: URL) throws {
self.buildCache = buildCache
self.pifCachePath = try Self.pifCachePath(in: buildCache)

do {
Expand All @@ -34,13 +32,12 @@ class PIFCache {
}

private static func pifCachePath(in buildCache: URL) throws -> URL {
// TODO: test this variation, because I haven't seen this personally
let cmakePIFCachePath = buildCache
.deletingLastPathComponent()
.appendingPathComponent("XCBuildData")
.appendingPathComponent("PIFCache")

let regularPIFCachePath = buildCache
.appendingPathComponent("Build")
.appendingPathComponent("Intermediates.noindex")
.appendingPathComponent("XCBuildData")
.appendingPathComponent("PIFCache")
Expand Down Expand Up @@ -111,12 +108,10 @@ extension PIF.BaseTarget: Hashable {
}

struct PIFDependencyProvider: DependencyProviding {
private let targets: [Target]
private let cache: PIFCache
private var guidToTargets: [PIF.GUID: Target]

init(targets: [Target], cache: PIFCache) {
self.targets = targets
self.cache = cache

self.guidToTargets = targets
Expand Down Expand Up @@ -168,24 +163,20 @@ struct PIFDependencyProvider: DependencyProviding {
.compactMap { resolveSwiftPackage($0) }

// Framework build phase dependencies
let frameworkBuildPhases = value
// NOTE: Previously we just cast this - all of a sudden with pods this is broken
// Not the end of the world - just as quick to do a dictionary lookup
let frameworkGUIDs = value
.baseTarget
.buildPhases
.compactMap { $0 as? PIF.FrameworksBuildPhase }

let referenceGUIDs = frameworkBuildPhases
.flatMap { $0.buildFiles }
// .compactMap { $0 as? PIF.FrameworksBuildPhase }
.compactMap {
switch $0.reference {
case .file(let guid): return guid
case .target: return nil // TODO: is this fine? I think so since we're looking for .framework file references here not targets which should be a dependency
case let .file(guid): return guid
case .target: return nil
}
}

let frameworkGUIDs = referenceGUIDs
.compactMap {
cache.frameworks[$0]?.guid
}
.compactMap { cache.frameworks[$0]?.guid }

let dependencyTargets = (dependencyTargetGUIDs + frameworkGUIDs).compactMap { guidToTargets[$0] }

Expand Down
27 changes: 19 additions & 8 deletions Sources/GenIR/XcodeLogParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,25 @@ class XcodeLogParser {
guard let startIndex = line.firstIndex(of: ":") else { continue }

let stripped = line[line.index(after: startIndex)..<line.endIndex].trimmingCharacters(in: .whitespacesAndNewlines)
// Stripped will be to the build description path, we want the root of the build path which is 6 folders up
buildCachePath = String(stripped).fileURL
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
var cachePath = String(stripped).fileURL

if cachePath.pathComponents.contains("DerivedData") {
// We want the 'project' folder which is the 'Project-randomcrap' folder inside of DerivedData.
// Build description path is inside this folder, but depending on the build - it can be a variable number of folders up
while cachePath.deletingLastPathComponent().lastPathComponent != "DerivedData" {
cachePath.deleteLastPathComponent()
}
} else {
// This build location is outside of the DerivedData directory - we want to go up to the folder _after_ the Build directory
// TODO: test this with fucking CMake....
while cachePath.lastPathComponent != "Build" {
cachePath.deleteLastPathComponent()
}

cachePath.deleteLastPathComponent()
}

buildCachePath = cachePath
}

if let target = target(from: line), currentTarget != target {
Expand Down
60 changes: 60 additions & 0 deletions TestAssets/CMakeDiscoveryTest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.20)

set(DEPLOYMENT_TARGET 17.0)
set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET})
set(CMAKE_SYSTEM_NAME iOS)

project("CMakeDiscoveryTest" Swift)

set(NAME "CMakeDiscovery")
set(APP_BUNDLE_IDENTIFIER "com.veracode.${NAME}")
set(EXECUTABLE_NAME ${NAME})
set(PRODUCT_NAME ${NAME})
set(CODE_SIGNING_IDENTITY "")
set(EXECUTABLE_NAME ${NAME})
set(MACOSX_BUNDLE_EXECUTABLE_NAME ${NAME})
set(MACOSX_BUNDLE_INFO_STRING ${APP_BUNDLE_IDENTIFIER})
set(MACOSX_BUNDLE_GUI_IDENTIFIER ${APP_BUNDLE_IDENTIFIER})
set(MACOSX_BUNDLE_BUNDLE_NAME ${APP_BUNDLE_IDENTIFIER})
set(MACOSX_BUNDLE_ICON_FILE "")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "1.0")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0")
set(MACOSX_BUNDLE_BUNDLE_VERSION "1.0")
set(MACOSX_BUNDLE_COPYRIGHT "Copyright")
set(IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET})

set(SOURCE_FILES
"${CMAKE_SOURCE_DIR}/Source/App.swift"
)

add_executable(
${NAME}
MACOSX_BUNDLE
${SOURCE_FILES}
)

find_library(SWIFTUI SwiftUI)
find_library(FOUNDATION Foundation)

target_link_libraries(${NAME} ${SWIFT_UI})
target_link_libraries(${NAME} ${FOUNDATION})

set_target_properties(${NAME} PROPERTIES
XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym"
XCODE_ATTRIBUTE_GCC_PREFIX_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/Prefix.pch"
RESOURCE "${RESOURCES}"
XCODE_ATTRIBUTE_GCC_PRECOMPILE_PREFIX_HEADER "YES"
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ""
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
XCODE_ATTRIBUTE_COMBINE_HIDPI_IMAGES NO
XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
XCODE_ATTRIBUTE_ENABLE_TESTABILITY YES
XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN YES
XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "iphoneos iphonesimulator"
XCODE_ATTRIBUTE_ARCHS "$(ARCHS_STANDARD)"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
)
10 changes: 10 additions & 0 deletions TestAssets/CMakeDiscoveryTest/Source/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftUI

@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
Text("Hello, World!")
}
}
}
54 changes: 54 additions & 0 deletions Tests/GenIRTests/CMakeDiscoveryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import XCTest
@testable import gen_ir

final class CMakeDiscoveryTests: XCTestCase {
let testPath: URL = {
TestContext.testAssetPath
.appendingPathComponent("CMakeDiscoveryTest")
.appendingPathComponent(".build")
.appendingPathComponent("CMakeDiscoveryTest.xcodeproj")
}()

private lazy var buildPath: URL = {
TestContext.testAssetPath
.appendingPathComponent("CMakeDiscoveryTest")
.appendingPathComponent(".build")
}()

let scheme = "CMakeDiscovery"

private func generate() throws {
if !FileManager.default.fileExists(atPath: buildPath.path) {
try FileManager.default.createDirectory(at: buildPath, withIntermediateDirectories: true)
}

_ = try Process.runShell(
"cmake",
arguments: [
"-GXcode",
TestContext.testAssetPath.appendingPathComponent("CMakeDiscoveryTest").path
],
runInDirectory: buildPath
)

// If this isn't set - xcodebuild will refuse to clean the project (thanks Apple!)
_ = try Process.runShell(
"xattr",
arguments: [
"-w",
"com.apple.xcode.CreatedByBuildSystem",
"true",
buildPath.appendingPathComponent("build").path
]
)
}

func testCMakeDiscovery() throws {
try generate()
let context = TestContext()
try context.build(test: testPath, scheme: scheme)

let cache = context.pifCache
XCTAssertEqual(cache.targets.filter { $0.name == "CMakeDiscovery" }.count, 1)
}
}
10 changes: 5 additions & 5 deletions Tests/GenIRTests/DependencyGraphTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ final class DependencyGraphTests: XCTestCase {

// App should have two nodes - Framework & Common
XCTAssertTrue(app.edges.count == 2, "App's edges is not equal to 2")
_ = try XCTUnwrap(app.edges.first(where: { $0.to.valueName == "Framework" }), "Failed to get Framework edge from App")
let commonEdge = try XCTUnwrap(app.edges.first(where: { $0.to.valueName == "Common" }), "Failed to get Common edge from App")
_ = try XCTUnwrap(app.edges.first(where: { $0.to.valueName == "Framework.framework" }), "Failed to get Framework edge from App")
let commonEdge = try XCTUnwrap(app.edges.first(where: { $0.to.valueName == "Common.framework" }), "Failed to get Common edge from App")

let frameworkTarget = try XCTUnwrap(targets.first(where: { $0.name == "Framework"}), "Failed to get Framework from targets")
let framework = try XCTUnwrap(graph.findNode(for: frameworkTarget), "Failed to find Framework node in graph")

// Framework should have two dependency edges - Common & SFSafeSymbols and one depender edge - App
XCTAssertTrue(framework.edges.count == 3, "Framework's edges is not equal to 3")
let symbolsEdge = try XCTUnwrap(framework.edges.first(where: { $0.to.valueName == "SFSafeSymbols" }), "Failed to get SFSafeSymbols edge from Framework")
let frameworkCommonEdge = try XCTUnwrap(framework.edges.first(where: { $0.to.valueName == "Common" }), "Failed to get SFSafeSymbols edge from Framework")
let frameworkAppEdge = try XCTUnwrap(framework.edges.first(where: { $0.to.valueName == "App" }), "Failed to get App edge from Framework")
let symbolsEdge = try XCTUnwrap(framework.edges.first(where: { $0.to.valueName == "SFSafeSymbols.o" }), "Failed to get SFSafeSymbols edge from Framework")
let frameworkCommonEdge = try XCTUnwrap(framework.edges.first(where: { $0.to.valueName == "Common.framework" }), "Failed to get SFSafeSymbols edge from Framework")
let frameworkAppEdge = try XCTUnwrap(framework.edges.first(where: { $0.to.valueName == "App.app" }), "Failed to get App edge from Framework")

XCTAssertNotEqual(commonEdge, frameworkCommonEdge, "App's Common edge is equal to Framework's Common edge - they should have different from values")
XCTAssertEqual(symbolsEdge.relationship, .dependency)
Expand Down

0 comments on commit 6cf282c

Please sign in to comment.