Skip to content

Commit e6419d3

Browse files
Merge pull request #3635 from SwiftPackageIndex/issue-3469-dependency-transition-21
Issue 3469 dependency transition 21
2 parents b58a888 + de96c22 commit e6419d3

19 files changed

+248
-139
lines changed

Resources/ChartData/rfs6-errors.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,4 @@
254254
}
255255
]
256256
}
257-
]
257+
]

Sources/App/Commands/Analyze.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@ extension Analyze {
7171

7272

7373
static func trimCheckouts() throws {
74+
@Dependency(\.fileManager) var fileManager
7475
let checkoutDir = URL(
75-
fileURLWithPath: Current.fileManager.checkoutsDirectory(),
76+
fileURLWithPath: fileManager.checkoutsDirectory(),
7677
isDirectory: true
7778
)
78-
try Current.fileManager.contentsOfDirectory(atPath: checkoutDir.path)
79+
try fileManager.contentsOfDirectory(atPath: checkoutDir.path)
7980
.map { dir -> (String, Date)? in
8081
let url = checkoutDir.appendingPathComponent(dir)
81-
guard let mod = try Current.fileManager
82+
guard let mod = try fileManager
8283
.attributesOfItem(atPath: url.path)[.modificationDate] as? Date
8384
else { return nil }
8485
return (url.path, mod)
@@ -139,7 +140,8 @@ extension Analyze {
139140
AppMetrics.analyzeCandidatesCount?.set(packages.count)
140141

141142
// get or create directory
142-
let checkoutDir = Current.fileManager.checkoutsDirectory()
143+
@Dependency(\.fileManager) var fileManager
144+
let checkoutDir = fileManager.checkoutsDirectory()
143145
Current.logger().info("Checkout directory: \(checkoutDir)")
144146
if !Current.fileManager.fileExists(atPath: checkoutDir) {
145147
try await createCheckoutsDirectory(client: client, path: checkoutDir)
@@ -251,8 +253,9 @@ extension Analyze {
251253
/// - Throws: Shell errors
252254
static func clone(cacheDir: String, url: String) async throws {
253255
Current.logger().info("cloning \(url) to \(cacheDir)")
256+
@Dependency(\.fileManager) var fileManager
254257
try await Current.shell.run(command: .gitClone(url: URL(string: url)!, to: cacheDir),
255-
at: Current.fileManager.checkoutsDirectory())
258+
at: fileManager.checkoutsDirectory())
256259
}
257260

258261

@@ -286,7 +289,8 @@ extension Analyze {
286289
/// - Parameters:
287290
/// - package: `Package` to refresh
288291
static func refreshCheckout(package: Joined<Package, Repository>) async throws {
289-
guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: package.model) else {
292+
@Dependency(\.fileManager) var fileManager
293+
guard let cacheDir = fileManager.cacheDirectoryPath(for: package.model) else {
290294
throw AppError.invalidPackageCachePath(package.model.id, package.model.url)
291295
}
292296

@@ -322,7 +326,8 @@ extension Analyze {
322326
guard let repo = package.repository else {
323327
throw AppError.genericError(package.model.id, "updateRepository: no repository")
324328
}
325-
guard let gitDirectory = Current.fileManager.cacheDirectoryPath(for: package.model) else {
329+
@Dependency(\.fileManager) var fileManager
330+
guard let gitDirectory = fileManager.cacheDirectoryPath(for: package.model) else {
326331
throw AppError.invalidPackageCachePath(package.model.id, package.model.url)
327332
}
328333

@@ -375,7 +380,8 @@ extension Analyze {
375380
/// - Returns: future with incoming `Version`s
376381
static func getIncomingVersions(client: Client,
377382
package: Joined<Package, Repository>) async throws -> [Version] {
378-
guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: package.model) else {
383+
@Dependency(\.fileManager) var fileManager
384+
guard let cacheDir = fileManager.cacheDirectoryPath(for: package.model) else {
379385
throw AppError.invalidPackageCachePath(package.model.id, package.model.url)
380386
}
381387

@@ -546,7 +552,8 @@ extension Analyze {
546552
/// - Returns: `Result` with `Manifest` data
547553
static func getPackageInfo(package: Joined<Package, Repository>, version: Version) async throws -> PackageInfo {
548554
// check out version in cache directory
549-
guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: package.model) else {
555+
@Dependency(\.fileManager) var fileManager
556+
guard let cacheDir = fileManager.cacheDirectoryPath(for: package.model) else {
550557
throw AppError.invalidPackageCachePath(package.model.id,
551558
package.model.url)
552559
}

Sources/App/Commands/ReAnalyzeVersions.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ enum ReAnalyzeVersions {
183183
try await withEscapedDependencies { dependencies in
184184
try await database.transaction { tx in
185185
try await dependencies.yield {
186-
guard let cacheDir = Current.fileManager.cacheDirectoryPath(for: pkg.model) else { return }
186+
@Dependency(\.fileManager) var fileManager
187+
guard let cacheDir = fileManager.cacheDirectoryPath(for: pkg.model) else { return }
187188
if !Current.fileManager.fileExists(atPath: cacheDir) || refreshCheckouts {
188189
try await Analyze.refreshCheckout(package: pkg)
189190
}

Sources/App/Core/AppEnvironment.swift

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,12 @@ extension AppEnvironment {
4545

4646

4747
struct FileManager: Sendable {
48-
var attributesOfItem: @Sendable (_ path: String) throws -> [FileAttributeKey : Any]
49-
var contentsOfDirectory: @Sendable (_ path: String) throws -> [String]
50-
var contents: @Sendable (_ atPath: String) -> Data?
51-
var checkoutsDirectory: @Sendable () -> String
5248
var createDirectory: @Sendable (String, Bool, [FileAttributeKey : Any]?) throws -> Void
5349
var fileExists: @Sendable (String) -> Bool
5450
var removeItem: @Sendable (_ path: String) throws -> Void
5551
var workingDirectory: @Sendable () -> String
5652

5753
// pass-through methods to preserve argument labels
58-
func attributesOfItem(atPath path: String) throws -> [FileAttributeKey : Any] {
59-
try attributesOfItem(path)
60-
}
61-
func contents(atPath path: String) -> Data? { contents(path) }
62-
func contentsOfDirectory(atPath path: String) throws -> [String] {
63-
try contentsOfDirectory(path)
64-
}
6554
func createDirectory(atPath path: String,
6655
withIntermediateDirectories createIntermediates: Bool,
6756
attributes: [FileAttributeKey : Any]?) throws {
@@ -71,10 +60,6 @@ struct FileManager: Sendable {
7160
func removeItem(atPath path: String) throws { try removeItem(path) }
7261

7362
static let live: Self = .init(
74-
attributesOfItem: { try Foundation.FileManager.default.attributesOfItem(atPath: $0) },
75-
contentsOfDirectory: { try Foundation.FileManager.default.contentsOfDirectory(atPath: $0) },
76-
contents: { Foundation.FileManager.default.contents(atPath: $0) },
77-
checkoutsDirectory: { Environment.get("CHECKOUTS_DIR") ?? DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts" },
7863
createDirectory: { try Foundation.FileManager.default.createDirectory(atPath: $0, withIntermediateDirectories: $1, attributes: $2) },
7964
fileExists: { Foundation.FileManager.default.fileExists(atPath: $0) },
8065
removeItem: { try Foundation.FileManager.default.removeItem(atPath: $0) },
@@ -83,14 +68,6 @@ struct FileManager: Sendable {
8368
}
8469

8570

86-
extension FileManager {
87-
func cacheDirectoryPath(for package: Package) -> String? {
88-
guard let dirname = package.cacheDirectoryName else { return nil }
89-
return checkoutsDirectory() + "/" + dirname
90-
}
91-
}
92-
93-
9471
struct Git: Sendable {
9572
var commitCount: @Sendable (String) async throws -> Int
9673
var firstCommitDate: @Sendable (String) async throws -> Date
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
import Dependencies
18+
import DependenciesMacros
19+
import IssueReporting
20+
import Vapor
21+
22+
23+
@DependencyClient
24+
struct FileManagerClient {
25+
var attributesOfItem: @Sendable (_ atPath: String) throws -> [FileAttributeKey : Any]
26+
var checkoutsDirectory: @Sendable () -> String = { reportIssue("checkoutsDirectory"); return "SPI-checkouts" }
27+
var contents: @Sendable (_ atPath: String) -> Data?
28+
var contentsOfDirectory: @Sendable (_ atPath: String) throws -> [String]
29+
}
30+
31+
32+
extension FileManagerClient {
33+
func cacheDirectoryPath(for package: Package) -> String? {
34+
guard let dirname = package.cacheDirectoryName else { return nil }
35+
return checkoutsDirectory() + "/" + dirname
36+
}
37+
}
38+
39+
40+
extension FileManagerClient: DependencyKey {
41+
static var liveValue: Self {
42+
.init(
43+
attributesOfItem: { try Foundation.FileManager.default.attributesOfItem(atPath: $0) },
44+
checkoutsDirectory: { Environment.get("CHECKOUTS_DIR") ?? DirectoryConfiguration.detect().workingDirectory + "SPI-checkouts" },
45+
contents: { Foundation.FileManager.default.contents(atPath: $0) },
46+
contentsOfDirectory: { try Foundation.FileManager.default.contentsOfDirectory(atPath: $0) }
47+
)
48+
}
49+
}
50+
51+
52+
extension FileManagerClient: TestDependencyKey {
53+
static var testValue: Self {
54+
var mock = Self()
55+
// Override the `unimplemented` default because it is a very common dependency.
56+
mock.checkoutsDirectory = { "SPI-checkouts" }
57+
return mock
58+
}
59+
}
60+
61+
62+
extension DependencyValues {
63+
var fileManager: FileManagerClient {
64+
get { self[FileManagerClient.self] }
65+
set { self[FileManagerClient.self] = newValue }
66+
}
67+
}

Sources/App/Views/Blog/BlogActions+Model.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ enum BlogActions {
6363
}
6464

6565
static func allSummaries() throws -> [PostSummary] {
66-
guard let data = Current.fileManager.contents(atPath: Self.blogIndexYmlPath) else {
66+
@Dependency(\.fileManager) var fileManager
67+
guard let data = fileManager.contents(atPath: Self.blogIndexYmlPath) else {
6768
throw AppError.genericError(nil, "failed to read posts.yml")
6869
}
6970

@@ -78,11 +79,12 @@ enum BlogActions {
7879
extension BlogActions.Model.PostSummary {
7980

8081
var postMarkdown: String {
82+
@Dependency(\.fileManager) var fileManager
8183
let markdownPath = Current.fileManager.workingDirectory()
8284
.appending("Resources/Blog/Posts/")
8385
.appending(slug)
8486
.appending(".md")
85-
if let markdownData = Current.fileManager.contents(atPath: markdownPath),
87+
if let markdownData = fileManager.contents(atPath: markdownPath),
8688
let markdown = String(data: markdownData, encoding: .utf8)
8789
{
8890
let parsedMarkdown = MarkdownParser().parse(markdown)

Sources/App/Views/ReadyForSwift6/ReadyForSwift6Show+Model.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
// limitations under the License.
1414

1515
import Foundation
16+
17+
import Dependencies
1618
import Plot
1719

20+
1821
enum ReadyForSwift6Show {
1922

2023
struct Model {
@@ -25,11 +28,12 @@ enum ReadyForSwift6Show {
2528
}
2629

2730
func readyForSwift6Chart(kind: ChartKind, includeTotals: Bool = false) -> Node<HTML.BodyContext> {
31+
@Dependency(\.fileManager) var fileManager
2832
let plotDataPath = Current.fileManager.workingDirectory().appending("Resources/ChartData/\(kind.dataFile)")
2933
let eventDataPath = Current.fileManager.workingDirectory().appending("Resources/ChartData/rfs6-events.json")
30-
guard let plotData = Current.fileManager.contents(atPath: plotDataPath)?.compactJson(),
31-
let eventData = Current.fileManager.contents(atPath: eventDataPath)?.compactJson()
32-
else { return .p("Couldn’t load chart data.") }
34+
guard let plotData = fileManager.contents(atPath: plotDataPath)?.compactJson(),
35+
let eventData = fileManager.contents(atPath: eventDataPath)?.compactJson()
36+
else { return .p("Couldn’t load chart data.") }
3337

3438
return .div(
3539
.data(named: "controller", value: "vega-chart"),
@@ -73,7 +77,7 @@ private extension ReadyForSwift6Show.Model.ChartKind {
7377
private extension Data {
7478
func compactJson() -> String? {
7579
guard let json = try? JSONSerialization.jsonObject(with: self),
76-
let compactedJsonData = try? JSONSerialization.data(withJSONObject: json),
80+
let compactedJsonData = try? JSONSerialization.data(withJSONObject: json, options: [.sortedKeys]),
7781
let compactJson = String(data: compactedJsonData, encoding: .utf8)
7882
else { return nil }
7983
return compactJson

Sources/App/Views/ResourceReloadIdentifier.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ struct ResourceReloadIdentifier {
3636

3737
private static func modificationDate(forLocalResource resource: String) -> Date {
3838
@Dependency(\.date.now) var now
39+
@Dependency(\.fileManager) var fileManager
3940
let pathToPublic = DirectoryConfiguration.detect().publicDirectory
4041
let url = URL(fileURLWithPath: pathToPublic + resource)
4142

4243
// Assume the file has been modified *now* if the file can't be found.
43-
guard let attributes = try? Current.fileManager.attributesOfItem(atPath: url.path)
44+
guard let attributes = try? fileManager.attributesOfItem(atPath: url.path)
4445
else { return now }
4546

4647
// Also assume the file is modified now if the attribute doesn't exist.

Tests/AppTests/AnalyzerTests.swift

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -507,11 +507,7 @@ class AnalyzerTests: AppTestCase {
507507
Current.fileManager.fileExists = { @Sendable _ in true }
508508
let commands = QueueIsolated<[String]>([])
509509
Current.shell.run = { @Sendable cmd, path in
510-
// mask variable checkout
511-
let checkoutDir = Current.fileManager.checkoutsDirectory()
512-
commands.withValue {
513-
$0.append(cmd.description.replacingOccurrences(of: checkoutDir, with: "..."))
514-
}
510+
commands.withValue { $0.append(cmd.description) }
515511
return ""
516512
}
517513
let jpr = try await Package.fetchCandidate(app.db, id: pkg.id!)
@@ -961,17 +957,13 @@ class AnalyzerTests: AppTestCase {
961957
try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion)
962958
let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10)
963959

964-
let checkoutDir = Current.fileManager.checkoutsDirectory()
965960
// claim every file exists, including our ficticious 'index.lock' for which
966961
// we want to trigger the cleanup mechanism
967962
Current.fileManager.fileExists = { @Sendable path in true }
968963

969964
let commands = QueueIsolated<[String]>([])
970965
Current.shell.run = { @Sendable cmd, path in
971-
commands.withValue {
972-
let c = cmd.description.replacingOccurrences(of: checkoutDir, with: "...")
973-
$0.append(c)
974-
}
966+
commands.withValue { $0.append(cmd.description) }
975967
return ""
976968
}
977969

@@ -995,17 +987,13 @@ class AnalyzerTests: AppTestCase {
995987
try await savePackage(on: app.db, "1".asGithubUrl.url, processingStage: .ingestion)
996988
let pkgs = try await Package.fetchCandidates(app.db, for: .analysis, limit: 10)
997989

998-
let checkoutDir = Current.fileManager.checkoutsDirectory()
999990
// claim every file exists, including our ficticious 'index.lock' for which
1000991
// we want to trigger the cleanup mechanism
1001992
Current.fileManager.fileExists = { @Sendable path in true }
1002993

1003994
let commands = QueueIsolated<[String]>([])
1004995
Current.shell.run = { @Sendable cmd, path in
1005-
commands.withValue {
1006-
let c = cmd.description.replacingOccurrences(of: checkoutDir, with: "${checkouts}")
1007-
$0.append(c)
1008-
}
996+
commands.withValue { $0.append(cmd.description) }
1009997
if cmd == .gitCheckout(branch: "master") {
1010998
throw TestError.simulatedCheckoutError
1011999
}
@@ -1155,11 +1143,7 @@ class AnalyzerTests: AppTestCase {
11551143
Current.fileManager.fileExists = { @Sendable _ in true }
11561144
let commands = QueueIsolated<[String]>([])
11571145
Current.shell.run = { @Sendable cmd, _ in
1158-
commands.withValue {
1159-
// mask variable checkout
1160-
let checkoutDir = Current.fileManager.checkoutsDirectory()
1161-
$0.append(cmd.description.replacingOccurrences(of: checkoutDir, with: "..."))
1162-
}
1146+
commands.withValue { $0.append(cmd.description) }
11631147
if cmd == .gitFetchAndPruneTags { throw TestError.simulatedFetchError }
11641148
return ""
11651149
}
@@ -1235,6 +1219,7 @@ class AnalyzerTests: AppTestCase {
12351219
// Ensure we handle 404 repos properly
12361220
// https://github.com/SwiftPackageIndex/SwiftPackageIndex-Server/issues/914
12371221
// setup
1222+
let checkoutDir = "/checkouts"
12381223
do {
12391224
let url = "1".asGithubUrl.url
12401225
let pkg = Package.init(url: url, processingStage: .ingestion)
@@ -1243,13 +1228,13 @@ class AnalyzerTests: AppTestCase {
12431228
if path.hasSuffix("github.com-foo-1") { return false }
12441229
return true
12451230
}
1246-
let repoDir = try Current.fileManager.checkoutsDirectory() + "/" + XCTUnwrap(pkg.cacheDirectoryName)
1231+
let repoDir = try checkoutDir + "/" + XCTUnwrap(pkg.cacheDirectoryName)
12471232
struct ShellOutError: Error {}
12481233
Current.shell.run = { @Sendable cmd, path in
12491234
if cmd == .gitClone(url: url, to: repoDir) {
12501235
throw ShellOutError()
12511236
}
1252-
fatalError("should not be reached")
1237+
throw TestError.unknownCommand
12531238
}
12541239
}
12551240
let lastUpdated = Date()
@@ -1268,16 +1253,16 @@ class AnalyzerTests: AppTestCase {
12681253
func test_trimCheckouts() throws {
12691254
try withDependencies {
12701255
$0.date.now = .t0
1271-
} operation: {
1272-
// setup
1273-
Current.fileManager.checkoutsDirectory = { "/checkouts" }
1274-
Current.fileManager.contentsOfDirectory = { @Sendable _ in ["foo", "bar"] }
1275-
Current.fileManager.attributesOfItem = { @Sendable path in
1256+
$0.fileManager.attributesOfItem = { @Sendable path in
12761257
[
12771258
"/checkouts/foo": [FileAttributeKey.modificationDate: Date.t0.adding(days: -31)],
12781259
"/checkouts/bar": [FileAttributeKey.modificationDate: Date.t0.adding(days: -29)],
12791260
][path]!
12801261
}
1262+
$0.fileManager.checkoutsDirectory = { "/checkouts" }
1263+
$0.fileManager.contentsOfDirectory = { @Sendable _ in ["foo", "bar"] }
1264+
} operation: {
1265+
// setup
12811266
let removedPaths = NIOLockedValueBox<[String]>([])
12821267
Current.fileManager.removeItem = { @Sendable p in removedPaths.withLockedValue { $0.append(p) } }
12831268

0 commit comments

Comments
 (0)