I am trying to write a script with Swift (not an Xcode project). To be clear, the first line of my file is
#!/usr/bin/swift
And I am just calling it from the command-line.
However, I can't figure out how to have that script use code that is in another .swift file. It doesn't pick it up from the same directory and there is no way to import
that I can see.
Is this supported?
Swift is friendly to new programmers. It's an industrial-quality programming language that's as expressive and enjoyable as a scripting language. Writing Swift code in a playground lets you experiment with code and see the results immediately, without the overhead of building and running an app.
Compiling your Swift file and executing it from Xcode is as easy as doing it from the terminal. All you need to do is define a New Run Script Phase and add the commands you executed on the terminal into it. From the Project navigator, select the project file.
To create a new Swift package, open Xcode and select File > New > Swift Package. Choose a name and select a file location. Select “Create Git repository on my Mac” to put your package under version control. On completion, the Swift package opens in Xcode and looks similar to a standard Xcode project.
I use a variant of Marián's solution:
cat A.swift B.swift main.swift | swift -
There's a better way!
#!/usr/bin/swift -frontend -interpret -enable-source-import -I.
import other_file // this imports other_file.swift in the same folder
funcFromOtherFile()
if you want to import files from ExampleFolder
it would be like:
#!/usr/bin/swift -frontend -interpret -enable-source-import -I./ExampleFolder
import other_file // this imports ./ExampleFolder/other_file.swift
funcFromOtherFile()
My current solution is a simple shell script that concatenates all the files into one and executes the concatenated file:
TMPFILE=`mktemp /tmp/Project.swift.XXXXXX` || exit 1
trap "rm -f $TMPFILE" EXIT
cat *.swift > $TMPFILE
swift $TMPFILE
Inspired by this answer, I've written a simple Python script to replicate a gcc
-esque compilation process. You can find the gist here.
The core idea is to (1) concat all Swift files into one, (2) add some boilerplate code if applicable, and then (3) write the joined text to a temporary file and have Swift execute that file.
#!/usr/bin/env python3
"""
Simple bootstrap script for quickly prototyping
Swift-based macOS applications.
usage: swift-run.py [-h] [-c SWIFT_COMPILER] [-a ARGS] [-m MAIN] [-s SAVE]
file [file ...]
positional arguments:
file list of files to run
optional arguments:
-h, --help show this help message and exit
-c SWIFT_COMPILER, --swift-compiler SWIFT_COMPILER
swift compiler path (default: swift)
-a ARGS, --args ARGS swift compiler args (default: )
-m MAIN, --main MAIN main SwiftUI view (default:
ContentView().frame(maxWidth: .infinity, maxHeight:
.infinity))
-s SAVE, --save SAVE saves the joined swift files + boilerplate to this
file (default: )
"""
import argparse
import os
import subprocess
import tempfile
from typing import List
DEFAULTS = {
"main_view": "ContentView().frame(maxWidth: .infinity, maxHeight: .infinity)",
"swift_args": "",
"swift_exec": "swift",
}
def join_files(files: List[str], boilerplate: str = ""):
all_text = ""
for file in files:
with open(file, "r") as f:
all_text += f.read() + "\n\n"
all_text += boilerplate
return all_text
def exec_text(text: str, swift_exec: str = "", swift_args: str = "", script_out: str = None):
with tempfile.TemporaryDirectory() as tmp_dir:
if script_out is None or script_out.strip() == "":
script_out = os.path.join(tmp_dir, "main.swift")
with open(script_out, "w") as f:
f.write(text)
cmd = f"{swift_exec} {swift_args} {script_out}"
print(cmd)
subprocess.run(
cmd.split(),
check=True,
capture_output=True,
)
def get_boilerplate(main_view: str = "ContentView()"):
return (
"""
// Run any SwiftUI view as a Mac app.
import Cocoa
import SwiftUI
NSApplication.shared.run {
"""
+ main_view
+ """
}
extension NSApplication {
public func run<V: View>(@ViewBuilder view: () -> V) {
let appDelegate = AppDelegate(view())
NSApp.setActivationPolicy(.regular)
mainMenu = customMenu
delegate = appDelegate
run()
}
}
// Inspired by https://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
extension NSApplication {
var customMenu: NSMenu {
let appMenu = NSMenuItem()
appMenu.submenu = NSMenu()
let appName = ProcessInfo.processInfo.processName
appMenu.submenu?.addItem(NSMenuItem(title: "About \(appName)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: ""))
appMenu.submenu?.addItem(NSMenuItem.separator())
let services = NSMenuItem(title: "Services", action: nil, keyEquivalent: "")
self.servicesMenu = NSMenu()
services.submenu = self.servicesMenu
appMenu.submenu?.addItem(services)
appMenu.submenu?.addItem(NSMenuItem.separator())
appMenu.submenu?.addItem(NSMenuItem(title: "Hide \(appName)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h"))
let hideOthers = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h")
hideOthers.keyEquivalentModifierMask = [.command, .option]
appMenu.submenu?.addItem(hideOthers)
appMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: ""))
appMenu.submenu?.addItem(NSMenuItem.separator())
appMenu.submenu?.addItem(NSMenuItem(title: "Quit \(appName)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
let windowMenu = NSMenuItem()
windowMenu.submenu = NSMenu(title: "Window")
windowMenu.submenu?.addItem(NSMenuItem(title: "Minmize", action: #selector(NSWindow.miniaturize(_:)), keyEquivalent: "m"))
windowMenu.submenu?.addItem(NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: ""))
windowMenu.submenu?.addItem(NSMenuItem.separator())
windowMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: "m"))
let mainMenu = NSMenu(title: "Main Menu")
mainMenu.addItem(appMenu)
mainMenu.addItem(windowMenu)
return mainMenu
}
}
class AppDelegate<V: View>: NSObject, NSApplicationDelegate, NSWindowDelegate {
init(_ contentView: V) {
self.contentView = contentView
}
var window: NSWindow!
var hostingView: NSView?
var contentView: V
func applicationDidFinishLaunching(_ notification: Notification) {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
hostingView = NSHostingView(rootView: contentView)
window.contentView = hostingView
window.makeKeyAndOrderFront(nil)
window.delegate = self
NSApp.activate(ignoringOtherApps: true)
}
}
"""
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"files", metavar="file", type=str, nargs="+", help="list of files to run"
)
parser.add_argument(
"-c",
"--swift-compiler",
help="swift compiler path",
type=str,
default=DEFAULTS["swift_exec"],
)
parser.add_argument(
"-a",
"--args",
help="swift compiler args",
type=str,
default=DEFAULTS["swift_args"],
)
parser.add_argument(
"-m",
"--main",
help="main SwiftUI view",
type=str,
default=DEFAULTS["main_view"],
)
parser.add_argument(
"-s",
"--save",
help="saves the joined swift files + boilerplate to this file",
type=str,
default="",
)
args = parser.parse_args()
print(args)
exec_text(
join_files(args.files, boilerplate=get_boilerplate(args.main)),
swift_exec=args.swift_compiler,
swift_args=args.args,
script_out=args.save
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With