 	}
 }
 
-/// Returns the value of all build settings in the given configuration.
-public func buildSettings(arguments: BuildArguments) -> ColdSignal<Dictionary<String, String>> {
-	let task = xcodebuildTask("-showBuildSettings", arguments)
-
-	return launchTask(task)
-		.map { (data: NSData) -> String in
-			return NSString(data: data, encoding: NSStringEncoding(NSUTF8StringEncoding))!
-		}
-		.map { (string: String) -> ColdSignal<String> in
-			return string.linesSignal
-		}
-		.merge(identity)
-		.map { (line: String) -> Dictionary<String, String> in
-			let components = split(line, { $0 == "=" }, maxSplit: 1)
-			let trimSet = NSCharacterSet.whitespaceAndNewlineCharacterSet()
-
-			if components.count == 2 {
-				return [ components[0].stringByTrimmingCharactersInSet(trimSet): components[1].stringByTrimmingCharactersInSet(trimSet) ]
-			} else {
-				return [:]
-			}
-		}
-		.reduce(initial: [:], combineDictionaries)
-}
-
-/// Returns the value for the given build setting, or an error if it could not
-/// be determined.
-private func valueForBuildSetting(setting: String, settings: Dictionary<String, String>) -> ColdSignal<String> {
-	if let value = settings[setting] {
-		return .single(value)
-	} else {
-		return .error(CarthageError.MissingBuildSetting(setting).error)
-	}
-}
-
-/// Returns the value for the given build setting, or an error if it could not
-/// be determined.
-private func valueForBuildSetting(setting: String, arguments: BuildArguments) -> ColdSignal<String> {
-	return buildSettings(arguments)
-		.map { settings in valueForBuildSetting(setting, settings) }
-		.merge(identity)
-}
-
-/// Attempts to determine the ProductType corresponding to the given build
-/// settings.
-private func productType(settings: Dictionary<String, String>) -> ColdSignal<ProductType> {
-	return valueForBuildSetting("PRODUCT_TYPE", settings)
-		.tryMap { typeString -> Result<ProductType> in
+public func ==(lhs: ProductType, rhs: ProductType) -> Bool {
+	switch (lhs, rhs) {
+	case (.Framework, .Framework):
+		return true
+
+	case (.StaticLibrary, .StaticLibrary):
+		return true
+
+	case (.TestBundle, .TestBundle):
+		return true
+
+	default:
+		return false
+	}
+}
+
+/// A map of build settings and their values, as generated by Xcode.
+public struct BuildSettings {
+	/// All build settings given at initialization.
+	public let settings: Dictionary<String, String>
+
+	public init(_ settings: Dictionary<String, String>) {
+		self.settings = settings
+	}
+
+	/// Invokes `xcodebuild` to retrieve build settings for the given build
+	/// arguments.
+	public static func loadWithArguments(arguments: BuildArguments) -> ColdSignal<BuildSettings> {
+		let task = xcodebuildTask("-showBuildSettings", arguments)
+
+		return launchTask(task)
+			.map { (data: NSData) -> String in
+				return NSString(data: data, encoding: NSStringEncoding(NSUTF8StringEncoding))!
+			}
+			.map { (string: String) -> ColdSignal<String> in
+				return string.linesSignal
+			}
+			.merge(identity)
+			.map { (line: String) -> Dictionary<String, String> in
+				let components = split(line, { $0 == "=" }, maxSplit: 1)
+				let trimSet = NSCharacterSet.whitespaceAndNewlineCharacterSet()
+
+				if components.count == 2 {
+					return [ components[0].stringByTrimmingCharactersInSet(trimSet): components[1].stringByTrimmingCharactersInSet(trimSet) ]
+				} else {
+					return [:]
+				}
+			}
+			.reduce(initial: [:], combineDictionaries)
+			.map { self($0) }
+	}
+
+	/// Determines which platform the given scheme builds for, by default.
+	///
+	/// If the platform is unrecognized or could not be determined, an error will be
+	/// sent on the returned signal.
+	public static func platformForScheme(scheme: String, inProject project: ProjectLocator) -> ColdSignal<Platform> {
+		return loadWithArguments(BuildArguments(project: project, scheme: scheme))
+			.tryMap { settings -> Result<String> in
+				return settings["PLATFORM_NAME"]
+			}
+			.tryMap(Platform.fromString)
+	}
+
+	/// Returns the value for the given build setting, or an error if it could
+	/// not be determined.
+	public subscript(key: String) -> Result<String> {
+		if let value = settings[key] {
+			return success(value)
+		} else {
+			return failure(CarthageError.MissingBuildSetting(key).error)
+		}
+	}
+
+	/// Attempts to determine the ProductType specified in these build settings.
+	public var productType: Result<ProductType> {
+		return self["PRODUCT_TYPE"].flatMap { typeString in
 			return ProductType.fromString(typeString)
 		}
-}
-
-/// Determines the URL to the built products directory for the given settings.
-private func URLToBuildProductsDirectory(settings: Dictionary<String, String>) -> ColdSignal<NSURL> {
-	return valueForBuildSetting("BUILT_PRODUCTS_DIR", settings)
-		.tryMap { productsDir -> Result<NSURL> in
+	}
+
+	/// Attempts to determine the URL to the built products directory.
+	public var builtProductsDirectoryURL: Result<NSURL> {
+		return self["BUILT_PRODUCTS_DIR"].flatMap { productsDir in
 			if let fileURL = NSURL.fileURLWithPath(productsDir, isDirectory: true) {
 				return success(fileURL)
 			} else {
 				return failure(CarthageError.ParseError(description: "expected file URL for built products directory, got \(productsDir)").error)
 			}
 		}
-}
-
-/// Calculates a URL against `BUILT_PRODUCTS_DIR` using a build setting that
-/// represents a relative path.
-private func URLWithPathRelativeToBuildProductsDirectory(pathSettingName: String, settings: Dictionary<String, String>) -> ColdSignal<NSURL> {
-	return URLToBuildProductsDirectory(settings)
-		// TODO: This should be a zip.
-		.combineLatestWith(valueForBuildSetting(pathSettingName, settings))
-		.map { (productsDirURL, path) in productsDirURL.URLByAppendingPathComponent(path) }
-}
-
-/// Determines where the executable for the product of the given scheme will
-/// exist, when built with the given settings.
-private func URLToBuiltExecutable(settings: Dictionary<String, String>) -> ColdSignal<NSURL> {
-	return URLWithPathRelativeToBuildProductsDirectory("EXECUTABLE_PATH", settings)
-}
-
-/// Determines where the build product for the given scheme will exist, when
-/// built with the given settings.
-private func URLToBuiltProduct(settings: Dictionary<String, String>) -> ColdSignal<NSURL> {
-	return URLWithPathRelativeToBuildProductsDirectory("WRAPPER_NAME", settings)
-}
-
-/// Determines the relative path (from the build folder) where the modules of a
-/// bundle will exist, when built with the given settings.
-///
-/// If the product does not build modules, the returned signal will complete
-/// without sending any values.
-private func pathToModulesFolder(settings: Dictionary<String, String>) -> ColdSignal<String> {
-	return valueForBuildSetting("CONTENTS_FOLDER_PATH", settings)
-		// TODO: This should be a zip.
-		.combineLatestWith(valueForBuildSetting("PRODUCT_MODULE_NAME", settings))
-		.catch { _ in .empty() }
-		.map { (contentsPath, moduleName) -> String in
-			return contentsPath.stringByAppendingPathComponent("Modules").stringByAppendingPathComponent(moduleName).stringByAppendingPathExtension("swiftmodule")!
-		}
+	}
+
+	/// Attempts to determine the URL to the built executable.
+	public var executableURL: Result<NSURL> {
+		return builtProductsDirectoryURL.flatMap { builtProductsURL in
+			return self["EXECUTABLE_PATH"].map { executablePath in
+				return builtProductsURL.URLByAppendingPathComponent(executablePath)
+			}
+		}
+	}
+
+	/// Attempts to determine the URL to the built product's wrapper.
+	public var wrapperURL: Result<NSURL> {
+		return builtProductsDirectoryURL.flatMap { builtProductsURL in
+			return self["WRAPPER_NAME"].map { wrapperName in
+				return builtProductsURL.URLByAppendingPathComponent(wrapperName)
+			}
+		}
+	}
+
+	/// Attempts to determine the relative path (from the build folder) where
+	/// the Swift modules for the built product will exist.
+	///
+	/// If the product does not build any modules, `nil` will be returned.
+	private var relativeModulesPath: Result<String?> {
+		if let moduleName = self["PRODUCT_MODULE_NAME"].value() {
+			return self["CONTENTS_FOLDER_PATH"].map { contentsPath in
+				return contentsPath.stringByAppendingPathComponent("Modules").stringByAppendingPathComponent(moduleName).stringByAppendingPathExtension("swiftmodule")!
+			}
+		} else {
+			return success(nil)
+		}
+	}
 }
 
 /// Finds the built product for the given settings, then copies it (preserving


	/// Attempts to parse a product type from a string returned from
	/// `xcodebuild`.
	static func fromString(string: String) -> Result<ProductType> {
		switch string {
		case "com.apple.product-type.framework":
			return success(.Framework)

}

/// Describes the type of product built by an Xcode target.
public enum ProductType: Equatable {
	/// A framework bundle.
	case Framework

