Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Examples

# The Sources/SafeDIMacros/SafeDICore symlink points at Sources/SafeDICore's
# BUILD.bazel, so Bazel would otherwise register a duplicate package. We
# consume the files via the `SafeDICoreSources` filegroup instead.
Sources/SafeDIMacros/SafeDICore
7 changes: 7 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SafeDI Bazel configuration.

# Use bzlmod for external dependency resolution.
common --enable_bzlmod

# Fail loudly on errors — makes CI logs readable.
common --verbose_failures
1 change: 1 addition & 0 deletions .bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9.1.0
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,22 @@ jobs:
verbose: true
os: linux

bazel:
name: Bazel Build on macOS
runs-on: macos-26
permissions:
contents: read
steps:
- name: Checkout Repo
uses: actions/checkout@v6
- name: Select Xcode Version
run: sudo xcode-select --switch /Applications/Xcode_26.4.app/Contents/Developer
- name: Build Sources
run: bazelisk build //Sources/...
- name: Build Example
working-directory: Examples/ExampleBazelIntegration
run: bazelisk build //Subproject:Subproject //ExampleBazelIntegration:ExampleBazelIntegration

readme-validation:
name: Check Markdown links
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ checkout/
.swiftpm
.build/
codesign/
.claude/
.claude/

# Bazel
bazel-*
6 changes: 6 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Root Bazel package — exports Package.swift/Package.resolved for rules_swift_package_manager."""

exports_files([
"Package.swift",
"Package.resolved",
])
5 changes: 5 additions & 0 deletions Examples/ExampleBazelIntegration/.bazelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.build
bazel-bin
bazel-out
bazel-testlogs
bazel-example_bazel_integration
1 change: 1 addition & 0 deletions Examples/ExampleBazelIntegration/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
common --enable_bzlmod
1 change: 1 addition & 0 deletions Examples/ExampleBazelIntegration/.bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9.1.0
1 change: 1 addition & 0 deletions Examples/ExampleBazelIntegration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bazel-*
2 changes: 2 additions & 0 deletions Examples/ExampleBazelIntegration/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Root BUILD file — no top-level targets. Each module lives in its
# own package (Subproject/, ExampleBazelIntegration/).
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@safedi//bazel:safedi.bzl", "safedi_compile")

# `safedi_compile` emits a single `<rule>.swift` (the dependency tree
# + every mock the module needs) and a `<rule>.safedi` module-info
# artifact in one SafeDITool invocation. SafeDITool's
# `--combined-output` mode concatenates the per-root / per-mock file
# split into one declared output, so `outs` doesn't need a hand-
# maintained list regardless of source contents.
safedi_compile(
name = "ExampleBazelIntegration_safedi",
srcs = glob(["**/*.swift"]),
deps = [
"//Subproject:Subproject_safedi",
],
)

swift_library(
name = "ExampleBazelIntegration",
srcs = glob(["**/*.swift"]) + [":ExampleBazelIntegration_safedi"],
module_name = "ExampleBazelIntegration",
visibility = ["//visibility:public"],
deps = [
"//Subproject:Subproject",
"@safedi//Sources/SafeDI",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SafeDI

#SafeDIConfiguration()
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SafeDI
import Subproject
import SwiftUI

@Instantiable(generateMock: true)
public struct LoggedInView: Instantiable, View {
public init(
user: User,
userService: AnyUserService,
noteStorage: NoteStorage,
) {
self.user = user
self.userService = userService
self.noteStorage = noteStorage
_note = State(initialValue: noteStorage.note)
}

public var body: some View {
VStack {
Text("\(user.name)’s note")
TextEditor(text: $note)
.onChange(of: note) { _, newValue in
noteStorage.note = newValue
}
Button(action: {
userService.user = nil
}, label: {
Text("Log out")
})
}
.padding()
}

@Forwarded private let user: User
@Received private let userService: AnyUserService
@Instantiated private let noteStorage: NoteStorage

@State private var note: String = ""
}

#if DEBUG
#Preview("Bare mock") {
LoggedInView.mock()
}

#Preview("Customized") {
LoggedInView.mock(
user: User(name: "dfed"),
safeDIOverrides: .init(
noteStorage: .init(defaultNote: "dfed says hello"),
),
)
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SafeDI
import Subproject
import SwiftUI

@Instantiable(generateMock: true)
public struct NameEntryView: Instantiable, View {
public init(userService: AnyUserService) {
self.userService = userService
}

public var body: some View {
VStack {
TextField(
text: $name,
prompt: Text("Enter your name"),
label: {},
)
Button(action: {
userService.user = User(name: name)
}, label: {
Text("Log in")
})
}
.padding()
}

@State private var name: String = ""

@Received private let userService: AnyUserService
}

#if DEBUG
#Preview {
NameEntryView.mock()
}
#endif
Loading
Loading