-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
234 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"steps": [ | ||
{ | ||
"name": "Build Playground.app", | ||
"type": "macos-bundle", | ||
"with": { | ||
"name": "Playground", | ||
"identifier": "dev.shaftui.playground", | ||
"version": "1.0.0", | ||
"product": "Playground", | ||
"output": ".build/Playground.app" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,90 @@ | ||
// { | ||
// "app": { | ||
// "name": "Playground", | ||
// "identifier": "dev.shaftui.playground", | ||
// "version": "1.0.0", | ||
// "product": "Playground" | ||
// } | ||
// "steps": [ | ||
// { | ||
// "name": "Build ShaftBrowser.app", | ||
// "type": "macos-bundle", | ||
// "with": { | ||
// "name": "ShaftBrowser", | ||
// "identifier": "dev.shaftui.browser", | ||
// "version": "1.0.0", | ||
// "product": "ShaftBrowser", | ||
// "output": ".build/ShaftBrowser.app" | ||
// } | ||
// }, | ||
// { | ||
// "name": "Build ShaftBrowser Helper.app", | ||
// "type": "macos-bundle", | ||
// "with": { | ||
// "name": "ShaftBrowser Helper", | ||
// "identifier": "dev.shaftui.browserhelper", | ||
// "version": "1.0.0", | ||
// "product": "ShaftBrowserHelper", | ||
// "output": ".build/ShaftBrowser Helper.app" | ||
// } | ||
// }, | ||
// { | ||
// "name": "Move ShaftBrowser Helper.app to ShaftBrowser.app/Contents/Frameworks", | ||
// "type": "move", | ||
// "with": { | ||
// "source": ".build/ShaftBrowser Helper.app", | ||
// "destination": ".build/ShaftBrowser.app/Contents/Frameworks" | ||
// } | ||
// } | ||
// ] | ||
// } | ||
|
||
struct BuildSpec: Codable { | ||
let app: AppSpec? | ||
let steps: [BuildStep] | ||
} | ||
|
||
/// The specification for the app | ||
struct AppSpec: Codable { | ||
/// The name of the app | ||
let name: String | ||
enum BuildStepType: String, Codable { | ||
case macOSBundle = "macos-bundle" | ||
case move = "move" | ||
} | ||
|
||
/// The identifier of the app. This is usually a reverse domain name. | ||
let identifier: String | ||
enum BuildStep: Codable { | ||
case macOSBundle(MacOSBundleInput) | ||
case move(MoveInput) | ||
|
||
/// The version of the app | ||
let version: String | ||
init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let type = try container.decode(BuildStepType.self, forKey: .type) | ||
switch type { | ||
case .macOSBundle: | ||
self = .macOSBundle(try container.decode(MacOSBundleInput.self, forKey: .with)) | ||
case .move: | ||
self = .move(try container.decode(MoveInput.self, forKey: .with)) | ||
} | ||
} | ||
|
||
func encode(to encoder: Encoder) throws { | ||
switch self { | ||
case .macOSBundle(let input): | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(BuildStepType.macOSBundle, forKey: .type) | ||
try container.encode(input, forKey: .with) | ||
case .move(let input): | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
try container.encode(BuildStepType.move, forKey: .type) | ||
try container.encode(input, forKey: .with) | ||
} | ||
} | ||
|
||
/// Name of the product defined in the package. The product will be used to | ||
/// create the bundle. | ||
private enum CodingKeys: String, CodingKey { | ||
case type | ||
case with | ||
} | ||
} | ||
|
||
struct MacOSBundleInput: Codable { | ||
let name: String | ||
let identifier: String | ||
let version: String | ||
let product: String | ||
let output: String | ||
} | ||
|
||
struct MoveInput: Codable { | ||
let source: String | ||
let destination: String | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import Foundation | ||
import PackagePlugin | ||
|
||
func executeMacOSBundleStep(_ input: MacOSBundleInput, context: StepContext) { | ||
// Ensure the product exists in the package | ||
guard let product = context.findProduct(input.product) else { | ||
print("Product \(input.product) not found. Valid products are:") | ||
for product in context.package.products { | ||
print(" - \(product.name)") | ||
} | ||
exit(1) | ||
} | ||
|
||
print("Building \(product.name) in \(context.configuration.rawValue) mode") | ||
|
||
var buildParameters = PackageManager.BuildParameters(echoLogs: true) | ||
buildParameters.otherLinkerFlags = ["-L.shaft/skia"] | ||
|
||
let buildResult = try! context.packageManager.build( | ||
.product(input.product), | ||
parameters: buildParameters | ||
) | ||
|
||
if !buildResult.succeeded { | ||
print("Build failed. See logs above for details.") | ||
return | ||
} | ||
|
||
guard let mainArtifect = buildResult.builtArtifacts.first else { | ||
print("No built artifacts found. Skipping bundle creation.") | ||
return | ||
} | ||
print("Main artifact: \(mainArtifect.path.string)") | ||
|
||
if mainArtifect.kind != .executable { | ||
print("Main artifact is \(mainArtifect.kind). Skipping bundle creation.") | ||
return | ||
} | ||
|
||
print("Creating bundle with input: \(input)") | ||
createBundle(from: mainArtifect, input: input, configuration: context.configuration) | ||
} | ||
|
||
private func createBundle( | ||
from artifect: PackageManager.BuildResult.BuiltArtifact, | ||
input: MacOSBundleInput, | ||
configuration: BuilderConfiguration | ||
) { | ||
let outputDirectory = URL(fileURLWithPath: input.output) | ||
|
||
print("Creating bundle at \(outputDirectory)") | ||
|
||
let structure = AppleBundleStructure( | ||
at: outputDirectory, | ||
platform: .macOS, | ||
appName: input.name | ||
) | ||
|
||
try! structure.ensureDirectoriesExist() | ||
|
||
/// Copy the main executable | ||
copyFile(from: artifect.path.string, to: structure.mainExecutable.path) | ||
|
||
/// Copy .dSYM file in debug mode | ||
if configuration == .debug { | ||
copyFile(from: artifect.path.string + ".dSYM", to: structure.mainExecutableDSYM.path) | ||
} | ||
|
||
// Write the Info.plist | ||
let infoPlist = """ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleExecutable</key> | ||
<string>\(input.product)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>\(input.identifier)</string> | ||
<key>CFBundleName</key> | ||
<string>\(input.name)</string> | ||
<key>CFBundleVersion</key> | ||
<string>\(input.version)</string> | ||
</dict> | ||
</plist> | ||
""" | ||
|
||
try! infoPlist.write(to: structure.infoPlistFile, atomically: true, encoding: .utf8) | ||
|
||
print("Bundle created at \(outputDirectory)") | ||
} | ||
|
||
private func copyFile(from source: String, to destination: String) { | ||
if FileManager.default.fileExists(atPath: destination) { | ||
print("Removing existing file at \(destination)") | ||
try! FileManager.default.removeItem(atPath: destination) | ||
} | ||
print("Copying \(source) to \(destination)") | ||
try! FileManager.default.copyItem(atPath: source, toPath: destination) | ||
} |
Oops, something went wrong.