mirror of
https://github.com/xtool-org/xtool.git
synced 2026-02-04 11:53:30 +01:00
146 lines
5.4 KiB
Swift
146 lines
5.4 KiB
Swift
#if os(macOS)
|
|
import Foundation
|
|
import PathKit
|
|
import Version
|
|
import ProjectSpec
|
|
import XcodeGenKit
|
|
import XcodeProj
|
|
|
|
public struct XcodePacker {
|
|
public var plan: Plan
|
|
|
|
public init(plan: Plan) {
|
|
self.plan = plan
|
|
}
|
|
|
|
public func createProject() async throws -> URL {
|
|
let targetName = "\(plan.product)-App"
|
|
|
|
let xtoolDir: Path = "xtool"
|
|
|
|
let projectDir: Path = xtoolDir + ".xtool-tmp"
|
|
try? xtoolDir.delete()
|
|
try projectDir.mkpath()
|
|
|
|
let infoPath = projectDir + "Info.plist"
|
|
|
|
var plist = plan.infoPlist
|
|
let families = (plist.removeValue(forKey: "UIDeviceFamily") as? [Int]) ?? [1, 2]
|
|
plist["CFBundleExecutable"] = targetName
|
|
plist["CFBundleName"] = targetName
|
|
plist["CFBundleDisplayName"] = plan.product
|
|
|
|
let encodedPlist = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)
|
|
try infoPath.write(encodedPlist)
|
|
|
|
let emptyFile = projectDir + "empty.c"
|
|
try emptyFile.write(Data("// leave this file empty".utf8))
|
|
|
|
let fromProjectToRoot = try Path(".").relativePath(from: projectDir)
|
|
|
|
guard let deploymentTarget = Version(tolerant: plan.deploymentTarget) else {
|
|
throw StringError("Could not parse deployment target '\(plan.deploymentTarget)'")
|
|
}
|
|
|
|
let project = Project(
|
|
name: plan.product,
|
|
targets: [
|
|
Target(
|
|
name: targetName,
|
|
type: .application,
|
|
platform: .iOS,
|
|
deploymentTarget: deploymentTarget,
|
|
settings: Settings(buildSettings: [
|
|
"PRODUCT_BUNDLE_IDENTIFIER": plan.bundleID,
|
|
"TARGETED_DEVICE_FAMILY": families.map { "\($0)" }.joined(separator: ","),
|
|
]),
|
|
sources: [
|
|
TargetSource(path: (fromProjectToRoot + emptyFile).string),
|
|
],
|
|
dependencies: [
|
|
Dependency(
|
|
type: .package(products: [plan.product]),
|
|
reference: "RootPackage"
|
|
),
|
|
],
|
|
info: Plist(
|
|
path: (fromProjectToRoot + infoPath).string,
|
|
attributes: [:]
|
|
)
|
|
)
|
|
],
|
|
packages: [
|
|
"RootPackage": .local(
|
|
path: fromProjectToRoot.string,
|
|
group: nil,
|
|
excludeFromProject: false
|
|
),
|
|
],
|
|
options: SpecOptions(
|
|
localPackagesGroup: ""
|
|
)
|
|
)
|
|
|
|
// TODO: Handle plan.resources of type .root
|
|
// TODO: Handle plan.iconPath
|
|
|
|
let generator = ProjectGenerator(project: project)
|
|
let xcodeproj = projectDir + "\(plan.product).xcodeproj"
|
|
let xcworkspace = xtoolDir + "\(plan.product).xcworkspace"
|
|
do {
|
|
let current = Path.current
|
|
Path.current = xcodeproj.parent()
|
|
defer { Path.current = current }
|
|
let xcodeProject = try generator.generateXcodeProject(userName: NSUserName())
|
|
if let packageRef = xcodeProject.pbxproj.fileReferences.first(where: { $0.name == ".." }) {
|
|
for group in xcodeProject.pbxproj.groups {
|
|
group.children.removeAll(where: { $0.uuid == packageRef.uuid })
|
|
}
|
|
xcodeProject.pbxproj.delete(object: packageRef)
|
|
}
|
|
|
|
if let emptyFileRef = xcodeProject.pbxproj.fileReferences.first(where: { $0.path == "empty.c" }),
|
|
let existingGroup = xcodeProject.pbxproj.groups.first(where: {
|
|
$0.children.contains { $0.uuid == emptyFileRef.uuid }
|
|
}),
|
|
let existingGroupGroup = xcodeProject.pbxproj.groups.first(where: {
|
|
$0.children.contains { $0.uuid == existingGroup.uuid }
|
|
}) {
|
|
existingGroupGroup.children.removeAll(where: { $0.uuid == existingGroup.uuid })
|
|
existingGroupGroup.children.insert(emptyFileRef, at: 0)
|
|
xcodeProject.pbxproj.delete(object: existingGroup)
|
|
}
|
|
|
|
try xcodeProject.write(path: Path(xcodeproj.lastComponent))
|
|
}
|
|
|
|
do {
|
|
let current = Path.current
|
|
Path.current = xcworkspace.parent()
|
|
defer { Path.current = current }
|
|
|
|
let xcworkspaceDirectory = xcworkspace.parent()
|
|
let fromWorkspaceToSelf = try Path(".").relativePath(from: xcworkspaceDirectory).withName()
|
|
let fromWorkspaceToProject = try xcodeproj.relativePath(from: xcworkspaceDirectory).withName()
|
|
let workspace = XCWorkspace(data: .init(children: [
|
|
.file(.init(location: .container(fromWorkspaceToSelf.string))),
|
|
.file(.init(location: .group(fromWorkspaceToProject.string))),
|
|
]))
|
|
try workspace.write(path: Path(xcworkspace.lastComponent))
|
|
}
|
|
|
|
return xcworkspace.url
|
|
}
|
|
}
|
|
|
|
extension Path {
|
|
fileprivate func withName() -> Path {
|
|
// eg if curr dir is Foo, this converts "." to "../Foo"
|
|
// which includes the name in the path, and therefore
|
|
// in the Xcode navigator
|
|
self.parent() + self.absolute().lastComponent
|
|
}
|
|
}
|
|
|
|
#endif
|