Files
SideStore/SideStoreApp/Package.swift
Joseph Mattello c4c2d17ffc snapshot
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-04-02 02:28:12 -04:00

732 lines
23 KiB
Swift

// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import Foundation
import PackageDescription
// Process enviroment variables.
typealias EnviromentBool = (var: String, default: Bool)
// Possible keys for `env` and their default value
let USE_CARGO = envBool(("USE_CARGO", false))
let USE_LOCAL_MINIMUXER = envBool(("USE_LOCAL_MINIMUXER", true))
let USE_LOCAL_EM_PROXY = envBool(("USE_LOCAL_EM_PROXY", true))
let USE_CXX_INTEROP = envBool(("USE_CXX_INTEROP", false))
let USE_CXX_MODULES = envBool(("USE_CXX_MODULES", false))
let INHIBIT_UPSTREAM_WARNINGS = envBool(("INHIBIT_UPSTREAM_WARNINGS", true))
let STATIC_LIBRARY = envBool(("STATIC_LIBRARY", false))
let unsafe_flags: [String] = INHIBIT_UPSTREAM_WARNINGS ? ["-w"] : [String]()
let unsafe_flags_cxx: [String] = INHIBIT_UPSTREAM_WARNINGS ? ["-w", "-Wno-module-import-in-extern-c"] : ["-Wno-module-import-in-extern-c"]
enum Consts {
static let minimuxer_checksum: String = "57b5126605ad127f209a57a2f8e6fe7230ce9acabc499f2b7510983ddec954bb"
static let em_proxy_checksum: String = "79f90075b8ff2f47540a5bccf5fb7740905cda63463f833e2505256237df3c1b"
}
extension Package.Dependency {
/// The combination of all the dependencies for the Package.
enum SideStore {
static let dependencies: [Package.Dependency] =
Packages_SideStoreTeam +
Packages_3rdParty +
Plugins_BuildTools +
Plugins_CICD
}
/// Side Store Team Packages
static let Packages_SideStoreTeam: [Package.Dependency] = [
.github("SideStore/AltSign", from: "1.0.3"),
.package(name: "iMobileDevice.swift", path: "../../iMobileDevice.swift"),
// .github("SideStore/iMobileDevice.swift", from: "1.0.5"),
.github("SideStore/SideKit", from: "0.1.0"),
/// @JoeMatt updated fork for Riley's `Roxas`
.github("JoeMatt/Roxas", from: "1.2.2"),
]
#if HAVE_MACOS_DAEMON
static let Packages_macOS_Daemon: [Package.Dependency] = [
.github("sindresorhus/LaunchAtLogin", from: "5.0.0")
]
#else
static let Packages_macOS_Daemon: [Package.Dependency] = []
#endif
/// 3rd party Packages
static let Packages_3rdParty: [Package.Dependency] = [
// Logging
.github("apple/swift-log", from: "1.5.2"),
// XCode console output
.github("ShivaHuang/swift-log-SwiftyBeaver", from: "0.1.0"),
.github("johnxnguyen/Down", branch: "master"),
.github("kean/Nuke", from: "7.0.0"),
.github("kishikawakatsumi/KeychainAccess", from: "4.2.0"),
.github("microsoft/appcenter-sdk-apple", from: "4.2.0"),
.github("SwiftPackageIndex/SemanticVersion", from: "0.3.5")
] + Packages_macOS_Daemon
static let Plugins_BuildTools: [Package.Dependency] = [
// Plugins
/// IntentBuilder for spm support of intents and Logger injection
.github("JoeMatt/SwiftPMPlugins", from: "1.0.0"),
/// Generate swift files with git head info
.github("elegantchaos/Versionator", from: "1.0.3").disable,
/// plists from .json, including Info.plist
.github("elegantchaos/InfomaticPlugin", branch: "main").disable
]-?
static let Plugins_CICD: [Package.Dependency] = [
/// Swiftlint
.github("lukepistrol/SwiftLintPlugin", from: "0.2.2").disable,
/// git secrets from env (for adding sensative api keys via CI/CD,
/// ```sh
/// swift package plugin --allow-writing-to-package-directory secret-keys generate
/// ```
/// or using Mint
/// ```sh
/// mint run secret-keys generate
/// ```
.github("simorgh3196/swift-secret-keys", from: "0.0.1").disable,
/// #__ Swift docc generator __
/// `swift package generate-documentation`
/// ## Inline creation
/// ```sh
/// swift package \
/// --allow-writing-to-directory ./docs \
/// generate-documentation \
/// --target MyFramework \
/// --output-path ./docs
/// ```
/// ## Preview:
/// `swift package --disable-sandbox preview-documentation --target MyFramework`
/// [Hosting](https://apple.github.io/swift-docc-plugin/documentation/swiftdoccplugin/)
.github("apple/swift-docc-plugin", from: "1.1.0").disable,
]-?
#if USE_RESULT_BUILDER
// TODO: Make a DSL using a @resultBuilder
@DependencyBuilder
static var Additional_Plugins: [Package.Dependency] {
// Secrets manager using `.env`
github("vdka/SecretsManager.git", from: "1.0.0")
// Generate `PackageBuild` struct with build time info about repo
github("DimaRU/PackageBuildInfo", branch: "master")
/// Plugin for simply updating your Package.swift file consistently and understandably.
github("mackoj/PackageGeneratorPlugin", from: "0.3.0")
/// Plugin for quickly updating your Schemes files
github("mackoj/SchemeGeneratorPlugin", from: "0.5.5")
github("pointfreeco/swift-composable-architecture", from: "0.45.0")
}
#else
static let Additional_Plugins: [Package.Dependency] = [
// Secrets manager using `.env`
github("vdka/SecretsManager.git", from: "1.0.0").disable,
// Generate `PackageBuild` struct with build time info about repo
github("DimaRU/PackageBuildInfo", branch: "master").disable,
/// Plugin for simply updating your `Package.swift` file consistently and understandably.
github("mackoj/PackageGeneratorPlugin", from: "0.3.0").disable,
/// Plugin for quickly updating your `.xcscheme` files
github("mackoj/SchemeGeneratorPlugin", from: "0.5.5").disable,
github("pointfreeco/swift-composable-architecture", from: "0.45.0").disable
].removeNils()
#endif
}
let InfomaticPlugin : Target.PluginUsage = .plugin(name: "InfomaticPlugin", package: "InfomaticPlugin")
let PackageBuildInfoPlugin : Target.PluginUsage = .plugin(name: "PackageBuildInfoPlugin",package: "PackageBuildInfo")
let SafeURLPlugin : Target.PluginUsage = .plugin(name: "SafeURLPlugin", package: "SafeURLPlugin")
let SecretsManagerPlugin : Target.PluginUsage = .plugin(name: "SecretsManagerPlugin", package: "SecretsManager")
let VersionatorPlugin : Target.PluginUsage = .plugin(name: "VersionatorPlugin", package: "Versionator")
let IntentBuilderPlugin : Target.PluginUsage = .plugin(name: "IntentBuilderPlugin", package: "SwiftPMPlugins")
let LoggerPlugin : Target.PluginUsage = .plugin(name: "LoggerPlugin", package: "SwiftPMPlugins")
let commonPlugins: [Target.PluginUsage] = [LoggerPlugin]
// MARK: - 3rd Party Dependencies
let AppCenterAnalytics: Target.Dependency = .product(name: "AppCenterAnalytics", package: "appcenter-sdk-apple")
let AppCenterCrashes : Target.Dependency = .product(name: "AppCenterCrashes", package: "appcenter-sdk-apple")
let CoreCrypto : Target.Dependency = .product(name: "CoreCrypto", package: "AltSign")
let CCoreCrypto : Target.Dependency = .product(name: "CCoreCrypto", package: "AltSign")
let libimobiledevice: Target.Dependency = .product(name: "libimobiledevice", package: "iMobileDevice.swift")
let iMobileDevice : Target.Dependency = .product(name: "iMobileDevice", package: "iMobileDevice.swift")
let Roxas : Target.Dependency = .product(name: "Roxas", package: "Roxas")
let RoxasUI : Target.Dependency = .product(name: "RoxasUI", package: "Roxas")
let Logging : Target.Dependency = .product(name: "Logging", package: "swift-log")
let LoggingSwiftyBeaver : Target.Dependency = .product(name: "LoggingSwiftyBeaver", package: "swift-log-SwiftyBeaver")
// MARK: - Linking
let frameworksCommon: [LinkerSetting] = [ "Avfoundation",
"CoreData",
"Combine",
"Network",
"UniformTypeIdentifiers" ].map{LinkerSetting.linkedFramework($0)}
let frameworksIOS: [LinkerSetting] = [
"ARKit",
"AudioToolbox",
"AuthenticationServices",
"CoreHaptics",
"Intents",
"IntentsUI",
"MessageUI",
"MobileCoreServices",
"QuickLook",
"SafariServices",
"UserNotifications",
"WidgetKit"
]
.map{LinkerSetting.linkedFramework($0,
.when(platforms: [.iOS, .macCatalyst]))}
// MARK: -- PACKAGE --
let package = Package(
name: "SideStore",
defaultLocalization: "en",
platforms: [
.iOS(.v14),
.tvOS(.v14),
.macCatalyst(.v14),
.macOS(.v12),
.driverKit("99")
],
products: Product.products,
dependencies: Package.Dependency.SideStore.dependencies,
targets: Target.SideStore.targets,
swiftLanguageVersions: [.v5],
cLanguageStandard: .gnu11,
cxxLanguageStandard: .gnucxx14
)
// ----------------
// MARK: - Products
// ----------------
extension Product {
static let products: [Product] = [
// Modules
SideStoreUIKit.0,
SideStoreUIKit.static,
SideStoreUIKit.dynamic,
SideStoreCore.0,
SideStoreCore.static,
SideStoreCore.dynamic,
SideWidgetKit.0,
SideWidgetKit.static,
SideWidgetKit.dynamic,
// `.app` Executables
SideStore_app,
] + cliProducts
// CLI Executables
#if HAVE_MACOS_DAEMON
static let cliProducts: [Product] = [
SideDaemon
]
#else
static let cliProducts: [Product] = []
#endif
// SideStoreUIKit
static let SideStoreUIKit = librarySet("SideStoreUIKit")
// SideStoreCore
static let SideStoreCore = librarySet("SideStoreCore")
// SideWidgetKit
static let SideWidgetKit = librarySet("SideWidgetKit")
// SideStore Executable
static let SideStore_app: Product = .executable( name: "SideStore", targets: ["SideStore"])
#if HAVE_MACOS_DAEMON
// SideDaemon Executable
static let SideDaemon : Product = .executable(name: "SideDaemon", targets: ["SideDaemon"])
#endif
#if USE_CARGO_BUILD_PLUGIN
// Cargo Plugin (WIP)
static let CargoPlugin: Product = .plugin(name: "CargoPlugin", targets: ["CargoPlugin"])
#endif
}
// ----------------
// MARK: - Targets
// ----------------
extension Target {
enum SideStore { //: Encodable, CaseIterable {
static let linkerSettings: [LinkerSetting] = frameworksCommon + frameworksIOS + [
.linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst, .tvOS])),
.linkedLibrary("AppleArchive")
]
}
}
typealias TargetPair = (target: PackageDescription.Target, testTarget: PackageDescription.Target?)
// MARK: - SideStoreUIKit
extension Target.SideStore {
/// All the targets fo be added to `Package(targets:)`
static let targets: [Target] = publicTargets + Internal.targets + pluginTargets
/// __Public Targets__
static let publicTargets: [Target] = [
// SideStoreUIKit
SideStoreUIKit,
// SideUIShared
SideUIShared,
// iOS Widget
SideWidgetKit,
// SideStoreCore
SideStoreCore.target,
SideStoreCore.testTarget,
// SideBackup Executable
SideBackup,
// SidePatcher
SidePatcher.target,
SidePatcher.testTarget,
// App Bundle
Apps.SideStore_app,
].compactMap{$0}
#if HAVE_MACOS_DAEMON
static let cliTargets: [Target] = [
// SideDaemon
SideDaemon.target,
SideDaemon.testTarget,
]
#else
static let cliTargets: [Target] = []
#endif
/// __PluginTargets__
static let pluginTargets: [Target] = {
Cargo.Plugins
}()
// MARK: - SideStoreUIKit
static let SideStoreUIKit: Target =
.target(
name: "SideStoreUIKit",
dependencies: [
AppCenterAnalytics,
AppCenterCrashes,
"AltSign",
"Down",
"EmotionalDamage",
"KeychainAccess",
libimobiledevice,
"MiniMuxer",
"Nuke",
Roxas,
RoxasUI,
"SideKit",
"SidePatcher",
"SideStoreCore",
],
resources: [],
linkerSettings: linkerSettings,
plugins: [LoggerPlugin, IntentBuilderPlugin]
)
// MARK: - SideUIShared
static let SideUIShared: Target =
.target(
name: "SideUIShared",
dependencies: [
AppCenterAnalytics,
AppCenterCrashes,
"AltSign",
"Down",
"EmotionalDamage",
"KeychainAccess",
libimobiledevice,
iMobileDevice,
"MiniMuxer",
Roxas,
RoxasUI,
"SideKit",
"SidePatcher",
"SideStoreCore",
],
resources: [],
linkerSettings: linkerSettings,
plugins: [LoggerPlugin, IntentBuilderPlugin]
)
// MARK: - SideWidgetKit
static let SideWidgetKit: Target =
.target(
name: "SideWidgetKit",
dependencies: [
Roxas,
RoxasUI,
"SideKit",
"SideUIShared",
"SideStoreCore"
],
plugins: commonPlugins
)
// MARK: - SideStoreCore
static let SideStoreCore: TargetPair = (
.target(
name: "SideStoreCore",
dependencies: [
"AltSign",
"KeychainAccess",
Roxas,
"SemanticVersion",
Logging,
LoggingSwiftyBeaver,
"SideKit"
],
plugins: commonPlugins),
.testTarget(
name: "SideStoreCoreTests",
dependencies: ["SideStoreCore", "KeychainAccess", "AltSign", "SemanticVersion", "SideKit"]))
#if HAVE_MACOS_DAEMON
// MARK: - SideDaemon
/// This is mostly leftover from `AltDaemon`
/// We don't need or use it, but it felt bad to just delete it at the moment.
static let SideDaemon: TargetPair = (
.executableTarget(
name: "SideDaemon",
dependencies: [
"AltSign",
"SideKit",
CoreCrypto,
CCoreCrypto,
.product(name: "LaunchAtLogin", package: "LaunchAtLogin", condition: .when(platforms: [.macOS])),
],
plugins: commonPlugins
),
.testTarget(name: "SideDaemonTests", dependencies: ["SideDaemon"]))
#endif
// MARK: - SideBackup
static let SideBackup: Target = .executableTarget(
name: "SideBackup",
dependencies: [
"AltSign",
"Roxas",
"SideStoreCore",
"SideKit"
],
exclude: [
"Info.plist",
"AltBackup.entitlements"]
.map{ "Resources/\($0)" },
resources: [
.process("Resources/")
],
plugins: commonPlugins)
// MARK: - SidePatcher
/// Note: This is Objective-C so Swift generator's will fail if you try to apply them here - @JoeMatt
static let SidePatcher: TargetPair = (
.target(name: "SidePatcher", dependencies: [ RoxasUI ], plugins: []),
.testTarget(name: "SidePatcherTests", dependencies: ["SidePatcher"]))
}
// MARK: -- .app Targets
/// This seems to be a new feature for XCode 14.3 beta.
/// `PackageDescription.ProductSetting` has no developer documentation
/// though there's probably info on swift-lang.org
extension Target.SideStore {
// MARK: - SideStore.app
enum Apps: Encodable, CaseIterable {
case _apps(Target)
static var allCases: [Apps] = [_apps(SideStore_app)]
static let SideStore_app: Target = .executableTarget(
name: "SideStore",
dependencies: [ "SideStoreUIKit" ],
exclude: [
"Resources/Info.info",
"Resources/Info.plist",
"Resources/SideStore.entitlements",
"Resources/tempEnt.plist",
],
resources: [
.copy("Resources/JSON/apps-alpha.json"),
.copy("Resources/JSON/apps.json"),
.process("Resources/Assets"),
.process("Resources/Base.lproj"),
.process("Resources/Settings.bundle"),
.process("Resources/Sounds"),
.process("Resources/Storyboards"),
.process("Resources/XIB")
],
linkerSettings: linkerSettings,
plugins: commonPlugins
)
#if false
static let productSettings: [PackageDescription.ProductSetting] = [
.bundleIdentifier(""),
.bundleVersion(""),
.displayVersion(""),
.teamIdentifier(""),
.iOSAppInfo(
.init(
appIcon: .asset("AppIcon"),
accentColor: .asset("SettingsHighlighted"),
supportedDeviceFamilies: [.phone, .mac],
supportedInterfaceOrientations: [
.portrait,
.landscapeLeft(.when(deviceFamilies: [.mac, .pad])),
.landscapeRight(.when(deviceFamilies: [.mac, .pad]))
],
capabilities: [
.appTransportSecurity(configuration:
.init(
)),
.fileAccess(.userSelectedFiles, mode: .readOnly),
.fileAccess(.downloadsFolder, mode: .readOnly),
.incomingNetworkConnections(.when(deviceFamilies: [.mac, .pad, .phone])),
.localNetwork(purposeString: "", bonjourServiceTypes: ["_altserver._tcp"]),
.outgoingNetworkConnections(),
//.bluetoothAlways(purposeString: ""),
//.calendars(purposeString: ""),
//.camera(purposeString: ""),
//.contacts(purposeString: ""),
//.faceID(purposeString: ""),
//.locationAlwaysAndWhenInUse(purposeString: ""),
],
appCategory: .developerTools,
additionalInfoPlistContentFilePath: "Resources/Info.info")
)
]
#endif
}
}
// MARK: -- Internal Targets
extension Target.SideStore {
enum Internal: Encodable, CaseIterable {
static var allCases: [PackageDescription.Target.SideStore.Internal] = targets.map{._internal($0)}
case _internal(Target)
// MARK: Internal Targets
static let targets: [Target] = [
em_proxy_target,
emotionalDamageTarget.target,
emotionalDamageTarget.testTarget,
minimuxer_target,
miniMuxerSwiftTarget.target,
miniMuxerSwiftTarget.testTarget
].compactMap{$0}
// MARK: em_proxy
private static let em_proxy_target: Target = USE_LOCAL_EM_PROXY ?
.binaryTarget(name: "em_proxy",
path: "Dependencies/em_proxy/em_proxy.xcframework") :
.binaryTarget(
name: "em_proxy",
url: "https://github.com/SideStore/em_proxy/releases/download/build/em_proxy.xcframework.zip",
checksum: Consts.em_proxy_checksum)
// MARK: - EmotionalDamage (Swift)
private static let emotionalDamageTarget: TargetPair = (
.target(name: "EmotionalDamage", dependencies: ["em_proxy"]),
.testTarget(name: "EmotionalDamageTests", dependencies: ["EmotionalDamage"]))
// MARK: minimuxer
private static let minimuxer_target: Target = USE_LOCAL_MINIMUXER ?
.binaryTarget(name: "minimuxer",
path: "Dependencies/minimuxer/minimuxer.xcframework") :
.binaryTarget(name: "minimuxer",
url: "https://github.com/SideStore/minimuxer/releases/download/build/minimuxer.xcframework.zip",
checksum: Consts.minimuxer_checksum)
// MARK: MiniMuxer.Swift
private static let miniMuxerSwiftTarget: TargetPair = (
.target(name: "MiniMuxer", dependencies: ["minimuxer", libimobiledevice]),
.testTarget(name: "MiniMuxerTests",
dependencies: ["MiniMuxer", libimobiledevice]))
}
}
// MARK: - Helpers
/// Utility function to test if a value of enviroment variable is a representation of `true`, as case-insensative word, letter or digit
/// - Parameter key: A key value from an enviroment variable
/// - Returns: `true` if `1`, "on", "true", or "yes" when `.lowercased()`, or `false` if "off", "false", 0, "no". `.lowercased()`
func envBool(_ key: EnviromentBool) -> Bool {
guard let value = ProcessInfo.processInfo.environment[key.`var`]?.lowercased() else { return key.default }
// We check for the opposite style string to be true
// or return the default. That way a junk string will
// always return the default.
if key.default == true {
// Check if a known 'false' type or return 'true'
let falseValues = ["0", "off", "f", "false", "n", "no"]
return !falseValues.contains(value)
} else {
let trueValues = ["1", "on", "t", "true", "y", "yes"]
return trueValues.contains(value)
}
}
/// Utility function to generate 3 of the same target with different types` {_, .static, .dynamic}`
/// - Parameter name: Name of your exported library. Must have target of same name.
/// - Returns: `[Product.Library, Product.Library.Static, Product.Library.Dynamic]`
func librarySet(_ name: String) -> (Product, `static`: Product, `dynamic`: Product) {(
.library(name: name, targets: [name]),
.library(name: "\(name)-Static", type: .static, targets: [name]),
.library(name: "\(name)-Dynamic", type: .dynamic, targets: [name]))}
#if USE_CARGO_BUILD_PLUGIN
enum Cargo: Encodable {
enum Dependencies: Encodable {case _dependencies([Package.Dependency])}
enum Target: Encodable {case _target(Target, test: Target?)}
enum Plugin: Encodable {case _plugin(Target)}
static let Dependencies: Cargo.Dependencies = [
.github("apple/swift-argument-parser.git", from: "1.0.3"),
.github("apple/swift-package-manager.git", branch: "release/5.7"),
.github("apple/swift-tools-support-core.git", branch: "release/5.7"),
]
static let Executable: (Cargo.Target, test: Cargo.Target?) = ._target(
.executableTarget(
name: "Cargo",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "SwiftPM-auto", package: "swift-package-manager"),
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core")
]),
.testTarget(
name: "CargoTests",
dependencies: ["Cargo"],
exclude: [
"swiftlint",
"xcframework"
]
))
static let Plugins: [Cargo.Plugin] = [
.plugin(
name: "CargoPlugin",
capability: .buildTool(),
dependencies: [
"Cargo"
]
),
.plugin(
name: "CargoPlugin-Generate",
capability: .command(
intent: .custom(
verb: "generate-code-from-rust",
description: "Creates .c code from your `rust` code"
),
permissions: [
.writeToPackageDirectory(reason: "This command generates source code")
]
),
dependencies: ["Cargo"]
)].map{._plugin($0)}
}
#else
enum Cargo {
static let Plugins: [Target] = []
}
#endif
extension PackageDescription.Package.Dependency {
var disable: PackageDescription.Package.Dependency? { nil }
static func github(_ repo: String,
from: Version) -> PackageDescription.Package.Dependency {
.package(url: "https://github.com/\(repo).git", from: from)}
static func github(_ repo: String,
exact: Version) -> PackageDescription.Package.Dependency {
.package(url: "https://github.com/\(repo).git", exact: exact)}
static func github(_ repo: String,
branch: String) -> PackageDescription.Package.Dependency {
.package(url: "https://github.com/\(repo).git", branch: branch)}
static func github(_ repo: String,
revision: String) -> PackageDescription.Package.Dependency {
.package(url: "https://github.com/\(repo).git", revision: revision)}
}
/// `-?` Operator added as a quick way to `.compactMap{$0}` an
/// array that has optionals. In combination with adding `.disable` which returns `Self?`
/// as an easy way to disable packages from `Package.swift` since there are some limitions
/// 1. #if's can't be used in static array initiliziers, unless using ugly inline functions, and that kills
/// the Swift processor a lot.
/// The other option was to use a `@resultBuilder`, which I started below, to add some
/// synataic suger but probablly overkill honeslty.
/// - Author: @JoeMatt
postfix operator -?
extension Array where Element == Package.Dependency? {
func removeNils() -> [Package.Dependency] { self.compactMap{$0} }
static postfix func -? (array: Self) -> [Package.Dependency] { array.removeNils() }
}
extension Array where Element == Target? {
func removeNils() -> [Target] { self.compactMap{$0} }
static postfix func -? (array: Self) -> [Target] { array.removeNils() }
}
/// I'm a WIP as a less verbose way of merging array's of various`Target`s and `Dependency` lists
@resultBuilder
struct DependencyBuilder {
typealias Component = [Package.Dependency]
typealias Expression = Package.Dependency
static func buildExpression(_ element: Expression) -> Component {
return [element]
}
static func buildOptional(_ component: Component?) -> Component {
guard let component = component else { return [] }
return component
}
static func buildEither(first component: Component) -> Component {
return component
}
static func buildEither(second component: Component) -> Component {
return component
}
static func buildArray(_ components: [Component]) -> Component {
return Array(components.joined())
}
static func buildBlock(_ components: Component...) -> Component {
return Array(components.joined())
}
}