Skip to content

Commit

Permalink
Merge pull request #103 from Carthage/no-logging-in-carthagekit
Browse files Browse the repository at this point in the history
Stop writing console messages in CarthageKit
  • Loading branch information
jspahrsummers committed Nov 19, 2014
2 parents 23f2939 + 97d24f2 commit 788c046
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 135 deletions.
4 changes: 4 additions & 0 deletions Carthage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
88ED56D619ECE34900CBF5C4 /* Git.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ED56D519ECE34900CBF5C4 /* Git.swift */; };
D00758481A1C6AC000625DBF /* CarthageKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00758471A1C6AC000625DBF /* CarthageKitExtensions.swift */; };
D01D82D71A10160700F0DD94 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01D82D61A10160700F0DD94 /* Resolver.swift */; };
D01D82DD1A10B01D00F0DD94 /* ResolverSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01D82DC1A10B01D00F0DD94 /* ResolverSpec.swift */; };
D01F8A3F19EA28C400643E7C /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D01F8A3E19EA28C400643E7C /* ReactiveCocoa.framework */; };
Expand Down Expand Up @@ -100,6 +101,7 @@

/* Begin PBXFileReference section */
88ED56D519ECE34900CBF5C4 /* Git.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Git.swift; sourceTree = "<group>"; };
D00758471A1C6AC000625DBF /* CarthageKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CarthageKitExtensions.swift; path = carthage/CarthageKitExtensions.swift; sourceTree = SOURCE_ROOT; };
D01D82D61A10160700F0DD94 /* Resolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
D01D82DC1A10B01D00F0DD94 /* ResolverSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolverSpec.swift; sourceTree = "<group>"; };
D01F8A3E19EA28C400643E7C /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ReactiveCocoa.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -227,6 +229,7 @@
children = (
D095BA3B1A187D40007F15D6 /* Bootstrap.swift */,
D03B32BE19EA49D8007788BE /* Build.swift */,
D00758471A1C6AC000625DBF /* CarthageKitExtensions.swift */,
F66685FB19E8F1EC00487A88 /* Checkout.swift */,
D06939B019E8A757001E44AE /* Command.swift */,
D06939B219E8A79F001E44AE /* Help.swift */,
Expand Down Expand Up @@ -620,6 +623,7 @@
D0E7B65519E9C76900EDBA4D /* Help.swift in Sources */,
D0E7B65419E9C76900EDBA4D /* Command.swift in Sources */,
D03B32BF19EA49D8007788BE /* Build.swift in Sources */,
D00758481A1C6AC000625DBF /* CarthageKitExtensions.swift in Sources */,
D0E7B65619E9C76900EDBA4D /* main.swift in Sources */,
F689C33619EA14D4006D1EFA /* Checkout.swift in Sources */,
);
Expand Down
1 change: 1 addition & 0 deletions Carthage/Bootstrap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public struct BootstrapCommand: CommandType {
return ColdSignal.fromResult(Project.loadFromDirectory(directoryURL))
.on(next: { project in
project.preferHTTPS = !options.useSSH
project.projectEvents.observe(ProjectEventSink())
})
.map { project -> ColdSignal<()> in
return ColdSignal.lazy {
Expand Down
31 changes: 31 additions & 0 deletions Carthage/CarthageKitExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// CarthageKitExtensions.swift
// Carthage
//
// Created by Justin Spahr-Summers on 2014-11-18.
// Copyright (c) 2014 Carthage. All rights reserved.
//

// This file contains extensions to CarthageKit, for use by the Carthage command
// line tool. Generally, these extensions are not general enough to include in
// the framework itself.

import CarthageKit
import Foundation
import ReactiveCocoa

/// Logs project events put into the sink.
internal struct ProjectEventSink: SinkType {
mutating func put(event: ProjectEvent) {
switch event {
case let .Cloning(project):
println("*** Cloning \(project.name)")

case let .Fetching(project):
println("*** Fetching \(project.name)")

case let .CheckingOut(project, revision):
println("*** Checking out \(project.name) at \"\(revision)\"")
}
}
}
1 change: 1 addition & 0 deletions Carthage/Checkout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public struct CheckoutCommand: CommandType {
return ColdSignal.fromResult(Project.loadFromDirectory(directoryURL))
.on(next: { project in
project.preferHTTPS = !options.useSSH
project.projectEvents.observe(ProjectEventSink())
})
.map { $0.checkoutLockedDependencies() }
.merge(identity)
Expand Down
1 change: 1 addition & 0 deletions Carthage/Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public struct UpdateCommand: CommandType {
return ColdSignal.fromResult(Project.loadFromDirectory(directoryURL))
.on(next: { project in
project.preferHTTPS = !options.useSSH
project.projectEvents.observe(ProjectEventSink())
})
.map { $0.updateDependencies() }
.merge(identity)
Expand Down
11 changes: 8 additions & 3 deletions CarthageKit/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public enum CarthageError {
public static let exitCodeKey = "CarthageErrorExitCode"

/// A launched task failed with an erroneous exit code.
case ShellTaskFailed(exitCode: Int)
case ShellTaskFailed(exitCode: Int, standardError: String?)

/// One or more arguments was invalid.
case InvalidArgument(description: String)
Expand Down Expand Up @@ -48,9 +48,14 @@ public enum CarthageError {
/// An `NSError` object corresponding to this error code.
public var error: NSError {
switch (self) {
case let .ShellTaskFailed(code):
case let .ShellTaskFailed(code, errors):
var description = "A shell task failed with exit code \(code)"
if let errors = errors {
description += ":\n\(errors)"
}

return NSError(domain: CarthageErrorDomain, code: 1, userInfo: [
NSLocalizedDescriptionKey: "A shell task failed with exit code \(code)",
NSLocalizedDescriptionKey: description,
CarthageError.exitCodeKey: code
])

Expand Down
58 changes: 40 additions & 18 deletions CarthageKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ public let CarthageProjectCartfilePath = "Cartfile"
/// The relative path to a project's Cartfile.lock.
public let CarthageProjectCartfileLockPath = "Cartfile.lock"

/// Describes an event occurring to or with a project.
public enum ProjectEvent {
/// The project is beginning to clone.
case Cloning(ProjectIdentifier)

/// The project is beginning a fetch.
case Fetching(ProjectIdentifier)

/// The project is being checked out to the specified revision.
case CheckingOut(ProjectIdentifier, String)
}

/// Represents a project that is using Carthage.
public final class Project {
/// File URL to the root directory of the project.
Expand All @@ -35,7 +47,16 @@ public final class Project {
/// Whether to prefer HTTPS for cloning (vs. SSH).
public var preferHTTPS = true

/// Sends each event that occurs to a project underneath the receiver (or
/// the receiver itself).
public let projectEvents: HotSignal<ProjectEvent>
private let _projectEventsSink: SinkOf<ProjectEvent>

public required init(directoryURL: NSURL, cartfile: Cartfile) {
let (signal, sink) = HotSignal<ProjectEvent>.pipe()
projectEvents = signal
_projectEventsSink = sink

self.directoryURL = directoryURL

// TODO: Load this lazily.
Expand Down Expand Up @@ -154,10 +175,10 @@ public final class Project {
if NSFileManager.defaultManager().createDirectoryAtURL(repositoryURL, withIntermediateDirectories: false, attributes: nil, error: nil) {
// If we created the directory, we're now responsible for
// cloning it.
println("*** Cloning \(project.name)")
self._projectEventsSink.put(.Cloning(project))
result = cloneRepository(remoteURL, repositoryURL).wait()
} else {
println("*** Fetching \(project.name)")
self._projectEventsSink.put(.Fetching(project))
result = fetchRepository(repositoryURL, remoteURL: remoteURL).wait()
}

Expand Down Expand Up @@ -248,7 +269,7 @@ public final class Project {

let checkoutSignal = checkoutRepositoryToDirectory(repositoryURL, workingDirectoryURL, revision: revision)
.on(subscribed: {
println("*** Checking out \(project.name) at \"\(revision)\"")
self._projectEventsSink.put(.CheckingOut(project, revision))
})

return commitExistsInRepository(repositoryURL, revision: revision)
Expand Down Expand Up @@ -280,28 +301,29 @@ public final class Project {

/// Attempts to build each Carthage dependency that has been checked out.
///
/// Returns a signal of all standard output from `xcodebuild`, and a signal
/// which will send each dependency successfully built.
public func buildCheckedOutDependencies(configuration: String) -> (HotSignal<NSData>, ColdSignal<Dependency<PinnedVersion>>) {
/// Returns a signal of all standard output from `xcodebuild`, and a
/// signal-of-signals representing each scheme being built.
public func buildCheckedOutDependencies(configuration: String) -> (HotSignal<NSData>, ColdSignal<BuildSchemeSignal>) {
let (stdoutSignal, stdoutSink) = HotSignal<NSData>.pipe()

let dependenciesSignal = ColdSignal<CartfileLock>.lazy {
let schemeSignals = ColdSignal<CartfileLock>.lazy {
return .fromResult(self.readCartfileLock())
}
.map { lockFile in ColdSignal.fromValues(lockFile.dependencies) }
.merge(identity)
.map { dependency -> ColdSignal<Dependency<PinnedVersion>> in
let (buildOutput, buildProducts) = buildDependencyProject(dependency.project, self.directoryURL, withConfiguration: configuration)
let outputDisposable = buildOutput.observe(stdoutSink)

return buildProducts
.then(.single(dependency))
.on(disposed: {
outputDisposable.dispose()
})
.map { dependency -> ColdSignal<BuildSchemeSignal> in
let (buildOutput, schemeSignals) = buildDependencyProject(dependency.project, self.directoryURL, withConfiguration: configuration)

return ColdSignal.lazy {
let outputDisposable = buildOutput.observe(stdoutSink)

return schemeSignals
.on(disposed: {
outputDisposable.dispose()
})
}
}
.concat(identity)

return (stdoutSignal, dependenciesSignal)
return (stdoutSignal, schemeSignals)
}
}
66 changes: 38 additions & 28 deletions CarthageKit/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,35 @@ extension TaskDescription: Printable {
}
}

/// Creates a pipe that, when written to, will place data into the given sink.
private func pipeForWritingToSink(sink: SinkOf<NSData>) -> NSPipe {
/// Creates an NSPipe that will aggregate all data sent to it, and eventually
/// replay it upon the returned signal.
///
/// If a sink is given, data received on the pipe will also be forwarded to it
/// as it arrives.
private func pipeForAggregatingData(forwardingSink: SinkOf<NSData>?) -> (NSPipe, ColdSignal<NSData>) {
let (signal, sink) = HotSignal<NSData>.pipe()
let pipe = NSPipe()

pipe.fileHandleForReading.readabilityHandler = { handle in
sink.put(handle.availableData)
let data = handle.availableData
sink.put(data)
forwardingSink?.put(data)
}

return pipe
let aggregatedData = signal.scan(initial: NSData()) { (accumulated, data) in
let buffer = accumulated.mutableCopy() as NSMutableData
buffer.appendData(data)
return buffer
}.replay(1)

// Start the aggregated data with an initial value.
sink.put(NSData())

return (pipe, aggregatedData)
}

/// Launches a new shell task, using the parameters from `taskDescription`.
///
/// If `standardError` is not specified, it will be inherited from the parent
/// process.
///
/// Returns a cold signal that will launch the task when started, then send one
/// `NSData` value (representing aggregated data from `stdout`) and complete
/// upon success.
Expand Down Expand Up @@ -105,32 +118,29 @@ public func launchTask(taskDescription: TaskDescription, standardOutput: SinkOf<
subscriber.disposable.addDisposable(disposable)
}

let (stdout, stdoutSink) = HotSignal<NSData>.pipe()
task.standardOutput = pipeForWritingToSink(stdoutSink)

let aggregatedOutput = stdout.scan(initial: NSData()) { (accumulated, data) in
let buffer = accumulated.mutableCopy() as NSMutableData
buffer.appendData(data)
return buffer
}.replay(1)
let (stdoutPipe, stdout) = pipeForAggregatingData(standardOutput)
task.standardOutput = stdoutPipe

// Start the aggregated output with an initial value.
stdoutSink.put(NSData())

if let output = standardOutput {
subscriber.disposable.addDisposable(stdout.observe(output))
}

if let error = standardError {
task.standardError = pipeForWritingToSink(error)
}
let (stderrPipe, stderr) = pipeForAggregatingData(standardError)
task.standardError = stderrPipe

task.terminationHandler = { task in
if task.terminationStatus == EXIT_SUCCESS {
aggregatedOutput.take(1).start(subscriber)
stdout.take(1).start(subscriber)
} else {
let error = CarthageError.ShellTaskFailed(exitCode: Int(task.terminationStatus))
subscriber.put(.Error(error.error))
stderr
.take(1)
.map { data -> String? in
if data.length > 0 {
return NSString(data: data, encoding: NSUTF8StringEncoding) as String?
} else {
return nil
}
}
.start(next: { string in
let error = CarthageError.ShellTaskFailed(exitCode: Int(task.terminationStatus), standardError: string)
subscriber.put(.Error(error.error))
})
}
}

Expand Down
47 changes: 27 additions & 20 deletions CarthageKit/Xcode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -572,16 +572,21 @@ public func buildScheme(scheme: String, withConfiguration configuration: String,
return (stdoutSignal, buildSignal)
}

/// A signal representing a scheme being built.
///
/// A signal of this type should send the project and scheme name when building
/// begins, then complete or error when building terminates.
public typealias BuildSchemeSignal = ColdSignal<(ProjectLocator, String)>

/// Attempts to build the dependency identified by the given project, then
/// places its build product into the root directory given.
///
/// Returns a signal of all standard output from `xcodebuild`, and a signal
/// which will send the file URLs to each build product.
public func buildDependencyProject(dependency: ProjectIdentifier, rootDirectoryURL: NSURL, withConfiguration configuration: String) -> (HotSignal<NSData>, ColdSignal<NSURL>) {
/// Returns signals in the same format as buildInDirectory().
public func buildDependencyProject(dependency: ProjectIdentifier, rootDirectoryURL: NSURL, withConfiguration configuration: String) -> (HotSignal<NSData>, ColdSignal<BuildSchemeSignal>) {
let dependencyURL = rootDirectoryURL.URLByAppendingPathComponent(dependency.relativePath, isDirectory: true)

let (buildOutput, buildProducts) = buildInDirectory(dependencyURL, withConfiguration: configuration)
let copiedProducts = ColdSignal<NSURL>.lazy {
let (buildOutput, schemeSignals) = buildInDirectory(dependencyURL, withConfiguration: configuration)
let copyProducts = ColdSignal<BuildSchemeSignal>.lazy {
let rootBinariesURL = rootDirectoryURL.URLByAppendingPathComponent(CarthageBinariesFolderName, isDirectory: true)

var error: NSError?
Expand All @@ -599,23 +604,23 @@ public func buildDependencyProject(dependency: ProjectIdentifier, rootDirectoryU
return .error(error ?? CarthageError.WriteFailed(dependencyBinariesURL).error)
}

return buildProducts
return schemeSignals
}

return (buildOutput, copiedProducts)
return (buildOutput, copyProducts)
}

/// Builds the first project or workspace found within the given directory.
///
/// Returns a signal of all standard output from `xcodebuild`, and a signal
/// which will send the URL to each product successfully built.
public func buildInDirectory(directoryURL: NSURL, withConfiguration configuration: String) -> (HotSignal<NSData>, ColdSignal<NSURL>) {
/// Returns a signal of all standard output from `xcodebuild`, and a
/// signal-of-signals representing each scheme being built.
public func buildInDirectory(directoryURL: NSURL, withConfiguration configuration: String) -> (HotSignal<NSData>, ColdSignal<BuildSchemeSignal>) {
precondition(directoryURL.fileURL)

let (stdoutSignal, stdoutSink) = HotSignal<NSData>.pipe()
let locatorSignal = locateProjectsInDirectory(directoryURL)

let productURLs = locatorSignal
let schemeSignals = locatorSignal
.filter { (project: ProjectLocator) in
switch project {
case .ProjectFile:
Expand All @@ -631,17 +636,19 @@ public func buildInDirectory(directoryURL: NSURL, withConfiguration configuratio
}
.merge(identity)
.combineLatestWith(locatorSignal.take(1))
.map { (scheme: String, project: ProjectLocator) -> ColdSignal<NSURL> in
.map { (scheme: String, project: ProjectLocator) -> ColdSignal<(ProjectLocator, String)> in
let (buildOutput, productURLs) = buildScheme(scheme, withConfiguration: configuration, inProject: project, workingDirectoryURL: directoryURL)
let outputDisposable = buildOutput.observe(stdoutSink)

return productURLs.on(subscribed: {
println("*** Building scheme \"\(scheme)\" in \(project)")
}, disposed: {
outputDisposable.dispose()
})
return ColdSignal.lazy {
let outputDisposable = buildOutput.observe(stdoutSink)

return ColdSignal.single((project, scheme))
.concat(productURLs.then(.empty()))
.on(disposed: {
outputDisposable.dispose()
})
}
}
.concat(identity)

return (stdoutSignal, productURLs)
return (stdoutSignal, schemeSignals)
}

0 comments on commit 788c046

Please sign in to comment.