Carthage: No version file create for objc only framework

Created on 10 Jan 2019  路  22Comments  路  Source: Carthage/Carthage

  • carthage install method: [ ] .pkg, [ ] homebrew, [X] source
  • which carthage: /usr/local/bin/cartage
  • carthage version: current master (https://github.com/Carthage/Carthage/commit/c31b3f5261d9ac1927ad26cb0543f9202bec1a39)
  • xcodebuild -version: Xcode 10.1
    Build version 10B61
  • Are you using --no-build? n
  • Are you using --no-use-binaries? y
  • Are you using --use-submodules? y
  • Are you using --cache-builds? y
  • Are you using --new-resolver? n

Cartfile

github "Openium/SwiftiumKit" ~> 1.3
github "github/Archimedes" ~> 1.0

Carthage Output

carthage update --platform iOS --no-use-binaries --use-submodules
*** Fetching Archimedes
*** Fetching SwiftiumKit
*** Checking out SwiftiumKit at "v1.4.0"
*** Checking out Archimedes at "1.1.5"
*** xcodebuild output can be found in /var/folders/7v/dfg0p2vj1z58n65d99l2n1lh0000gp/T/carthage-xcodebuild.vPJM7m.log
*** Building scheme "Archimedes iOS" in Archimedes.xcworkspace
*** Building scheme "SwiftiumKit" in SwiftiumKit.xcodeproj

Actual outcome
Carthage did not produce the .Archimedes.version file in Carthage/Build:

$ ls -a Carthage/Build/
.                    ..                   .SwiftiumKit.version iOS

I think it is related to https://github.com/Carthage/Carthage/pull/2585, which adds swiftToolchainVersion to the version file, by looking for swift version in Dep.framework/Headers/Dep-Swift.h
And thus, make CI job (https://travis-ci.org/Carthage/Carthage/jobs/477319552) of the https://github.com/Carthage/Carthage/issues/2400 PR I made on "shared cache" fail after last update to current master

The origin is there is no Dep-Swift.h header generated for "ObjC only" frameworks (as github/Archimedes is)

Expected outcome
Carthage should generate the .Archimedes.version file

bug

All 22 comments

Carthage also looks at dSYMs to generate the version file.

You're right:

https://github.com/Carthage/Carthage/blob/master/Source/CarthageKit/VersionFile.swift#L151-L152

But frameworkSwiftVersion() is also called here without the check in dSYM https://github.com/Carthage/Carthage/blob/master/Source/CarthageKit/VersionFile.swift#L414-L415, which is executed after Archimedes.framework build

@kenji21 Do you want to take a shot at improving this?

This is a bit curious though as for example I'm sure the version file is generated for https://github.com/swisspol/GCDWebServer

It is also the case for GCDWebServer, got it printed:

*** Building scheme "Archimedes iOS" in Archimedes.xcworkspace
error Unable to determine framework Swift version: Could not derive version from header file. for dep : (dependency: github "github/Archimedes", version: "1.1.5")
*** Building scheme "GCDWebServers (iOS)" in GCDWebServer.xcodeproj
error Unable to determine framework Swift version: Could not derive version from header file. for dep : (dependency: github "swisspol/GCDWebServer", version: "3.5.2")

with these changes:

diff --git a/Source/CarthageKit/Xcode.swift b/Source/CarthageKit/Xcode.swift
index cf3cca07..5536ec90 100644
--- a/Source/CarthageKit/Xcode.swift
+++ b/Source/CarthageKit/Xcode.swift
@@ -893,7 +893,10 @@ public func buildInDirectory( // swiftlint:disable:this function_body_length
                                        buildProducts: urls,
                                        rootDirectoryURL: rootDirectoryURL
                                        )
-                                       .flatMapError { _ in .empty }
+                                       .flatMapError { e in
+                                               print("error \(e) for dep : \(dependency)")
+                                               return .empty
+                                        }
                        }

The error is "caught" with the flatMapError at https://github.com/Carthage/Carthage/blob/master/Source/CarthageKit/Xcode.swift#L896

I replaced the error of my previous comment to look for version in dSYM:

diff --git a/Source/CarthageKit/Xcode.swift b/Source/CarthageKit/Xcode.swift
index cf3cca07..9af0fde3 100644
--- a/Source/CarthageKit/Xcode.swift
+++ b/Source/CarthageKit/Xcode.swift
@@ -59,7 +59,8 @@ internal func frameworkSwiftVersion(_ frameworkURL: URL) -> SignalProducer<Strin
                let contents = String(data: data, encoding: .utf8),
                let swiftVersion = parseSwiftVersionCommand(output: contents)
                else {
-                       return SignalProducer(error: .unknownFrameworkSwiftVersion(message: "Could not derive version from header file."))
+                       return dSYMSwiftVersion(frameworkURL.appendingPathExtension("dSYM"))
+                       //return SignalProducer(error: .unknownFrameworkSwiftVersion(message: "Could not derive version from header file."))
        }

And it still fails (as expected because these frameworks can't have a swiftVersion):

*** Building scheme "Archimedes iOS" in Archimedes.xcworkspace
error Unable to determine framework Swift version: No version found in dSYM. for dep : (dependency: github "github/Archimedes", version: "1.1.5")
*** Building scheme "GCDWebServers (iOS)" in GCDWebServer.xcodeproj
error Unable to determine framework Swift version: No version found in dSYM. for dep : (dependency: github "swisspol/GCDWebServer", version: "3.5.2")

The compiler for objc is clang:

/usr/bin/xcrun dwarfdump --arch=armv7 --debug-info Carthage/Build/iOS/Archimedes.framework.dSYM/ |grep AT_producer
             AT_producer( "Apple LLVM version 10.0.0 (clang-1000.11.45.5)" )

I think this is a bug that should be fixed.

And for Objective-C-only frameworks, we don't need to validate the compiler version becase ObjC has binary compatibility.

So the frameworkSwiftVersion()function can return ~an empty string~ a constant indicating "NotASwiftFramework"
with this simple code:

diff --git a/Source/CarthageKit/Xcode.swift b/Source/CarthageKit/Xcode.swift
index cf3cca07..27fba7b0 100644
--- a/Source/CarthageKit/Xcode.swift
+++ b/Source/CarthageKit/Xcode.swift
@@ -53,6 +53,10 @@ private func parseSwiftVersionCommand(output: String?) -> String? {
 /// Determines the Swift version of a framework at a given `URL`.
 internal func frameworkSwiftVersion(_ frameworkURL: URL) -> SignalProducer<String, SwiftVersionError> {

+       guard frameworkURL.swiftmoduleURL() != nil else {
+               return SignalProducer(value: "NotASwiftFramework")
+       }
+
        guard
                let swiftHeaderURL = frameworkURL.swiftHeaderURL(),
                let data = try? Data(contentsOf: swiftHeaderURL),

or "PureObjectiveCFramework" for the constant which will be stored in the version file

I agree this is a bug.

If I understand correctly you are suggesting to replace the swift version with a known value?

I think it would be better not to abuse the property and simply create a new one for objc. @ikesyo ?

I'm for creating a new (maybe boolean?) field to determine that a framework is including Swift or not.

Or maybe a property indicating what language the binary was produced from so that in the unlikely event of a third language we don't have to introduce another flag?

On a second thought @ikesyo is right and we don't need to know the language but just if it contains swift or not.

This bug seems to be causing bootstrap to fail when referencing a binary objc framework in Cartfile. Is a fix already in progress I should wait for?

I'm having the same error trying to build my project using Swift 5.0 compiler and Carthage master built locally. (803941b8)

Apple Swift version 5.0 (swiftlang-1001.0.45.7 clang-1001.0.37.7)
Target: x86_64-apple-darwin18.2.0
ABI version: 0.6
carthage build

*** Downloading binary-only framework Crashlytics at "https://building42.github.io/Specs/Carthage/iOS/Crashlytics.json"
*** xcodebuild output can be found in /var/folders/w4/qv7tltnn5bn0jw2r2grrkk3r0000gn/T/carthage-xcodebuild.m7Gmcs.log
Unable to determine framework Swift version: Could not derive version from header file.

The log file is empty.

Is there a workaround for this ?

Carthage derives the version looking at聽-Swift.h header or via the dSYM.

I just tried to implement the fix by adding a "isSwiftFramework" boolean to the CachedFramework structure, but Codable don't allow to specify a default value for this boolean when Decoding json, so its initialiser returns nil and make some tests failing

First, I added a check for version file presence of test "should build for one platform" which build Archimedes framework: https://github.com/Carthage/Carthage/compare/master...openium:fix-2677-no-version-objc-frameworks#diff-21b1859b20014a70776faa1516cd5064R347

And then added the frameworkSwiftVersionIfIsSwiftFramework function return an Optional when generating the version file:
https://github.com/openium/Carthage/tree/fix-2677-no-version-objc-frameworks

Do you think it's ready to merge like this ?

@kenji21 鈥撀燱ould this work for any Objective-C projects that I've checked out and run carthage build --no-skip-current and carthage archive on, then released a tag with the newly created binary framework?

Currently encountering the following when doing the above, given that third-party-vendor.xcodeproj is entirely Objective-C and therefore has no identified SWIFT_VERSION:

*** Downloading third-party-vendor.framework binary at "2.0.0"
***  Skipped installing third-party-vendor.framework binary due to the error:
    "Unable to determine framework Swift version: Could not derive version from header file."

    Falling back to building from the source

Trying to avoid building every time for a framework that rarely changes (or that we rarely take updates from).

I just tried with SimulatorStatusMagic:

git clone https://github.com/shinydevelopment/SimulatorStatusMagic

~/github/Carthage/.build/debug/carthage build --no-skip-current
*** xcodebuild output can be found in /var/folders/7v/dfg0p2vj1z58n65d99l2n1lh0000gp/T/carthage-xcodebuild.IB5Cy9.log
*** Building scheme "SimulatorStatusMagiciOS" in SimulatorStatusMagic.xcodeproj

ls Carthage/Build/
.SimulatorStatusMagic.version  iOS/

cat Carthage/Build/.SimulatorStatusMagic.version
{
  "Mac" : [

  ],
  "watchOS" : [

  ],
  "tvOS" : [

  ],
  "commitish" : "2.4.1",
  "iOS" : [
    {
      "name" : "SimulatorStatusMagiciOS",
      "hash" : "d0256cbe7099312e2603049e4ec453df899daab9c21e02ab44773382837f96b2"
    }
  ]
}

~/github/Carthage/.build/debug/carthage archive
*** Found Carthage/Build/iOS/SimulatorStatusMagiciOS.framework
*** Found Carthage/Build/iOS/SimulatorStatusMagiciOS.framework.dSYM
*** Found Carthage/Build/iOS/E31C631B-7A08-3228-B279-5AEE5D379A23.bcsymbolmap
*** Found Carthage/Build/iOS/3419D63A-0D5C-30CD-9B79-B78B3F45852B.bcsymbolmap
*** Found Carthage/Build/iOS/FFC52F13-D9DE-3497-B90A-1E214A0CF77B.bcsymbolmap
*** Found Carthage/Build/iOS/F14B1F88-403F-30A3-B9A6-BB81876ED115.bcsymbolmap
*** Created SimulatorStatusMagiciOS.framework.zip

The zip doesn't contains the .SimulatorStatusMagic.version file, and I don't know if it is expected or not

@gdoublev do you know an objc only framework with zip uploaded on GitHub release (just to add it to a sample Cartfile and check) ?

@kenji21 鈥撀爊o, but I forked the SimulatorStatusMagiciOS repository that you mentioned and archived a binary using the version of Carthage from your fix branch (https://github.com/openium/Carthage/tree/fix-2677-no-version-objc-frameworks) and was able to download that Objective-C framework binary without rebuilding the project, so that's awesome!

$ carthage update --platform iOS
Please update to the latest Carthage version: 0.32.0. You currently are on 0.31.2
*** Cloning SimulatorStatusMagic
*** Checking out SimulatorStatusMagic at "2.4.2"
*** xcodebuild output can be found in /var/folders/zw/kcs4m2kd7j386jn0rdq_7sdc0000gn/T/carthage-xcodebuild.xBH6fr.log
*** Downloading SimulatorStatusMagic.framework binary at "2.4.2"
$

Nice work! 馃憤

@kenji21 鈥撀燼ny plans to PR your fix?

As is it merged in 0.33, maybe this issue can be closed

Was this page helpful?
0 / 5 - 0 ratings