Skip to content

Commit

Permalink
Add ubuntu &/ windows test workflow (#76)
Browse files Browse the repository at this point in the history
* Add ubuntu test workflow

* Fix spacing

* Fix spacing

* Update ubuntu.yml

* Create windows.yml

* Update Application.swift to run on other platforms

* Improve multiplatform

* Improve multiplatform

* Improve logging multiplatform support

* Prefer limitting watchOS only

* Improve multiplatform

* Improve multiplatform support

* Improve multiplatform support

* Improve multiplatform support

* Fix ApplicationLogger

* Remove KeychainTests for Linux and Windows

* Change CGColor to String hex for tests

* Improve logging emoji for non-Apple platforms
  • Loading branch information
0xLeif authored Jan 23, 2024
1 parent 59d855a commit a203263
Show file tree
Hide file tree
Showing 25 changed files with 284 additions and 49 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This workflow will build a Swift project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift

name: Ubuntu

on:
push:
branches: ["**"]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: swift-actions/setup-swift@v1
- uses: actions/checkout@v3
- name: Build for release
run: swift build -v -c release
- name: Test
run: swift test -v
20 changes: 20 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Windows

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: windows-latest
steps:
- uses: compnerd/gha-setup-swift@main
with:
branch: swift-5.9.2-release
tag: 5.9.2-RELEASE

- uses: actions/checkout@v2
- run: swift build
- run: swift test
12 changes: 10 additions & 2 deletions Sources/AppState/Application/Application+public.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,15 @@ public extension Application {
_ column: Int = #column
) -> State<Value> {
let appState = shared.value(keyPath: keyPath)
#if !os(Linux) && !os(Windows)
let debugEmoji = "🔄"
#else
let debugEmoji = "📦"
#endif


log(
debug: "🔄 Getting State \(String(describing: keyPath)) -> \(appState.value)",
debug: "\(debugEmoji) Getting State \(String(describing: keyPath)) -> \(appState.value)",
fileID: fileID,
function: function,
line: line,
Expand Down Expand Up @@ -436,9 +442,10 @@ public extension Application {
}
}

#if !os(Linux) && !os(Windows)
// MARK: SyncState Functions

@available(iOS 15.0, watchOS 9.0, macOS 11.0, tvOS 15.0, visionOS 1.0, *)
@available(watchOS 9.0, *)
public extension Application {
/// Resets the value to the inital value. If the inital value was `nil`, then the value will be removed from `iClouds`
static func reset<Value>(
Expand Down Expand Up @@ -648,6 +655,7 @@ public extension Application {
)
}
}
#endif

// MARK: Slice Functions

Expand Down
46 changes: 42 additions & 4 deletions Sources/AppState/Application/Application.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Cache
#if !os(Linux) && !os(Windows)
import Combine
import OSLog
#else
import Foundation
#endif

/// `Application` is a class that can be observed for changes, keeping track of the states within the application.
open class Application: NSObject, ObservableObject {
open class Application: NSObject {
/// Singleton shared instance of `Application`
static var shared: Application = Application()

Expand Down Expand Up @@ -66,7 +70,12 @@ open class Application: NSObject, ObservableObject {
let debugMessage = message()
let codeID = codeID(fileID: fileID, function: function, line: line, column: column)


#if !os(Linux) && !os(Windows)
logger.debug("\(debugMessage) (\(codeID))")
#else
print("\(debugMessage) (\(codeID))")
#endif
}

/// Internal log function.
Expand All @@ -82,13 +91,23 @@ open class Application: NSObject, ObservableObject {

let codeID = codeID(fileID: fileID, function: function, line: line, column: column)

#if !os(Linux) && !os(Windows)
logger.error(
"""
\(message) Error: {
\(error)
} (\(codeID))
"""
)
#else
print(
"""
\(message) Error: {
\(error)
} (\(codeID))
"""
)
#endif
}

static var cacheDescription: String {
Expand All @@ -100,31 +119,43 @@ open class Application: NSObject, ObservableObject {
.joined(separator: "\n")
}

#if !os(Linux) && !os(Windows)
/// Logger specifically for AppState
public static let logger: Logger = Logger(subsystem: "AppState", category: "Application")
#else
/// Logger specifically for AppState
public static let logger: ApplicationLogger = ApplicationLogger()
#endif
static var isLoggingEnabled: Bool = false

private let lock: NSRecursiveLock
private var bag: Set<AnyCancellable>

#if !os(Linux) && !os(Windows)
private var bag: Set<AnyCancellable> = Set()
#endif

/// Cache to store values
let cache: Cache<String, Any>

#if !os(Linux) && !os(Windows)
deinit { bag.removeAll() }
#endif

/// Default init used as the default Application, but also any custom implementation of Application. You should never call this function, but instead should use `Application.promote(to: CustomApplication.self)`
public override required init() {
lock = NSRecursiveLock()
bag = Set()
cache = Cache()

super.init()

loadDefaultDependencies()

#if !os(Linux) && !os(Windows)
consume(object: cache)
#endif
}

#if !os(Linux) && !os(Windows)
/**
Called when the value of one or more keys in the local key-value store changed due to incoming data pushed from iCloud.

Expand All @@ -140,7 +171,7 @@ open class Application: NSObject, ObservableObject {

- Note: Calling `Application.dependency(\.icloudStore).synchronize()` does not force new keys and values to be written to iCloud. Rather, it lets iCloud know that new keys and values are available to be uploaded. Do not rely on your keys and values being available on other devices immediately. The system controls when those keys and values are uploaded. The frequency of upload requests for key-value storage is limited to several per minute.
*/
@objc @available(iOS 15.0, watchOS 9.0, macOS 11.0, tvOS 15.0, visionOS 1.0, *)
@objc @available(watchOS 9.0, *)
open func didChangeExternally(notification: Notification) {
Application.log(
debug: """
Expand All @@ -154,6 +185,7 @@ open class Application: NSObject, ObservableObject {
column: #column
)
}
#endif

/// Returns value for the provided keyPath. This method is thread safe
///
Expand All @@ -180,6 +212,7 @@ open class Application: NSObject, ObservableObject {
load(dependency: \.userDefaults)
}

#if !os(Linux) && !os(Windows)
/// Consumes changes in the provided ObservableObject and sends updates before the object will change.
///
/// - Parameter object: The ObservableObject to observe
Expand All @@ -195,4 +228,9 @@ open class Application: NSObject, ObservableObject {
)
)
}
#endif
}

#if !os(Linux) && !os(Windows)
extension Application: ObservableObject { }
#endif
53 changes: 53 additions & 0 deletions Sources/AppState/Application/Types/Helper/ApplicationLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#if os(Linux) || os(Windows)
open class ApplicationLogger {
open func debug(
_ message: String,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) {
debug(
{ message },
fileID,
function,
line,
column
)
}

open func debug(
_ message: () -> String,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) {
Application.log(
debug: message,
fileID: fileID,
function: function,
line: line,
column: column
)
}

open func error(
_ error: Error,
message: String,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) {
Application.log(
error: error,
message: message,
fileID: fileID,
function: function,
line: line,
column: column
)
}
}
#endif
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !os(Linux) && !os(Windows)
import Security
import Foundation

Expand Down Expand Up @@ -62,3 +63,4 @@ extension Application {
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#if !os(Linux) && !os(Windows)
import Foundation

@available(iOS 15.0, watchOS 9.0, macOS 11.0, tvOS 15.0, visionOS 1.0, *)
@available(watchOS 9.0, *)
extension Application {
/// The default `NSUbiquitousKeyValueStore` instance.
public var icloudStore: Dependency<NSUbiquitousKeyValueStore> {
Expand Down Expand Up @@ -124,3 +125,4 @@ extension Application {
}
}
}
#endif
2 changes: 2 additions & 0 deletions Sources/AppState/Dependencies/Keychain.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !os(Linux) && !os(Windows)
import Cache
import Foundation

Expand Down Expand Up @@ -189,3 +190,4 @@ public extension Keychain {
values(ofType: String.self)
}
}
#endif
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Combine

/// The `@AppDependency` property wrapper is a feature provided by AppState, intended to simplify dependency handling throughout your application. It makes it easy to access, share, and manage dependencies in a neat and Swift idiomatic way.
@propertyWrapper public struct AppDependency<Value> {
/// Path for accessing `Dependency` from Application.
Expand Down
6 changes: 0 additions & 6 deletions Sources/AppState/PropertyWrappers/Slice/Constant.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import Combine
import SwiftUI

/// A property wrapper that provides access to a specific part of the AppState's state.
@propertyWrapper public struct Constant<SlicedState: MutableApplicationState, Value, SliceValue, SliceKeyPath: KeyPath<Value, SliceValue>> where SlicedState.Value == Value {
/// Holds the singleton instance of `Application`.
@ObservedObject private var app: Application = Application.shared

/// Path for accessing `State` from Application.
private let stateKeyPath: KeyPath<Application, SlicedState>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import Combine
import SwiftUI

/// A property wrapper that provides access to a specific part of the AppState's state.
@propertyWrapper public struct OptionalConstant<SlicedState: MutableApplicationState, Value, SliceValue> where SlicedState.Value == Value? {
/// Holds the singleton instance of `Application`.
@ObservedObject private var app: Application = Application.shared

/// Path for accessing `State` from Application.
private let stateKeyPath: KeyPath<Application, SlicedState>

Expand Down
17 changes: 16 additions & 1 deletion Sources/AppState/PropertyWrappers/Slice/OptionalSlice.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
#if !os(Linux) && !os(Windows)
import Combine
import SwiftUI
#endif

/// A property wrapper that provides access to a specific part of the AppState's state that is optional.
@propertyWrapper public struct OptionalSlice<SlicedState: MutableApplicationState, Value, SliceValue>: DynamicProperty where SlicedState.Value == Value? {
@propertyWrapper public struct OptionalSlice<SlicedState: MutableApplicationState, Value, SliceValue> where SlicedState.Value == Value? {
#if !os(Linux) && !os(Windows)
/// Holds the singleton instance of `Application`.
@ObservedObject private var app: Application = Application.shared
#else
/// Holds the singleton instance of `Application`.
private var app: Application = Application.shared
#endif

/// Path for accessing `State` from Application.
private let stateKeyPath: KeyPath<Application, SlicedState>
Expand Down Expand Up @@ -83,13 +90,15 @@ import SwiftUI
}
}

#if !os(Linux) && !os(Windows)
/// A binding to the `State`'s value, which can be used with SwiftUI views.
public var projectedValue: Binding<SliceValue?> {
Binding(
get: { wrappedValue },
set: { wrappedValue = $0 }
)
}
#endif

/**
Initializes a Slice with the provided parameters. This constructor is used to create a Slice that provides access and modification to a specific part of an AppState's state. It provides granular control over the AppState.
Expand Down Expand Up @@ -155,6 +164,7 @@ import SwiftUI
self.sliceKeyPath = "\(stateKeyPathString)\(valueKeyPathString)"
}

#if !os(Linux) && !os(Windows)
/// A property wrapper's synthetic storage property. This is just for SwiftUI to mutate the `wrappedValue` and send event through `objectWillChange` publisher when the `wrappedValue` changes
public static subscript<OuterSelf: ObservableObject>(
_enclosingInstance observed: OuterSelf,
Expand All @@ -173,4 +183,9 @@ import SwiftUI
observed[keyPath: storageKeyPath].wrappedValue = newValue
}
}
#endif
}

#if !os(Linux) && !os(Windows)
extension OptionalSlice: DynamicProperty { }
#endif
Loading

0 comments on commit a203263

Please sign in to comment.