Skip to content

Commit

Permalink
Allow target to be configured in Bundle.json
Browse files Browse the repository at this point in the history
Also changed zsh to sh to be more robust (FunkyMonkey's Mac was
being super strange and swift-bundler couldn't find zsh yet they
use zsh as their shell. Also cleaned up config loading and cleaned
things up in general
  • Loading branch information
stackotter committed Oct 18, 2021
1 parent 40839cf commit 7ee0005
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 79 deletions.
19 changes: 19 additions & 0 deletions Sources/swift-bundler/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation

struct Configuration: Codable {
var target: String
var bundleIdentifier: String
var versionString: String
var buildNumber: Int
var category: String
var minOSVersion: String

static func load(_ directory: URL) -> Configuration {
do {
let data = try Data(contentsOf: directory.appendingPathComponent("Bundle.json"))
return try JSONDecoder().decode(Configuration.self, from: data)
} catch {
terminate("Failed to load config from Bundle.json. Please make sure that the current directory is setup for swift-bundler correctly; \(error)")
}
}
}
11 changes: 2 additions & 9 deletions Sources/swift-bundler/Subcommands/Build.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,13 @@ extension Bundler {

let configuration = configuration ?? .debug
let outputDir = outputDir ?? packageDir.appendingPathComponent(".build/bundler")
let packageName = getPackageName(from: packageDir)

// Run prebuild script if it exists
updateProgress("Running prebuild script", 0.02)
runPrebuild(packageDir)

updateProgress("Loading configuration", 0.05)
let config: Configuration
do {
let data = try Data(contentsOf: packageDir.appendingPathComponent("Bundle.json"))
config = try JSONDecoder().decode(Configuration.self, from: data)
} catch {
terminate("Failed to load config from Bundle.json; \(error)")
}
let config = Configuration.load(packageDir)

// Build package
updateProgress("Starting \(configuration.rawValue) build...", 0.1)
Expand Down Expand Up @@ -119,7 +112,7 @@ extension Bundler {
let buildDir = buildDirSymlink.resolvingSymlinksInPath()
Bundler.bundle(
packageDir: packageDir,
packageName: packageName,
target: config.target,
productsDir: buildDir,
outputDir: outputDir,
config: config,
Expand Down
26 changes: 9 additions & 17 deletions Sources/swift-bundler/Subcommands/Bundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,15 @@ struct Bundle: ParsableCommand {
var createSymlink = false

func run() throws {
let packageName = getPackageName(from: packageDir)

// Load configuration
let config: Configuration
do {
let data = try Data(contentsOf: packageDir.appendingPathComponent("Bundle.json"))
config = try JSONDecoder().decode(Configuration.self, from: data)
} catch {
terminate("Failed to load config from Bundle.json; \(error)")
}
let config = Configuration.load(packageDir)

if displayProgress {
runProgressJob({ setMessage, setProgress in
// A helper function to update the progress window (if present)
Bundler.bundle(
packageDir: packageDir,
packageName: packageName,
target: config.target,
productsDir: productsDir,
executable: executable,
outputDir: outputDir,
Expand All @@ -60,7 +52,7 @@ struct Bundle: ParsableCommand {
} else {
Bundler.bundle(
packageDir: packageDir,
packageName: packageName,
target: config.target,
productsDir: productsDir,
executable: executable,
outputDir: outputDir,
Expand All @@ -74,7 +66,7 @@ struct Bundle: ParsableCommand {
extension Bundler {
static func bundle(
packageDir: URL,
packageName: String,
target: String,
productsDir: URL? = nil,
executable: URL? = nil,
outputDir: URL? = nil,
Expand All @@ -87,15 +79,15 @@ extension Bundler {
updateProgressClosure(message, progress, shouldLog)
}

guard let executable = executable ?? productsDir?.appendingPathComponent("\(packageName)") else {
guard let executable = executable ?? productsDir?.appendingPathComponent("\(target)") else {
terminate("Please provide the `products-dir` option and/or the `executable` option")
}

let outputDir = outputDir ?? packageDir.appendingPathComponent(".build/bundler")

// Create app folder structure
updateProgress("Creating .app skeleton", 0)
let app = outputDir.appendingPathComponent("\(packageName).app")
let app = outputDir.appendingPathComponent("\(target).app")
let appContents = app.appendingPathComponent("Contents")
let appResources = appContents.appendingPathComponent("Resources")
let appMacOS = appContents.appendingPathComponent("MacOS")
Expand All @@ -113,9 +105,9 @@ extension Bundler {
// Copy executable
updateProgress("Copying executable", 0.2)
do {
try FileManager.default.copyItem(at: executable, to: appMacOS.appendingPathComponent(packageName))
try FileManager.default.copyItem(at: executable, to: appMacOS.appendingPathComponent(target))
} catch {
terminate("Failed to copy built executable to \(appMacOS.appendingPathComponent(packageName).path); \(error)")
terminate("Failed to copy built executable to \(appMacOS.appendingPathComponent(target).path); \(error)")
}

// Create app icon
Expand Down Expand Up @@ -152,7 +144,7 @@ extension Bundler {
updateProgress("Creating Info.plist", 0.7)
let infoPlistFile = appContents.appendingPathComponent("Info.plist")
let infoPlist = createAppInfoPlist(
packageName: packageName,
appName: target,
bundleIdentifier: config.bundleIdentifier,
versionString: config.versionString,
buildNumber: config.buildNumber,
Expand Down
49 changes: 26 additions & 23 deletions Sources/swift-bundler/Subcommands/GenerateXcodeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ struct GenerateXcodeSupport: ParsableCommand {

extension Bundler {
static func generateXcodeSupport(_ packageDir: URL) {
// Load configuration
let config = Configuration.load(packageDir)

// We used to create an xcodeproj but that had issues with autocomplete and certain types of dependencies showing up in multiple places, so now we just prepopulate the .swiftpm directory which xcode reads from when opening a swift package
// And now `swift package generate-xcodeproj` is getting phased out too. This solution just works much nicer in general
log.info("Creating schemes")
let packageName = getPackageName(from: packageDir)
let schemeString = createScheme(for: packageName)
let target = config.target
let schemeString = createScheme(for: target)
let schemesDir = packageDir.appendingPathComponent(".swiftpm/xcode/xcshareddata/xcschemes")
let schemeFile = schemesDir.appendingPathComponent("\(packageName).xcscheme")
let schemeFile = schemesDir.appendingPathComponent("\(target).xcscheme")
do {
try FileManager.default.createDirectory(at: schemesDir)
try schemeString.write(to: schemeFile, atomically: false, encoding: .utf8)
Expand All @@ -28,12 +31,12 @@ extension Bundler {
}
}

fileprivate static func createScheme(for packageName: String) -> String {
fileprivate static func createScheme(for target: String) -> String {
let buildOutputDir: URL
let builtApp: URL
do {
buildOutputDir = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("dev.stackotter.swift-bundler")
builtApp = buildOutputDir.appendingPathComponent("\(packageName).app")
builtApp = buildOutputDir.appendingPathComponent("\(target).app")
try FileManager.default.createDirectory(at: builtApp)
} catch {
terminate("Failed to locate and create built app directory; \(error)")
Expand All @@ -59,9 +62,9 @@ extension Bundler {
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "\(packageName)"
BuildableName = "\(packageName)"
BlueprintName = "\(packageName)"
BlueprintIdentifier = "\(target)"
BuildableName = "\(target)"
BlueprintName = "\(target)"
ReferencedContainer = "container:">
</BuildableReference>
</EnvironmentBuildable>
Expand All @@ -77,9 +80,9 @@ extension Bundler {
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "\(packageName)"
BuildableName = "\(packageName)"
BlueprintName = "\(packageName)"
BlueprintIdentifier = "\(target)"
BuildableName = "\(target)"
BlueprintName = "\(target)"
ReferencedContainer = "container:">
</BuildableReference>
</EnvironmentBuildable>
Expand All @@ -93,9 +96,9 @@ extension Bundler {
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "\(packageName)"
BuildableName = "\(packageName)"
BlueprintName = "\(packageName)"
BlueprintIdentifier = "\(target)"
BuildableName = "\(target)"
BlueprintName = "\(target)"
ReferencedContainer = "container:">
</BuildableReference>
</EnvironmentBuildable>
Expand All @@ -111,9 +114,9 @@ extension Bundler {
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "\(packageName)"
BuildableName = "\(packageName)"
BlueprintName = "\(packageName)"
BlueprintIdentifier = "\(target)"
BuildableName = "\(target)"
BlueprintName = "\(target)"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
Expand Down Expand Up @@ -144,9 +147,9 @@ extension Bundler {
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "\(packageName)"
BuildableName = "\(packageName)"
BlueprintName = "\(packageName)"
BlueprintIdentifier = "\(target)"
BuildableName = "\(target)"
BlueprintName = "\(target)"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
Expand All @@ -161,9 +164,9 @@ extension Bundler {
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "\(packageName)"
BuildableName = "\(packageName)"
BlueprintName = "\(packageName)"
BlueprintIdentifier = "\(target)"
BuildableName = "\(target)"
BlueprintName = "\(target)"
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
Expand Down
22 changes: 7 additions & 15 deletions Sources/swift-bundler/Subcommands/Init.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import Foundation
import ArgumentParser

struct Configuration: Codable {
var bundleIdentifier: String
var versionString: String
var buildNumber: Int
var category: String
var minOSVersion: String
}

struct Init: ParsableCommand {
static let configuration = CommandConfiguration(abstract: "Initialise a new Swift executable package and set it up for the bundler.", discussion: "To add bundler to an existing swift package you currently need to do so manually.")

Expand All @@ -35,10 +27,12 @@ struct Init: ParsableCommand {

func run() throws {
// Initialise the swift package
var name = packageName.replacingOccurrences(of: "-", with: "_")
name = name.replacingOccurrences(of: " ", with: "_")
var packageName = packageName.replacingOccurrences(of: "-", with: "_")
packageName = packageName.replacingOccurrences(of: " ", with: "_")

let directory = self.directory ?? URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent(name)
log.info("Using package name '\(packageName)' (spaces and hyphens are not allowed and may have been replaced)")

let directory = self.directory ?? URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent(packageName)
if !FileManager.default.itemExists(at: directory, withType: .directory) {
do {
try FileManager.default.createDirectory(at: directory)
Expand All @@ -48,14 +42,11 @@ struct Init: ParsableCommand {
}

log.info("Initialising swift package")
let command = "swift package init --type=executable --name=\"\(name)\""
let command = "swift package init --type=executable --name=\"\(packageName)\""
if Shell.getExitStatus(command, directory, silent: false) != 0 {
terminate("Failed to initialise default swift package")
}

// Just in-case swiftpm init changed the name provided
let packageName = getPackageName(from: directory)

log.info("Setting minimum macOS version")
setMinMacOSVersion(directory, packageName)
log.info("Replacing hello world example")
Expand All @@ -66,6 +57,7 @@ struct Init: ParsableCommand {
// Create default configuration
log.info("Creating configuration")
let config = Configuration(
target: packageName,
bundleIdentifier: bundleIdentifier ?? "com.example.\(packageName)",
versionString: versionString ?? "0.1.0",
buildNumber: buildNumber ?? 1,
Expand Down
15 changes: 11 additions & 4 deletions Sources/swift-bundler/Subcommands/Run.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ struct Run: ParsableCommand {
var shouldBuildUniversal = false

func run() throws {
let packageName = getPackageName(from: packageDir)
// Load configuration
let config = Configuration.load(packageDir)
let target = config.target
let outputDir = outputDir ?? packageDir.appendingPathComponent(".build/bundler")
let executable = outputDir.appendingPathComponent("\(packageName).app/Contents/MacOS/\(packageName)")
let executable = outputDir.appendingPathComponent("\(target).app/Contents/MacOS/\(target)")

if displayProgress {
runProgressJob({ setMessage, setProgress in
Expand All @@ -44,13 +46,18 @@ struct Run: ParsableCommand {
packageDir: packageDir,
configuration: configuration,
outputDir: outputDir,
shouldBuildUniversal: shouldBuildUniversal)
shouldBuildUniversal: shouldBuildUniversal,
updateProgress: { message, _, shouldLog in
if shouldLog {
log.info(message)
}
})
}

print() // New line to separate app output from bundler output

if Shell.getExitStatus(executable.path, silent: false) != 0 {
terminate("Failed to run bundled app at \(outputDir.appendingPathComponent("\(packageName).app").path)")
terminate("Failed to run bundled app at \(outputDir.appendingPathComponent("\(target).app").path)")
}
}
}
2 changes: 1 addition & 1 deletion Sources/swift-bundler/Utils/Shell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ enum Shell {
} else {
task.arguments = ["-c", "\(command)"]
}
task.launchPath = "/bin/zsh"
task.launchPath = "/bin/sh"
tasks.append(task)

return task
Expand Down
10 changes: 3 additions & 7 deletions Sources/swift-bundler/Utils/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ enum BundlerError: LocalizedError {
case icnsCreationFailed(exitStatus: Int)
}

func getPackageName(from dir: URL) -> String {
return String(Shell.getOutput(#"grep '\w*name: ".*",' Package.swift"#, dir).split(separator: "\n")[0].split(separator: "\"")[1])
}

/// Creates an AppIcon.icns in the given directory from the given 1024x1024 input png.
func createIcns(from icon1024: URL, outDir: URL) throws {
let iconPath = icon1024.path
Expand All @@ -32,14 +28,14 @@ rm -R \(iconSetPath)
}
}

func createAppInfoPlist(packageName: String, bundleIdentifier: String, versionString: String, buildNumber: Int, category: String, minOSVersion: String) -> String {
func createAppInfoPlist(appName: String, bundleIdentifier: String, versionString: String, buildNumber: Int, category: String, minOSVersion: String) -> String {
return """
<?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>\(packageName)</string>
<string>\(appName)</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
<key>CFBundleIconName</key>
Expand All @@ -49,7 +45,7 @@ func createAppInfoPlist(packageName: String, bundleIdentifier: String, versionSt
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>\(packageName)</string>
<string>\(appName)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
Expand Down
4 changes: 1 addition & 3 deletions Sources/swift-bundler/main.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
Bundler.main()

// TODO: support sandboxing
// TODO: add proper help messages to subcommands, options and flags
// TODO: silence warnings from dependencies
// TODO: automatically detect when bundles don't need fixing
// TODO: add proper help messages to subcommands, options and flags

0 comments on commit 7ee0005

Please sign in to comment.