Skip to content

Commit

Permalink
Merge branch 'master' into fork_cache_fix
Browse files Browse the repository at this point in the history
  • Loading branch information
mcfedr committed Dec 20, 2017
2 parents 96133fe + 1628d43 commit 43db3dd
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 113 deletions.
8 changes: 8 additions & 0 deletions Carthage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
2190FA981FD5B2F0008D8A79 /* OutdatedDependencies in Resources */ = {isa = PBXBuildFile; fileRef = 2190FA961FD5B05F008D8A79 /* OutdatedDependencies */; };
21F11B481FE67A26009FB783 /* DB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21F11B461FE6787F009FB783 /* DB.swift */; };
3A0472F31C782B4000097EC7 /* Algorithms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0472F21C782B4000097EC7 /* Algorithms.swift */; };
3A0472F61C7836EA00097EC7 /* AlgorithmsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0472F41C782D1D00097EC7 /* AlgorithmsSpec.swift */; };
3B19041E1E4CFE4A00A866AD /* FakeOldObjc.framework in Resources */ = {isa = PBXBuildFile; fileRef = 3B19041C1E4CFE3900A866AD /* FakeOldObjc.framework */; };
Expand Down Expand Up @@ -167,6 +169,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
2190FA961FD5B05F008D8A79 /* OutdatedDependencies */ = {isa = PBXFileReference; lastKnownFileType = folder; path = OutdatedDependencies; sourceTree = "<group>"; };
21F11B461FE6787F009FB783 /* DB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DB.swift; sourceTree = "<group>"; };
3A0472F21C782B4000097EC7 /* Algorithms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Algorithms.swift; sourceTree = "<group>"; };
3A0472F41C782D1D00097EC7 /* AlgorithmsSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlgorithmsSpec.swift; sourceTree = "<group>"; };
3B19041C1E4CFE3900A866AD /* FakeOldObjc.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = FakeOldObjc.framework; sourceTree = "<group>"; };
Expand Down Expand Up @@ -350,6 +354,7 @@
CD6500D61EDD30FC00C2C3FB /* Resources */ = {
isa = PBXGroup;
children = (
2190FA961FD5B05F008D8A79 /* OutdatedDependencies */,
CD9A779A1EDD7FEB00A77B00 /* BinaryOnly */,
CD9A77981EDD7E2000A77B00 /* DuplicateDependencies */,
8AC381421FC3A900007CF7E8 /* Alamofire.framework */,
Expand Down Expand Up @@ -552,6 +557,7 @@
D0DE89411A0F2D450030A3EC /* VersionSpec.swift */,
B1F27D3D1E45382B002D4754 /* VersionFileSpec.swift */,
D0DB09A319EA354200234B16 /* XcodeSpec.swift */,
21F11B461FE6787F009FB783 /* DB.swift */,
D0D1217C19E87B05005E4BAA /* Supporting Files */,
);
name = CarthageKitTests;
Expand Down Expand Up @@ -710,6 +716,7 @@
3B19041F1E4CFE4A00A866AD /* FakeOldSwift.framework in Resources */,
CD09E28B1F0E3FFB00A0378A /* FakeSwift.framework in Resources */,
BE624F491E13424400EAEFC9 /* DuplicateDependenciesCartfile in Resources */,
2190FA981FD5B2F0008D8A79 /* OutdatedDependencies in Resources */,
B1F27D401E4541A1002D4754 /* TestVersionFile in Resources */,
D0AAAB5519FB1062007B24B3 /* TestCartfile in Resources */,
D0F551DC1A0D71AB0093311F /* TestCartfile.resolved in Resources */,
Expand Down Expand Up @@ -847,6 +854,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
21F11B481FE67A26009FB783 /* DB.swift in Sources */,
D0D121E019E8999E005E4BAA /* CartfileSpec.swift in Sources */,
D0DE89421A0F2D450030A3EC /* VersionSpec.swift in Sources */,
89A8F1781C24AB3C00C0E75A /* FrameworkExtensionsSpec.swift in Sources */,
Expand Down
81 changes: 59 additions & 22 deletions Source/CarthageKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,19 +384,7 @@ public final class Project { // swiftlint:disable:this type_body_length
///
/// This will fetch dependency repositories as necessary, but will not check
/// them out into the project's working directory.
public func updatedResolvedCartfile(_ dependenciesToUpdate: [String]? = nil, useNewResolver: Bool = false) -> SignalProducer<ResolvedCartfile, CarthageError> {
let resolverType: ResolverProtocol.Type
if useNewResolver {
resolverType = NewResolver.self
} else {
resolverType = Resolver.self
}
let resolver = resolverType.init(
versionsForDependency: versions(for:),
dependenciesForDependency: dependencies(for:version:),
resolvedGitReference: resolvedGitReference
)

public func updatedResolvedCartfile(_ dependenciesToUpdate: [String]? = nil, resolver: ResolverProtocol) -> SignalProducer<ResolvedCartfile, CarthageError> {
let resolvedCartfile: SignalProducer<ResolvedCartfile?, CarthageError> = loadResolvedCartfile()
.map(Optional.init)
.flatMapError { _ in .init(value: nil) }
Expand All @@ -413,24 +401,61 @@ public final class Project { // swiftlint:disable:this type_body_length
.map(ResolvedCartfile.init)
}

/// Attempts to determine the latest version (whether satisfiable or not)
/// of the project's Carthage dependencies.
///
/// This will fetch dependency repositories as necessary, but will not check
/// them out into the project's working directory.
private func latestDependencies(resolver: ResolverProtocol) -> SignalProducer<[Dependency: PinnedVersion], CarthageError> {
return loadResolvedCartfile()
.map { $0.dependencies.mapValues { _ in VersionSpecifier.any } }
.flatMap(.merge) { resolver.resolve(dependencies: $0, lastResolved: nil, dependenciesToUpdate: nil) }
}

public typealias OutdatedDependency = (Dependency, PinnedVersion, PinnedVersion, PinnedVersion)
/// Attempts to determine which of the project's Carthage
/// dependencies are out of date.
///
/// This will fetch dependency repositories as necessary, but will not check
/// them out into the project's working directory.
public func outdatedDependencies(_ includeNestedDependencies: Bool) -> SignalProducer<[(Dependency, PinnedVersion, PinnedVersion)], CarthageError> {
typealias OutdatedDependency = (Dependency, PinnedVersion, PinnedVersion)
public func outdatedDependencies(_ includeNestedDependencies: Bool, useNewResolver: Bool = true, resolver: ResolverProtocol? = nil) -> SignalProducer<[OutdatedDependency], CarthageError> {
let resolverType: ResolverProtocol.Type
if useNewResolver {
resolverType = NewResolver.self
} else {
resolverType = Resolver.self
}

let dependencies: (Dependency, PinnedVersion) -> SignalProducer<(Dependency, VersionSpecifier), CarthageError>
if includeNestedDependencies {
dependencies = self.dependencies(for:version:)
} else {
dependencies = { _, _ in .empty }
}

let resolver = resolver ?? resolverType.init(
versionsForDependency: versions(for:),
dependenciesForDependency: dependencies,
resolvedGitReference: resolvedGitReference
)

let outdatedDependencies = SignalProducer
.combineLatest(
loadResolvedCartfile(),
updatedResolvedCartfile()
updatedResolvedCartfile(resolver: resolver),
latestDependencies(resolver: resolver)
)
.map { ($0.dependencies, $1.dependencies) }
.map { (currentDependencies, updatedDependencies) -> [OutdatedDependency] in
.map { ($0.dependencies, $1.dependencies, $2) }
.map { (currentDependencies, updatedDependencies, latestDependencies) -> [OutdatedDependency] in
return updatedDependencies.flatMap { (project, version) -> OutdatedDependency? in
if let resolved = currentDependencies[project], resolved != version {
return (project, resolved, version)
if let resolved = currentDependencies[project], let latest = latestDependencies[project], resolved != version || resolved != latest {
if SemanticVersion.from(resolved).value == nil, version == resolved {
// If resolved version is not a semantic version but a commit
// it is a false-positive if `version` and `resolved` are the same
return nil
}

return (project, resolved, version, latest)
} else {
return nil
}
Expand All @@ -447,7 +472,7 @@ public final class Project { // swiftlint:disable:this type_body_length
loadCombinedCartfile()
)
.map { oudatedDependencies, combinedCartfile -> [OutdatedDependency] in
return oudatedDependencies.filter { project, _, _ in
return oudatedDependencies.filter { project, _, _, _ in
return combinedCartfile.dependencies[project] != nil
}
}
Expand All @@ -462,7 +487,19 @@ public final class Project { // swiftlint:disable:this type_body_length
buildOptions: BuildOptions,
dependenciesToUpdate: [String]? = nil
) -> SignalProducer<(), CarthageError> {
return updatedResolvedCartfile(dependenciesToUpdate, useNewResolver: useNewResolver)
let resolverType: ResolverProtocol.Type
if useNewResolver {
resolverType = NewResolver.self
} else {
resolverType = Resolver.self
}
let resolver = resolverType.init(
versionsForDependency: versions(for:),
dependenciesForDependency: dependencies(for:version:),
resolvedGitReference: resolvedGitReference
)

return updatedResolvedCartfile(dependenciesToUpdate, resolver: resolver)
.attemptMap { resolvedCartfile -> Result<(), CarthageError> in
return self.writeResolvedCartfile(resolvedCartfile)
}
Expand Down
6 changes: 3 additions & 3 deletions Source/carthage/Outdated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ public struct OutdatedCommand: CommandProtocol {

if !outdatedDependencies.isEmpty {
carthage.println(formatting.path("The following dependencies are outdated:"))
for (project, current, updated) in outdatedDependencies {
for (project, current, updated, latest) in outdatedDependencies {
if options.outputXcodeWarnings {
carthage.println("warning: \(formatting.projectName(project.name)) is out of date (\(current) -> \(updated))")
carthage.println("warning: \(formatting.projectName(project.name)) is out of date (\(current) -> \(updated)) (Latest: \(latest))")
} else {
carthage.println(formatting.projectName(project.name) + " \(current) -> \(updated)")
carthage.println(formatting.projectName(project.name) + " \(current) -> \(updated) (Latest: \(latest))")
}
}
} else {
Expand Down
104 changes: 104 additions & 0 deletions Tests/CarthageKitTests/DB.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import CarthageKit
import ReactiveSwift
import Foundation
import Result
import Tentacle

// swiftlint:disable no_extension_access_modifier
let git1 = Dependency.git(GitURL("https://example.com/repo1"))
let git2 = Dependency.git(GitURL("https://example.com/repo2.git"))
let github1 = Dependency.gitHub(.dotCom, Repository(owner: "gob", name: "1"))
let github2 = Dependency.gitHub(.dotCom, Repository(owner: "gob", name: "2"))
let github3 = Dependency.gitHub(.dotCom, Repository(owner: "gob", name: "3"))
let github4 = Dependency.gitHub(.dotCom, Repository(owner: "gob", name: "4"))
let github5 = Dependency.gitHub(.dotCom, Repository(owner: "gob", name: "5"))
let github6 = Dependency.gitHub(.dotCom, Repository(owner: "gob", name: "6"))

extension PinnedVersion {
static let v0_1_0 = PinnedVersion("v0.1.0")
static let v1_0_0 = PinnedVersion("v1.0.0")
static let v1_1_0 = PinnedVersion("v1.1.0")
static let v1_2_0 = PinnedVersion("v1.2.0")
static let v2_0_0 = PinnedVersion("v2.0.0")
static let v2_0_0_beta_1 = PinnedVersion("v2.0.0-beta.1")
static let v2_0_1 = PinnedVersion("v2.0.1")
static let v3_0_0_beta_1 = PinnedVersion("v3.0.0-beta.1")
static let v3_0_0 = PinnedVersion("v3.0.0")
}

extension SemanticVersion {
static let v0_1_0 = SemanticVersion(major: 0, minor: 1, patch: 0)
static let v1_0_0 = SemanticVersion(major: 1, minor: 0, patch: 0)
static let v1_1_0 = SemanticVersion(major: 1, minor: 1, patch: 0)
static let v1_2_0 = SemanticVersion(major: 1, minor: 2, patch: 0)
static let v2_0_0 = SemanticVersion(major: 2, minor: 0, patch: 0)
static let v2_0_1 = SemanticVersion(major: 2, minor: 0, patch: 1)
static let v3_0_0 = SemanticVersion(major: 3, minor: 0, patch: 0)
}
// swiftlint:enable no_extension_access_modifier

internal struct DB {
var versions: [Dependency: [PinnedVersion: [Dependency: VersionSpecifier]]]
var references: [Dependency: [String: PinnedVersion]] = [:]

func versions(for dependency: Dependency) -> SignalProducer<PinnedVersion, CarthageError> {
if let versions = self.versions[dependency] {
return .init(versions.keys)
} else {
return .init(error: .taggedVersionNotFound(dependency))
}
}

func dependencies(for dependency: Dependency, version: PinnedVersion) -> SignalProducer<(Dependency, VersionSpecifier), CarthageError> {
if let dependencies = self.versions[dependency]?[version] {
return .init(dependencies.map { ($0.0, $0.1) })
} else {
return .empty
}
}

func resolvedGitReference(_ dependency: Dependency, reference: String) -> SignalProducer<PinnedVersion, CarthageError> {
if let version = references[dependency]?[reference] {
return .init(value: version)
} else {
return .empty
}
}

func resolver(_ resolverType: ResolverProtocol.Type = Resolver.self) -> ResolverProtocol {
return resolverType.init(
versionsForDependency: self.versions(for:),
dependenciesForDependency: self.dependencies(for:version:),
resolvedGitReference: self.resolvedGitReference(_:reference:)
)
}

func resolve(
_ resolverType: ResolverProtocol.Type,
_ dependencies: [Dependency: VersionSpecifier],
resolved: [Dependency: PinnedVersion] = [:],
updating: Set<Dependency> = []
) -> Result<[Dependency: PinnedVersion], CarthageError> {
let resolver = resolverType.init(
versionsForDependency: self.versions(for:),
dependenciesForDependency: self.dependencies(for:version:),
resolvedGitReference: self.resolvedGitReference(_:reference:)
)
return resolver
.resolve(
dependencies: dependencies,
lastResolved: resolved,
dependenciesToUpdate: updating.map { $0.name }
)
.first()!
}
}

extension DB: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (Dependency, [PinnedVersion: [Dependency: VersionSpecifier]])...) {
self.init(versions: [:], references: [:])
for (key, value) in elements {
versions[key] = value
}
}
}
97 changes: 97 additions & 0 deletions Tests/CarthageKitTests/ProjectSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,95 @@ class ProjectSpec: QuickSpec {
}
}

describe("outdated dependencies") {
it("should return return available updates for outdated dependencies") {
var db: DB = [
github1: [
.v1_0_0: [:]
],
github2: [
.v1_0_0: [:],
.v1_1_0: [:],
.v2_0_0: [:]
],
github3: [
.v1_0_0: [:],
.v1_1_0: [:],
.v1_2_0: [:],
.v2_0_0: [:],
.v2_0_1: [:]
],
github4: [
.v1_0_0: [:],
.v1_2_0: [:],
.v3_0_0_beta_1: [:],
.v3_0_0: [:]
],
github5: [
.v1_0_0: [:]
],
github6: [
.v1_0_0: [:]
]
]
let currentSHA = "2ea246ae4573538886ffb946d70d141583443734"
let nextSHA = "809b8eb20f4b6b9e805b62de3084fbc7fcde54cc"
db.references = [
github3: [
"2.0": PinnedVersion("v2.0.1")
],
github4: [
"2.0": PinnedVersion("v2.0.1")
],
github5: [
"development": PinnedVersion(currentSHA)
],
github6: [
"development": PinnedVersion(nextSHA)
]
]
let directoryURL = Bundle(for: type(of: self)).url(forResource: "OutdatedDependencies", withExtension: nil)!
let project = Project(directoryURL: directoryURL)

let result = project.outdatedDependencies(false, useNewResolver: false, resolver: db.resolver()).single()
expect(result).notTo(beNil())
expect(result!.error).to(beNil())
expect(result!.value!).notTo(beNil())

let outdatedDependencies = result!.value!.reduce(into: [:], { (result, next) in
result[next.0] = (next.1, next.2, next.3)
})

// Github 1 has no updates available
expect(outdatedDependencies[github1]).to(beNil())

// Github 2 is currently at 1.0.0, can be updated to the latest version which is 2.0.0
// Github 2 has no constraint in the Cartfile
expect(outdatedDependencies[github2]!.0) == PinnedVersion("v1.0.0")
expect(outdatedDependencies[github2]!.1) == PinnedVersion("v2.0.0")
expect(outdatedDependencies[github2]!.2) == PinnedVersion("v2.0.0")

// Github 3 is currently at 2.0.0, latest is 2.0.1, to which it can be updated
// Github 3 has a constraint in the Cartfile
expect(outdatedDependencies[github3]!.0) == PinnedVersion("v2.0.0")
expect(outdatedDependencies[github3]!.1) == PinnedVersion("v2.0.1")
expect(outdatedDependencies[github3]!.2) == PinnedVersion("v2.0.1")

// Github 4 is currently at 2.0.0, latest is 3.0.0, but it can only be updated to 2.0.1
expect(outdatedDependencies[github4]!.0) == PinnedVersion("v2.0.0")
expect(outdatedDependencies[github4]!.1) == PinnedVersion("v2.0.1")
expect(outdatedDependencies[github4]!.2) == PinnedVersion("v3.0.0")

// Github 5 is pinned to a branch and is already at the most recent commit, so it should not be displayed
expect(outdatedDependencies[github5]).to(beNil())

// Github 6 is pinned ot a branch which has new commits, so it should be displayed
expect(outdatedDependencies[github6]!.0) == PinnedVersion(currentSHA)
expect(outdatedDependencies[github6]!.1) == PinnedVersion(nextSHA)
expect(outdatedDependencies[github6]!.2) == PinnedVersion("v1.0.0")
}
}

describe("platformForFramework") {
let testStaticFrameworkURL = Bundle(for: type(of: self)).url(forResource: "Alamofire.framework", withExtension: nil)!
// Checks the framework's executable binary, not the Info.plist.
Expand Down Expand Up @@ -467,3 +556,11 @@ extension ProjectEvent {
return false
}
}

private func ==<A: Equatable, B: Equatable>(lhs: [(A, B)], rhs: [(A, B)]) -> Bool {
guard lhs.count == rhs.count else { return false }
for (lhs, rhs) in zip(lhs, rhs) {
guard lhs == rhs else { return false }
}
return true
}

0 comments on commit 43db3dd

Please sign in to comment.