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

Linux (and maybe Windows?) support #69

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
144 changes: 29 additions & 115 deletions Plugins/PDCPlugin/PDCPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,14 @@ struct ModuleBuildRequest {
// MARK: - PDCPlugin

@main struct PDCPlugin: CommandPlugin {
let home = FileManager.default.homeDirectoryForCurrentUser.path()
let arm_none_eabi_gcc = "/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gcc"

func performCommand(context: PluginContext, arguments: [String]) async throws {
var arguments = ArgumentExtractor(arguments)
let verbose = arguments.extractFlag(named: "verbose") > 0

// MARK: - Paths

let swiftToolchain = try swiftToolchain()
print("found Swift toolchain: \(swiftToolchain)")
let tools = Tools(context: context, verbose: verbose)
let playdateSDK = try tools.playdateSDK()

let playdateSDK = try playdateSDK()
print("found Playdate SDK")
// MARK: - Paths

let productName = context.package.displayName

Expand Down Expand Up @@ -99,7 +93,12 @@ struct ModuleBuildRequest {
"/include",
"/include-fixed",
"/../../../../arm-none-eabi/include"
].map { "/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major/lib/gcc/arm-none-eabi/9.2.1" + $0 }
].map { (ProcessInfo.processInfo.environment["ARM_TOOLCHAIN_PATH"] ?? "/usr/local/playdate/gcc-arm-none-eabi-9-2019-q4-major/lib/gcc/arm-none-eabi/9.2.1") + $0 }

guard FileManager.default.fileExists(atPath: gccIncludePaths.first!) else {
Diagnostics.error("Arm embedded toolchain not found. Ensure it is installed through the Playdate SDK (macOS) or manually and set in the ARM_TOOLCHAIN_PATH environment variable.")
throw Tools.Error.armNoneEabiGCCNotFound
}

let cFlags = gccIncludePaths.flatMap { ["-I", $0] }

Expand Down Expand Up @@ -131,76 +130,11 @@ struct ModuleBuildRequest {
"-module-alias", "PlaydateKit=\(playdateKit.moduleName(for: .simulator))"
]

// MARK: - CLI

@Sendable func cc(_ arguments: [String]) throws {
let process = Process()
process.executableURL = URL(filePath: arm_none_eabi_gcc)
process.arguments = ["-g3"] + arguments
if verbose { process.print() }
try process.run()
process.waitUntilExit()
guard process.terminationStatus == 0 else { throw Error.ccFailed(exitCode: process.terminationStatus) }
}

@Sendable func swiftc(_ arguments: [String]) throws {
let xcrun = try context.tool(named: "xcrun")
let process = Process()
process.executableURL = URL(filePath: xcrun.path.string)
process.arguments = ["-f", "swiftc", "--toolchain", swiftToolchain]
let pipe = Pipe()
process.standardOutput = pipe
if verbose { process.print() }
try process.run()
process.waitUntilExit()
guard process.terminationStatus == 0 else { throw Error.xcrunFailed(exitCode: process.terminationStatus) }
let swiftc = try String(decoding: pipe.fileHandleForReading.readToEnd() ?? Data(), as: UTF8.self)
.trimmingCharacters(in: .newlines)
let process2 = Process()
process2.executableURL = URL(filePath: swiftc)
process2.arguments = ["-g"] + arguments
if verbose { process2.print() }
try process2.run()
process2.waitUntilExit()
guard process2.terminationStatus == 0 else { throw Error.swiftcFailed(exitCode: process2.terminationStatus) }
}

@Sendable func clang(_ arguments: [String]) throws {
let clang = try context.tool(named: "clang")
let process = Process()
var environment = ProcessInfo.processInfo.environment

environment["TVOS_DEPLOYMENT_TARGET"] = nil
environment["DRIVERKIT_DEPLOYMENT_TARGET"] = nil
environment["MACOSX_DEPLOYMENT_TARGET"] = nil
environment["WATCHOS_DEPLOYMENT_TARGET"] = nil
environment["XROS_DEPLOYMENT_TARGET"] = nil
environment["IPHONEOS_DEPLOYMENT_TARGET"] = nil

process.environment = environment
process.executableURL = URL(filePath: clang.path.string)
process.arguments = ["-g"] + arguments
if verbose { process.print() }
try process.run()
process.waitUntilExit()
guard process.terminationStatus == 0 else { throw Error.clangFailed(exitCode: process.terminationStatus) }
}

func pdc(_ arguments: [String]) throws {
let process = Process()
process.executableURL = URL(filePath: "\(playdateSDK)/bin/pdc")
process.arguments = ["--skip-unknown"] + arguments
if verbose { process.print() }
try process.run()
process.waitUntilExit()
guard process.terminationStatus == 0 else { throw Error.pdcFailed(exitCode: process.terminationStatus) }
}

// MARK: - Build

// setup.o
let setup = context.pluginWorkDirectory.appending(["setup.o"]).string
try cc(mcFlags + [
try tools.cc(mcFlags + [
"-c", "-O2", "-falign-functions=16", "-fomit-frame-pointer", "-gdwarf-2", "-Wall", "-Wno-unused", "-Wstrict-prototypes", "-Wno-unknown-pragmas", "-fverbose-asm", "-Wdouble-promotion", "-mword-relocations", "-fno-common", "-ffunction-sections", "-fdata-sections", "-Wa,-ahlms=\(context.pluginWorkDirectory.appending(["setup.lst"]).string)", "-DTARGET_PLAYDATE=1", "-DTARGET_EXTENSION=1", "-MD", "-MP", "-MF",
context.pluginWorkDirectory.appending(["setup.o.d"]).string,
"-I", ".",
Expand Down Expand Up @@ -239,23 +173,23 @@ struct ModuleBuildRequest {
switch module.type {
case .playdateKit:
// playdatekit_device.swiftmodule
try swiftc(swiftFlags + swiftFlagsDevice + module.sourcefiles + [
try tools.swiftc(swiftFlags + swiftFlagsDevice + module.sourcefiles + [
"-module-name", module.moduleName(for: .device), "-emit-module", "-emit-module-path", module.modulePath(for: .device)
])
case .product:
// $(productName)_device.o
let linkedModules = productDependencies.map { ["-module-alias", "\($0.name)=\($0.moduleName(for: .device))"] }.flatMap { $0 }
try swiftc(swiftFlags + swiftFlagsDevice + linkedModules + module.sourcefiles + [
try tools.swiftc(swiftFlags + swiftFlagsDevice + linkedModules + module.sourcefiles + [
"-c", "-o", module.modulePath(for: .device)
])
print("building pdex.elf")
try cc([setup, module.modulePath(for: .device)] + mcFlags + [
try tools.cc([setup, module.modulePath(for: .device)] + mcFlags + [
"-T\(playdateSDK)/C_API/buildsupport/link_map.ld",
"-Wl,-Map=\(context.pluginWorkDirectory.appending(["pdex.map"]).string),--cref,--gc-sections,--no-warn-mismatch,--emit-relocs",
"-o", sourcePath.appending(["pdex.elf"]).string
])
case .productDependency:
try swiftc(swiftFlags + swiftFlagsDevice + module.sourcefiles + [
try tools.swiftc(swiftFlags + swiftFlagsDevice + module.sourcefiles + [
"-module-name", module.moduleName(for: .device), "-emit-module", "-emit-module-path", module.modulePath(for: .device)
])
}
Expand All @@ -267,28 +201,35 @@ struct ModuleBuildRequest {
try await Task {
switch module.type {
case .playdateKit:
try swiftc(swiftFlags + swiftFlagsSimulator + module.sourcefiles + [
try tools.swiftc(swiftFlags + swiftFlagsSimulator + module.sourcefiles + [
"-module-name", module.moduleName(for: .simulator), "-emit-module", "-emit-module-path", module.modulePath(for: .simulator)
])
case .product:
// $(productName)_simulator.o
let linkedModules = productDependencies.map { ["-module-alias", "\($0.name)=\($0.moduleName(for: .simulator))"] }.flatMap { $0 }
try swiftc(swiftFlags + swiftFlagsSimulator + linkedModules + module.sourcefiles + [
try tools.swiftc(swiftFlags + swiftFlagsSimulator + linkedModules + module.sourcefiles + [
"-c", "-o", module.modulePath(for: .simulator)
])
print("building pdex.dylib")
try clang([
"-nostdlib", "-dead_strip",
"-Wl,-exported_symbol,_eventHandlerShim", "-Wl,-exported_symbol,_eventHandler",
module.modulePath(for: .simulator), "-dynamiclib", "-rdynamic", "-lm",

#if os(Linux)
let linkerFlags = ["-Wl,--undefined=_eventHandlerShim", "-Wl,--undefined=_eventHandler"]
#else
let linkerFlags = ["-Wl,-exported_symbol,_eventHandlerShim", "-Wl,-exported_symbol,_eventHandler"]
#endif

try tools.clang([
"-nostdlib", "-dead_strip"
] + linkerFlags + [
module.modulePath(for: .simulator), "-dynamiclib", "-rdynamic", "-lc", "-lm",
"-DTARGET_SIMULATOR=1", "-DTARGET_EXTENSION=1",
"-I", ".",
"-I", "\(playdateSDK)/C_API",
"-o", sourcePath.appending(["pdex.dylib"]).string,
"\(playdateSDK)/C_API/buildsupport/setup.c"
])
case .productDependency:
try swiftc(swiftFlags + swiftFlagsSimulator + module.sourcefiles + [
try tools.swiftc(swiftFlags + swiftFlagsSimulator + module.sourcefiles + [
"-module-name", module.moduleName(for: .simulator), "-emit-module", "-emit-module-path", module.modulePath(for: .simulator)
])
}
Expand All @@ -302,38 +243,11 @@ struct ModuleBuildRequest {
try await build(module: product)

print("running pdc")
try pdc([
try tools.pdc([
sourcePath.string,
productPath
])
}

func swiftToolchain() throws -> String {
struct Info: Decodable { let CFBundleIdentifier: String }
let toolchainPath = "Library/Developer/Toolchains/swift-latest.xctoolchain"
if let toolchain = ProcessInfo.processInfo.environment["TOOLCHAINS"] {
return toolchain
} else if FileManager.default.fileExists(atPath: "\(home)\(toolchainPath)"),
let data = try? Data(contentsOf: URL(filePath: "\(home)\(toolchainPath)/Info.plist")),
let info = try? PropertyListDecoder().decode(Info.self, from: data) {
return info.CFBundleIdentifier
} else if FileManager.default.fileExists(atPath: "/\(toolchainPath)"),
let data = try? Data(contentsOf: URL(filePath: "/\(toolchainPath)/Info.plist")),
let info = try? PropertyListDecoder().decode(Info.self, from: data) {
return info.CFBundleIdentifier
}
throw Error.swiftToolchainNotFound
}

func playdateSDK() throws -> String {
if let sdk = ProcessInfo.processInfo.environment["PLAYDATE_SDK_PATH"],
FileManager.default.fileExists(atPath: sdk) {
return sdk
} else if FileManager.default.fileExists(atPath: "\(home)Developer/PlaydateSDK/") {
return "\(home)Developer/PlaydateSDK/"
}
throw Error.playdateSDKNotFound
}
}

// MARK: PDCPlugin.Error
Expand Down
Loading
Loading