Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for additional framework extensions #2843

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions Source/CarthageKit/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ public struct Constants {
/// The relative path to a project's Cartfile.resolved.
public static let resolvedCartfilePath = "Cartfile.resolved"

/// The text that needs to exist in a GitHub Release asset's name, for it to be
/// tried as a binary framework.
public static let binaryAssetPattern = ".framework"

/// MIME types allowed for GitHub Release assets, for them to be considered as
/// binary frameworks.
public static let binaryAssetContentTypes = ["application/zip", "application/octet-stream"]
Expand Down
76 changes: 76 additions & 0 deletions Source/CarthageKit/ProductExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import Foundation
import Result
import XCDBLD
import ReactiveSwift

/// Describes the type of packages file extensions
public enum ProductExtension: String, CaseIterable {
/// A .framework package.
case framework = "framework"

/// A .action package.
case action = "action"

/// A .bundle package.
case bundle = "bundle"

/// A .kext package.
case kext = "kext"

/// A .mdimporter package.
case mdimporter = "mdimporter"

/// A .metallib package.
case metallib = "metallib"

/// A .plugin package.
case plugin = "plugin"

/// A .prefPane package.
case prefPane = "prefPane"

/// A .qlgenerator package.
case qlgenerator = "qlgenerator"

/// A .saver package.
case saver = "saver"

/// A .xpc package.
case xpc = "xpc"

public static let supportedExtensions: [ProductExtension] = ProductExtension.allCases

/// Attempts to parse a product extension
public static func from(string: String) -> Result<ProductExtension, CarthageError> {
return Result(self.init(rawValue: string), failWith: .parseError(description: "unexpected product extension type \"\(string)\""))
}

/// Check if string is one of the supported extensions
public static func isSupportedExtension(_ string: String) -> Bool {
return (self.init(rawValue: string) != nil)
}
}

/// Enumerate all supported platforms and framework extensions (e.g., .framework, .qlgenerator, etc.).
/// Yield `path-to-framework`, `TARGET_NAME.EXT`
public func enumerateSupportedFrameworks(
target: String,
inDirectory: URL,
isBuildDirectory: Bool = false,
allowedPlatforms: [Platform] = Platform.supportedPlatforms,
allowedExtensions: [ProductExtension] = ProductExtension.supportedExtensions
) -> SignalProducer<(path: URL, name: String), CarthageError> {

return SignalProducer(allowedPlatforms)
.filterMap { platform -> SignalProducer<(path: URL, name: String), CarthageError> in
let absoluteURL = inDirectory
.appendingPathComponent(isBuildDirectory ? platform.rawValue : platform.relativePath)
.resolvingSymlinksInPath()
.appendingPathComponent(target, isDirectory: false)
return SignalProducer(allowedExtensions)
.map { absoluteURL.appendingPathExtension($0.rawValue) }
.filter { FileManager.default.fileExists(atPath: $0.path) }
.map { ($0.deletingLastPathComponent(), $0.lastPathComponent) }
}
.flatten(.merge)
}
8 changes: 8 additions & 0 deletions Source/CarthageKit/ProductType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ public enum ProductType: String {

/// A unit test bundle.
case testBundle = "com.apple.product-type.bundle.unit-test"

/// A generic bundle, e.g., QLGenerator.
case genericBundle = "com.apple.product-type.bundle"

/// Attempts to parse a product type from a string returned from
/// `xcodebuild`.
public static func from(string: String) -> Result<ProductType, CarthageError> {
return Result(self.init(rawValue: string), failWith: .parseError(description: "unexpected product type \"\(string)\""))
}

/// Returns true for .framework or .genericBundle
public func archivable() -> Bool {
relikd marked this conversation as resolved.
Show resolved Hide resolved
return self == .framework || self == .genericBundle;
}
}
17 changes: 11 additions & 6 deletions Source/CarthageKit/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ public final class Project { // swiftlint:disable:this type_body_length
frameworkNameAndExtension: String
) -> Result<URL, CarthageError> {
guard let lastComponent = URL(string: frameworkNameAndExtension)?.pathExtension,
lastComponent == "framework" else {
ProductExtension.isSupportedExtension(lastComponent) else {
return .failure(.internalError(description: "\(frameworkNameAndExtension) is not a valid framework identifier"))
}

Expand Down Expand Up @@ -777,10 +777,15 @@ public final class Project { // swiftlint:disable:this type_body_length
.flatMap(.concat) { release -> SignalProducer<URL, CarthageError> in
return SignalProducer<Release.Asset, CarthageError>(release.assets)
.filter { asset in
if asset.name.range(of: Constants.Project.binaryAssetPattern) == nil {
return false
var onionExt = (asset.name as NSString)
while onionExt.pathExtension != "" {
let ext = onionExt.pathExtension
if ProductExtension.isSupportedExtension(ext) {
return Constants.Project.binaryAssetContentTypes.contains(asset.contentType)
}
onionExt = (onionExt.deletingPathExtension as NSString)
}
return Constants.Project.binaryAssetContentTypes.contains(asset.contentType)
return false
}
.flatMap(.concat) { asset -> SignalProducer<URL, CarthageError> in
let fileURL = fileURLToCachedBinary(dependency, release, asset)
Expand Down Expand Up @@ -1432,12 +1437,12 @@ func platformForFramework(_ frameworkURL: URL) -> SignalProducer<Platform, Carth

/// Sends the URL to each framework bundle found in the given directory.
internal func frameworksInDirectory(_ directoryURL: URL) -> SignalProducer<URL, CarthageError> {
return filesInDirectory(directoryURL, kUTTypeFramework as String)
return filesInDirectory(directoryURL, kUTTypeBundle as String)
.filter { !$0.pathComponents.contains("__MACOSX") }
.filter { url in
// Skip nested frameworks
let frameworksInURL = url.pathComponents.filter { pathComponent in
return (pathComponent as NSString).pathExtension == "framework"
return ProductExtension.isSupportedExtension((pathComponent as NSString).pathExtension)
}
return frameworksInURL.count == 1
}.filter { url in
Expand Down
41 changes: 21 additions & 20 deletions Source/CarthageKit/VersionFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,24 +83,23 @@ struct VersionFile: Codable {
for cachedFramework: CachedFramework,
platform: Platform,
binariesDirectoryURL: URL
) -> URL {
return binariesDirectoryURL
.appendingPathComponent(platform.rawValue, isDirectory: true)
.resolvingSymlinksInPath()
.appendingPathComponent("\(cachedFramework.name).framework", isDirectory: true)
) -> SignalProducer<URL, CarthageError> {
return enumerateSupportedFrameworks(target: cachedFramework.name,
inDirectory: binariesDirectoryURL,
isBuildDirectory: true,
allowedPlatforms: [platform])
.filterMap { $0.path.appendingPathComponent($0.name, isDirectory: true) }
}

func frameworkBinaryURL(
for cachedFramework: CachedFramework,
platform: Platform,
binariesDirectoryURL: URL
) -> URL {
return frameworkURL(
for: cachedFramework,
platform: platform,
binariesDirectoryURL: binariesDirectoryURL
)
.appendingPathComponent("\(cachedFramework.name)", isDirectory: false)
) -> SignalProducer<URL, CarthageError> {
return frameworkURL(for: cachedFramework,
platform: platform,
binariesDirectoryURL: binariesDirectoryURL)
.filterMap { binaryURL($0).value }
}

/// Sends the hashes of the provided cached framework's binaries in the
Expand All @@ -111,13 +110,14 @@ struct VersionFile: Codable {
binariesDirectoryURL: URL
) -> SignalProducer<String?, CarthageError> {
return SignalProducer<CachedFramework, CarthageError>(cachedFrameworks)
.flatMap(.concat) { cachedFramework -> SignalProducer<String?, CarthageError> in
let frameworkBinaryURL = self.frameworkBinaryURL(
.flatMap(.concat) { cachedFramework -> SignalProducer<URL, CarthageError> in
self.frameworkBinaryURL(
for: cachedFramework,
platform: platform,
binariesDirectoryURL: binariesDirectoryURL
)

}
.flatMap(.concat) { frameworkBinaryURL -> SignalProducer<String?, CarthageError> in
return hashForFileAtURL(frameworkBinaryURL)
.map { hash -> String? in
return hash
Expand All @@ -141,13 +141,14 @@ struct VersionFile: Codable {
localSwiftVersion: String
) -> SignalProducer<Bool, CarthageError> {
return SignalProducer<CachedFramework, CarthageError>(cachedFrameworks)
.flatMap(.concat) { cachedFramework -> SignalProducer<Bool, CarthageError> in
let frameworkURL = self.frameworkURL(
.flatMap(.concat) { cachedFramework -> SignalProducer<URL, CarthageError> in
self.frameworkURL(
for: cachedFramework,
platform: platform,
binariesDirectoryURL: binariesDirectoryURL
)

}
.flatMap(.concat) { frameworkURL -> SignalProducer<Bool, CarthageError> in
if !isSwiftFramework(frameworkURL) {
return SignalProducer(value: true)
} else {
Expand Down Expand Up @@ -421,8 +422,8 @@ public func createVersionFileForCommitish(
frameworkName: frameworkName,
frameworkSwiftVersion: frameworkSwiftVersion)
let details = SignalProducer<FrameworkDetail, CarthageError>(value: frameworkDetail)
let binaryURL = url.appendingPathComponent(frameworkName, isDirectory: false)
return SignalProducer.zip(hashForFileAtURL(binaryURL), details)
let binary = binaryURL(url).value ?? url.appendingPathComponent(frameworkName, isDirectory: false)
return SignalProducer.zip(hashForFileAtURL(binary), details)
}
}
.reduce(into: platformCaches) { (platformCaches: inout [String: [CachedFramework]], values: (String, FrameworkDetail)) in
Expand Down
10 changes: 9 additions & 1 deletion Source/CarthageKit/Xcode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ internal enum FrameworkType {

/// A static framework.
case `static`

/// One of: action, bundle, kext, mdimporter, metallib, plugin, prefPane, qlgenerator, saver, xpc
case generic

init?(productType: ProductType, machOType: MachOType) {
switch (productType, machOType) {
Expand All @@ -276,6 +279,9 @@ internal enum FrameworkType {

case (.framework, .staticlib):
self = .static

case (.genericBundle, .bundle):
self = .generic

case _:
return nil
Expand Down Expand Up @@ -837,7 +843,9 @@ public func createDebugInformation(_ builtProductURL: URL) -> SignalProducer<Tas

let executableName = builtProductURL.deletingPathExtension().lastPathComponent
if !executableName.isEmpty {
let executable = builtProductURL.appendingPathComponent(executableName).path
let executable = binaryURL(builtProductURL).recover(
builtProductURL.appendingPathComponent(executableName)
).path
let dSYM = dSYMURL.path
let dsymutilTask = Task("/usr/bin/xcrun", arguments: ["dsymutil", executable, "-o", dSYM])

Expand Down
10 changes: 6 additions & 4 deletions Source/carthage/Archive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,21 @@ public struct ArchiveCommand: CommandProtocol {
let formatting = options.colorOptions.formatting

let frameworks: SignalProducer<[String], CarthageError>
let directoryURL = URL(fileURLWithPath: options.directoryPath, isDirectory: true)

if !options.frameworkNames.isEmpty {
frameworks = .init(value: options.frameworkNames.map {
return ($0 as NSString).appendingPathExtension("framework")!
return enumerateSupportedFrameworks(target: $0, inDirectory: directoryURL)
.map{ $0.name }.first()?.value ?? $0
})
} else {
let directoryURL = URL(fileURLWithPath: options.directoryPath, isDirectory: true)
frameworks = buildableSchemesInDirectory(directoryURL, withConfiguration: "Release")
.flatMap(.merge) { scheme, project -> SignalProducer<BuildSettings, CarthageError> in
let buildArguments = BuildArguments(project: project, scheme: scheme, configuration: "Release")
return BuildSettings.load(with: buildArguments)
}
.flatMap(.concat) { settings -> SignalProducer<String, CarthageError> in
if let wrapperName = settings.wrapperName.value, settings.productType.value == .framework {
if let wrapperName = settings.wrapperName.value, settings.productType.value?.archivable() ?? false {
relikd marked this conversation as resolved.
Show resolved Hide resolved
return .init(value: wrapperName)
} else {
return .empty
Expand Down Expand Up @@ -102,7 +104,7 @@ public struct ArchiveCommand: CommandProtocol {
let foundFrameworks = paths
.lazy
.map { ($0 as NSString).lastPathComponent }
.filter { $0.hasSuffix(".framework") }
.filter { ProductExtension.isSupportedExtension(($0 as NSString).pathExtension) }

if Set(foundFrameworks) != Set(frameworks) {
let error = CarthageError.invalidArgument(
Expand Down
6 changes: 3 additions & 3 deletions Source/carthage/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ internal struct ProjectEventSink {

case let .downloadingBinaries(dependency, release):
carthage.println(formatting.bullets + "Downloading " + formatting.projectName(dependency.name)
+ ".framework binary at " + formatting.quote(release))
+ " binary at " + formatting.quote(release))

case let .skippedDownloadingBinaries(dependency, message):
carthage.println(formatting.bullets + "Skipped downloading " + formatting.projectName(dependency.name)
+ ".framework binary due to the error:\n\t" + formatting.quote(message))
+ " binary due to the error:\n\t" + formatting.quote(message))

case let .skippedInstallingBinaries(dependency, error):
let output = """
\(formatting.bullets) Skipped installing \(formatting.projectName(dependency.name)).framework binary due to the error:
\(formatting.bullets) Skipped installing \(formatting.projectName(dependency.name)) binary due to the error:
\(formatting.quote(String(describing: error)))

Falling back to building from the source
Expand Down
24 changes: 24 additions & 0 deletions Tests/CarthageKitTests/XcodeSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,30 @@ class XcodeSpec: QuickSpec {
expect(path).to(beExistingDirectory())
}
}

it("should build frameworks with different extensions") {
let multipleSubprojects = "SampleOtherFrameworkTypes"
let _directoryURL = Bundle(for: type(of: self)).url(forResource: multipleSubprojects, withExtension: nil)!

let result = buildInDirectory(_directoryURL, withOptions: BuildOptions(configuration: "Debug"), rootDirectoryURL: directoryURL)
.ignoreTaskData()
.on(value: { project, scheme in // swiftlint:disable:this end_closure
NSLog("Building scheme \"\(scheme)\" in \(project)")
})
.wait()

expect(result.error).to(beNil())

let expectedFrameworks = [
("TestQuickLookPlugin", "qlgenerator"),
("TestScreensaverPlugin", "saver"),
]

for (name, ext) in expectedFrameworks {
let path = buildFolderURL.appendingPathComponent("Mac").appendingPathComponent(name).appendingPathExtension(ext).path
expect(path).to(beExistingDirectory())
}
}

it("should skip projects without shared framework schems") {
let dependency = "SchemeDiscoverySampleForCarthage"
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion script/copy-fixtures
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fi
current=$(cd "$(dirname $0)" && pwd)
destination="$1"

for fixture in "carthage-fixtures-ReactiveCocoaLayout-master" "CartfileOnly" "CartfilePrivateOnly" "NoCartfile" "SchemeDiscoverySampleForCarthage-0.2" "Swell-0.5.0" "SampleMultipleSubprojects" "SampleGitSubmodule" "DependencyTest" "WorkspaceWithDependency" "DynamicAndStatic" "WorkspaceWithDependency" "FilterBogusFrameworks" "NoSharedSchemesTest"
for fixture in "carthage-fixtures-ReactiveCocoaLayout-master" "CartfileOnly" "CartfilePrivateOnly" "NoCartfile" "SchemeDiscoverySampleForCarthage-0.2" "Swell-0.5.0" "SampleMultipleSubprojects" "SampleGitSubmodule" "DependencyTest" "WorkspaceWithDependency" "DynamicAndStatic" "WorkspaceWithDependency" "FilterBogusFrameworks" "NoSharedSchemesTest" "SampleOtherFrameworkTypes"
do
rm -Rf "$destination/__MACOSX"
rm -Rf "$destination/$fixture"
Expand Down