├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.resolved ├── Package.swift ├── Plugins ├── RunScriptCommandPlugin │ └── plugin.swift └── RunScriptPlugin │ └── plugin.swift ├── README.md ├── Sources └── run-script │ ├── Config.swift │ ├── main.swift │ └── print.swift └── scripts ├── create_artifactbundle.sh └── info.json.template /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/config/registries.json 8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 9 | .netrc 10 | 11 | run-script-bin.artifactbundle 12 | run-script-bin.artifactbundle.zip -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 p-x9 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-argument-parser", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-argument-parser", 7 | "state" : { 8 | "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a", 9 | "version" : "1.2.2" 10 | } 11 | }, 12 | { 13 | "identity" : "yams", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/jpsim/Yams.git", 16 | "state" : { 17 | "revision" : "4889c132978bc6ad3e80f680011ec3dd4fead90c", 18 | "version" : "5.0.4" 19 | } 20 | } 21 | ], 22 | "version" : 2 23 | } 24 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RunScriptPlugin", 7 | products: [ 8 | .plugin( 9 | name: "RunScriptPlugin", 10 | targets: ["RunScriptPlugin"] 11 | ), 12 | .plugin( 13 | name: "RunScriptCommandPlugin", 14 | targets: ["RunScriptCommandPlugin"] 15 | ), 16 | .executable( 17 | name: "run-script", 18 | targets: ["run-script"] 19 | ) 20 | ], 21 | dependencies: [ 22 | .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"), 23 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0") 24 | ], 25 | targets: [ 26 | .executableTarget( 27 | name: "run-script", 28 | dependencies: [ 29 | .product(name: "Yams", package: "Yams"), 30 | .product(name: "ArgumentParser", package: "swift-argument-parser") 31 | ] 32 | ), 33 | .binaryTarget( 34 | name: "run-script-bin", 35 | url: "https://github.com/p-x9/RunScriptPlugin/releases/download/0.4.0/run-script-bin.artifactbundle.zip", 36 | checksum: "28a4cacfbc3bb404e8a908837964afadc908da16b26041b0752f5d6f8b5b1644" 37 | ), 38 | // DEBUG 39 | // .binaryTarget(name: "run-script-bin", path: "./run-script-bin.artifactbundle.zip"), 40 | .plugin( 41 | name: "RunScriptPlugin", 42 | capability: .buildTool(), 43 | dependencies: [ 44 | "run-script-bin" 45 | ] 46 | ), 47 | .plugin( 48 | name: "RunScriptCommandPlugin", 49 | capability: .command( 50 | intent: .custom( 51 | verb: "run-script", 52 | description: "run scripts defined in `runscript.yml`" 53 | ), 54 | permissions: [ 55 | .writeToPackageDirectory(reason: "to run scripts defined in `runscript.yml`") 56 | ] 57 | ), 58 | dependencies: [ 59 | "run-script-bin" 60 | ] 61 | ) 62 | ] 63 | ) 64 | -------------------------------------------------------------------------------- /Plugins/RunScriptCommandPlugin/plugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // plugin.swift 3 | // 4 | // 5 | // Created by p-x9 on 2023/03/21. 6 | // 7 | 8 | import Foundation 9 | import PackagePlugin 10 | 11 | @main 12 | struct RunScriptCommandPlugin: CommandPlugin { 13 | func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { 14 | try performCommand( 15 | packageDirectory: context.package.directory, 16 | workingDirectory: context.pluginWorkDirectory, 17 | tool: try context.tool(named: "run-script-bin"), 18 | arguments: arguments 19 | ) 20 | } 21 | 22 | private func performCommand( 23 | packageDirectory: Path, 24 | workingDirectory: Path, 25 | tool: PluginContext.Tool, 26 | arguments: [String] 27 | ) throws { 28 | var argumentExtractor = ArgumentExtractor(arguments) 29 | let config = argumentExtractor.extractOption(named: "config").first 30 | ?? packageDirectory.firstConfigurationFileInParentDirectories()?.string ?? "" 31 | let timing = argumentExtractor.extractOption(named: "timing").first ?? "command" 32 | 33 | let process = Process() 34 | process.launchPath = tool.path.string 35 | process.arguments = [ 36 | "--config", 37 | config, 38 | "--timing", 39 | timing 40 | ] 41 | process.environment = environments( 42 | packageDirectory: packageDirectory, 43 | workingDirectory: workingDirectory 44 | ) 45 | 46 | try process.run() 47 | process.waitUntilExit() 48 | } 49 | 50 | private func environments( 51 | packageDirectory: Path, 52 | workingDirectory: Path 53 | ) -> [String: String] { 54 | var environments = ProcessInfo.processInfo.environment 55 | environments["RUN_SCRIPT_TARGET_PACKAGE_DIR"] = packageDirectory.string 56 | environments["RUN_SCRIPT_PLUGIN_WORK_DIR"] = workingDirectory.string 57 | return environments 58 | } 59 | } 60 | 61 | #if canImport(XcodeProjectPlugin) 62 | import XcodeProjectPlugin 63 | 64 | extension RunScriptCommandPlugin: XcodeCommandPlugin { 65 | func performCommand(context: XcodeProjectPlugin.XcodePluginContext, arguments: [String]) throws { 66 | try performCommand( 67 | packageDirectory: context.xcodeProject.directory, 68 | workingDirectory: context.pluginWorkDirectory, 69 | tool: try context.tool(named: "run-script-bin"), 70 | arguments: arguments 71 | ) 72 | } 73 | } 74 | #endif 75 | 76 | // ref: https://github.com/realm/SwiftLint/blob/main/Plugins/SwiftLintPlugin/Path%2BHelpers.swift 77 | extension Path { 78 | func firstConfigurationFileInParentDirectories() -> Path? { 79 | let defaultConfigurationFileNames = [ 80 | "runscript.yml", 81 | ".runscript.yml" 82 | ] 83 | let proposedDirectories = sequence( 84 | first: self, 85 | next: { path in 86 | guard path.stem.count > 1 else { 87 | // Check we're not at the root of this filesystem, as `removingLastComponent()` 88 | // will continually return the root from itself. 89 | return nil 90 | } 91 | 92 | return path.removingLastComponent() 93 | } 94 | ) 95 | 96 | for proposedDirectory in proposedDirectories { 97 | for fileName in defaultConfigurationFileNames { 98 | let potentialConfigurationFile = proposedDirectory.appending(subpath: fileName) 99 | if potentialConfigurationFile.isAccessible() { 100 | return potentialConfigurationFile 101 | } 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | /// Safe way to check if the file is accessible from within the current process sandbox. 108 | private func isAccessible() -> Bool { 109 | let result = string.withCString { pointer in 110 | access(pointer, R_OK) 111 | } 112 | 113 | return result == 0 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Plugins/RunScriptPlugin/plugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // plugin.swift 3 | // 4 | // 5 | // Created by p-x9 on 2023/02/04. 6 | // 7 | // 8 | 9 | import Foundation 10 | import PackagePlugin 11 | 12 | @main 13 | struct RunScriptPlugin: BuildToolPlugin { 14 | func createBuildCommands(context: PackagePlugin.PluginContext, target: PackagePlugin.Target) async throws -> [PackagePlugin.Command] { 15 | createBuildCommands( 16 | packageDirectory: context.package.directory, 17 | workingDirectory: context.pluginWorkDirectory, 18 | tool: try context.tool(named: "run-script-bin") 19 | ) 20 | } 21 | 22 | private func createBuildCommands( 23 | packageDirectory: Path, 24 | workingDirectory: Path, 25 | tool: PluginContext.Tool 26 | ) -> [Command] { 27 | guard let configuration = packageDirectory.firstConfigurationFileInParentDirectories() else { 28 | return [] 29 | } 30 | 31 | let arguments = ["--config", configuration.string] 32 | 33 | let environments = environments( 34 | packageDirectory: packageDirectory, 35 | workingDirectory: workingDirectory 36 | ) 37 | 38 | return [ 39 | .prebuildCommand( 40 | displayName: "RunScriptPlugin(PreBuild)", 41 | executable: tool.path, 42 | arguments: arguments + ["--timing", "prebuild"], 43 | environment: environments, 44 | outputFilesDirectory: workingDirectory 45 | ), 46 | .buildCommand( 47 | displayName: "RunScriptPlugin(Build)", 48 | executable: tool.path, 49 | arguments: arguments + ["--timing", "build"], 50 | environment: environments, 51 | outputFiles: [workingDirectory] 52 | ) 53 | ] 54 | } 55 | 56 | private func environments( 57 | packageDirectory: Path, 58 | workingDirectory: Path 59 | ) -> [String: any CustomStringConvertible] { 60 | var environments = ProcessInfo.processInfo.environment 61 | environments["RUN_SCRIPT_TARGET_PACKAGE_DIR"] = packageDirectory.string 62 | environments["RUN_SCRIPT_PLUGIN_WORK_DIR"] = workingDirectory.string 63 | return environments 64 | } 65 | } 66 | 67 | #if canImport(XcodeProjectPlugin) 68 | import XcodeProjectPlugin 69 | 70 | extension RunScriptPlugin: XcodeBuildToolPlugin { 71 | func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { 72 | return createBuildCommands( 73 | packageDirectory: context.xcodeProject.directory, 74 | workingDirectory: context.pluginWorkDirectory, 75 | tool: try context.tool(named: "run-script-bin") 76 | ) 77 | } 78 | } 79 | #endif 80 | 81 | // ref: https://github.com/realm/SwiftLint/blob/main/Plugins/SwiftLintPlugin/Path%2BHelpers.swift 82 | extension Path { 83 | func firstConfigurationFileInParentDirectories() -> Path? { 84 | let defaultConfigurationFileNames = [ 85 | "runscript.yml", 86 | ".runscript.yml" 87 | ] 88 | let proposedDirectories = sequence( 89 | first: self, 90 | next: { path in 91 | guard path.stem.count > 1 else { 92 | // Check we're not at the root of this filesystem, as `removingLastComponent()` 93 | // will continually return the root from itself. 94 | return nil 95 | } 96 | 97 | return path.removingLastComponent() 98 | } 99 | ) 100 | 101 | for proposedDirectory in proposedDirectories { 102 | for fileName in defaultConfigurationFileNames { 103 | let potentialConfigurationFile = proposedDirectory.appending(subpath: fileName) 104 | if potentialConfigurationFile.isAccessible() { 105 | return potentialConfigurationFile 106 | } 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | /// Safe way to check if the file is accessible from within the current process sandbox. 113 | private func isAccessible() -> Bool { 114 | let result = string.withCString { pointer in 115 | access(pointer, R_OK) 116 | } 117 | 118 | return result == 0 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RunScriptPlugin 2 | 3 | SwiftPackage Plugin for executing arbitrary ShellScript. 4 | 5 | ## Feature 6 | - BuildToolPlugin: RunScriptPlugin 7 | - CommandPlugin: RunScriptCommandPlugin 8 | - XcodeBuildToolPlugin: RunScriptPlugin 9 | - XcodeCommandPlugin: RunScriptCommandPlugin 10 | 11 | ## Usage 12 | Place the file named `runscript.yml`(or `.runscript.yml`) in the root of the project. 13 | Write the shell script you want to run in this file. 14 | 15 | The format is as follows. 16 | 17 | ```yaml 18 | prebuild: # prebuild Command 19 | - name: "Hello" 20 | script: "echo Hello" # Write scripts directly 21 | - name: "Show current path" 22 | script: "pwd" 23 | - name: "Write file" 24 | script: "echo Hello >> test.txt" 25 | 26 | - name: "SwiftLint" 27 | launchPath: "/bin/bash" # bash, zsh, etc. can be specified 28 | script: "swiftlint lint --fix" 29 | 30 | - name: "SwiftLint" # Another way to write ↑↑ 31 | launchPath: "/usr/local/bin/swiftlint" 32 | arguments: 33 | - "lint" 34 | - "--fix" 35 | 36 | - name: "Update schema" 37 | file: "update_schema.sh" # Execute .sh file 38 | 39 | build: # build Command 40 | - name: "Hello" 41 | script: "echo Hello" 42 | - name: "Make Swift Code" 43 | script: | 44 | echo "public enum Hello { case a,b,c,d }" > $RUN_SCRIPT_PLUGIN_WORK_DIR/tmp.swift" 45 | 46 | command: # Command Plugin 47 | - name: "Hello from Command" 48 | script: "echo 'Hello from Command'" 49 | 50 | all: # run in `prebuild`, `build`... 51 | - name: "Hello(all)" 52 | script: "echo Hello(all)" 53 | ``` 54 | 55 | > [!NOTE] 56 | > Due to a limitation of Xcode, you may get a permission error when trying to write a file. 57 | > 58 | > If CommandPlugin is run from the shell, it is possible to work around this by disabling the sandbox using the `--disable-sandbox` option. 59 | > ```sh 60 | > swift package plugin --allow-writing-to-package-directory --disable-sandbox run-script 61 | > ``` 62 | > 63 | > If you want to avoid the use of the BuildToolPlugin/CommandPlugin via Xcode, you can disable the use of the sandbox by configuring the following settings. 64 | > ```sh 65 | > defaults write com.apple.dt.Xcode IDEPackageSupportDisablePluginExecutionSandbox -bool YES 66 | > ``` 67 | 68 | ### Environment Valiables 69 | 70 | The following environment variables are available in the script to refer to the Plugin context. 71 | 72 | - RUN_SCRIPT_TARGET_PACKAGE_DIR 73 | Path of the target package on which the plugin runs. 74 | The path obtained by `PackagePlugin.PluginContext.package.directory` 75 | - RUN_SCRIPT_PLUGIN_WORK_DIR 76 | The path of a writable directory into which the plugin or the build commands it constructs can write anything it wants. 77 | The path obtained by `PackagePlugin.PluginContext.pluginWorkDirectory` 78 | 79 | ## Example 80 | - SwiftLint 81 | You can run Lint in SPM without using the SwiftLint plugin. 82 | ```yaml 83 | - name: "SwiftLint" 84 | script: "swiftlint lint" 85 | ``` 86 | 87 | - Build Log 88 | ```yaml 89 | - name: "Build Log" 90 | script: "echo \"[$(date)] Build >> build.log\"" 91 | ``` 92 | 93 | - Theos(Orion) install 94 | You can install the Tweak from the Build button in SPM. 95 | ```yaml 96 | - name: "Theos make package and install" 97 | script: "make do" 98 | ``` 99 | - SwiftFormat 100 | ```yaml 101 | - name: "SwiftFormat" 102 | script: "swiftformat ." 103 | ``` 104 | 105 | - SwiftGen 106 | ```yaml 107 | - name: "SwiftGen" 108 | script: "swiftgen config run" 109 | ``` 110 | -------------------------------------------------------------------------------- /Sources/run-script/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // 4 | // 5 | // Created by p-x9 on 2023/02/04. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(macOS) 12 | struct Config: Codable { 13 | let prebuild: [Script]? 14 | let build: [Script]? 15 | let command: [Script]? 16 | let all: [Script]? 17 | } 18 | 19 | struct Script: Codable { 20 | let name: String? 21 | 22 | let launchPath: String? 23 | 24 | let path: String? 25 | let script: String? 26 | 27 | let arguments: [String]? 28 | } 29 | 30 | extension Config { 31 | func scripts(for timing: Timing) -> [Script] { 32 | let all = all ?? [] 33 | switch timing { 34 | case .prebuild: 35 | return (prebuild ?? []) + all 36 | case .build: 37 | return (build ?? []) + all 38 | case .command: 39 | return (command ?? []) + all 40 | } 41 | } 42 | } 43 | #endif 44 | -------------------------------------------------------------------------------- /Sources/run-script/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Created by p-x9 on 2023/02/04. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Yams 11 | import ArgumentParser 12 | 13 | #if os(macOS) 14 | enum CommandError: LocalizedError { 15 | case configFileNotExisted 16 | } 17 | 18 | enum Timing: String, EnumerableFlag, ExpressibleByArgument { 19 | case prebuild 20 | case build 21 | case command 22 | 23 | static func name(for value: Self) -> NameSpecification { 24 | .long 25 | } 26 | 27 | static func help(for value: Self) -> ArgumentHelp? { 28 | "run scripts defined with '\(value.rawValue)' in configuration file " 29 | } 30 | } 31 | 32 | struct RunScript: ParsableCommand { 33 | static let configuration: CommandConfiguration = .init( 34 | commandName: "RunScript", 35 | abstract: "Run shell scripts configured in yaml files", 36 | version: "0.4.0", 37 | shouldDisplay: true, 38 | helpNames: [.long, .short] 39 | ) 40 | 41 | @Option(help: "configuration file path (YAML)") 42 | var config: String 43 | 44 | @Option(help: "Which scripts to run in the configuration file") 45 | var timing: Timing 46 | 47 | @Flag(name: .customLong("silence"), help: "Do not output logs") 48 | var silence: Bool = false 49 | 50 | func run() throws { 51 | let configFileURL = URL(fileURLWithPath: config) 52 | 53 | let fileManager = FileManager.default 54 | let decoder = YAMLDecoder() 55 | 56 | guard fileManager.fileExists(atPath: config) else { 57 | throw CommandError.configFileNotExisted 58 | } 59 | 60 | let data = try Data(contentsOf: configFileURL) 61 | let config = try decoder.decode(Config.self, from: data) 62 | 63 | let packageDirectory = ProcessInfo.processInfo.environment["RUN_SCRIPT_TARGET_PACKAGE_DIR"] 64 | let configDirectory = configFileURL.deletingLastPathComponent().path 65 | FileManager.default.changeCurrentDirectoryPath(packageDirectory ?? configDirectory) 66 | 67 | let scripts = config.scripts(for: timing) 68 | 69 | log("🏃[Start] RunScriptPlugin(\(timing.rawValue))") 70 | try scripts.enumerated().forEach { index, script in 71 | log("🏃[script] \(script.name ?? String(index))...") 72 | try run(script) 73 | } 74 | log("🏃[End] RunScriptPlugin(\(timing.rawValue))") 75 | } 76 | } 77 | 78 | extension RunScript { 79 | @inline(never) 80 | func log(_ items: Any..., separator: String = " ", terminator: String = "\n") { 81 | if silence { return } 82 | print(items, separator: separator, terminator: terminator, flush: true) 83 | } 84 | } 85 | 86 | extension RunScript { 87 | func run(_ script: Script) throws { 88 | let process = Process() 89 | let errorPipe = Pipe() 90 | 91 | process.launchPath = script.launchPath ?? "/bin/sh" 92 | process.standardError = errorPipe 93 | 94 | if let path = script.path { 95 | process.arguments = [path] 96 | } else if let arguments = script.arguments, !arguments.isEmpty { 97 | process.arguments = arguments 98 | } else if let script = script.script { 99 | process.arguments = ["-c", script] 100 | } 101 | 102 | do { 103 | try process.run() 104 | process.waitUntilExit() 105 | } catch { 106 | let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile() 107 | if let error = String(data: errorData, encoding: .utf8), 108 | !error.isEmpty { 109 | log("warning: [RunScriptPlugin] " + error) 110 | } 111 | } 112 | } 113 | } 114 | 115 | RunScript.main() 116 | #endif 117 | -------------------------------------------------------------------------------- /Sources/run-script/print.swift: -------------------------------------------------------------------------------- 1 | // 2 | // print.swift 3 | // 4 | // 5 | // Created by p-x9 on 2023/03/20. 6 | // 7 | 8 | import Foundation 9 | 10 | @inline(never) 11 | func print(_ items: Any..., separator: String = " ", terminator: String = "\n", flush: Bool) { 12 | Swift.print(items, separator: separator, terminator: terminator) 13 | if flush { 14 | fflush(stdout) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/create_artifactbundle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # example 4 | # 5 | # - version 6 | # scripts/create_artifactbundle.sh 1.0.0 7 | # 8 | # - version with artifactbundle name 9 | # scripts/create_artifactbundle.sh 1.0.0 run-script-bin 10 | 11 | set -Ceu 12 | 13 | VERSION_STRING=$1 14 | 15 | PRODUCT_NAME="run-script" 16 | NAME=$PRODUCT_NAME 17 | 18 | if (( $# > 1 )); then 19 | NAME=$2 20 | fi 21 | 22 | ARTIFACT_BUNDLE_NAME="${NAME}.artifactbundle" 23 | ARTIFACT_BUNDLE_BIN_PATH="${ARTIFACT_BUNDLE_NAME}/${NAME}/bin" 24 | 25 | INFO_TEMPLATE="./scripts/info.json.template" 26 | 27 | show_checksum() { 28 | CHECKSUM=$(swift package compute-checksum ./${ARTIFACT_BUNDLE_NAME}.zip) 29 | echo "checksum:" 30 | echo ${CHECKSUM} 31 | } 32 | 33 | create_artifact_bundle() { 34 | mkdir -p "${ARTIFACT_BUNDLE_BIN_PATH}" 35 | 36 | sed "s/__VERSION__/${VERSION_STRING}/g; s/__NAME__/${NAME}/g" ${INFO_TEMPLATE} > "${ARTIFACT_BUNDLE_NAME}/info.json" 37 | 38 | cp -f ".build/apple/Products/Release/${PRODUCT_NAME}" "${ARTIFACT_BUNDLE_BIN_PATH}" 39 | cp -f LICENSE "${ARTIFACT_BUNDLE_NAME}" 40 | 41 | mv "${ARTIFACT_BUNDLE_BIN_PATH}/${PRODUCT_NAME}" "${ARTIFACT_BUNDLE_BIN_PATH}/${NAME}" 42 | } 43 | 44 | zip_artifact_bundle() { 45 | zip -yr - "${ARTIFACT_BUNDLE_NAME}" > "./${ARTIFACT_BUNDLE_NAME}.zip" 46 | } 47 | 48 | build_target() { 49 | swift build -c release --product $PRODUCT_NAME --arch arm64 --arch x86_64 50 | } 51 | 52 | clear_outputs() { 53 | rm -rf $ARTIFACT_BUNDLE_NAME 54 | rm -f "${ARTIFACT_BUNDLE_NAME}.zip" 55 | } 56 | 57 | 58 | clear_outputs 59 | 60 | if [ "$1" = "--clear" ]; then 61 | exit 62 | fi 63 | 64 | build_target 65 | create_artifact_bundle 66 | zip_artifact_bundle 67 | 68 | show_checksum 69 | 70 | -------------------------------------------------------------------------------- /scripts/info.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "artifacts": { 4 | "__NAME__": { 5 | "version": "__VERSION__", 6 | "type": "executable", 7 | "variants": [ 8 | { 9 | "path": "__NAME__/bin/__NAME__", 10 | "supportedTriples": [ 11 | "x86_64-apple-macosx", 12 | "arm64-apple-macosx" 13 | ] 14 | } 15 | ] 16 | } 17 | } 18 | } 19 | --------------------------------------------------------------------------------