trinhngocthuyen / cocoapods-spm

A CocoaPods plugin to add SPM dependencies to CocoaPods-based projects
MIT License
44 stars 7 forks source link

[Bug] SPM package dependency with hyphenated name cannot be linked to test targets #87

Closed kalub92 closed 3 months ago

kalub92 commented 5 months ago

What happened?

When attempting to compile the test target for my project, I get the following errors:

Error 1 (SwiftDriver error building DependencyPackage-iOS):

Module name "DependencyPackage-iOS" is not a valid identifier

Error 2 (building PackageA):

No such module 'DependencyPackage_iOS'

It seems that the hyphenated DependencyPackage-iOS is an invalid module name, but I notice in PackageA that when they've been importing this dependency as DependencyPackage_iOS with an underscore. Does cocoapods-spm account for this?

I looked up the Module name is not a valid identifier error and saw that using hyphens in module names is not allowed but that there is a C99 modifier that can be used (e.g. c99extidentifier) when linking frameworks to allow this: $(PRODUCT_NAME:c99extidentifier).

Wondering if this is part of the issue?

CocoaPods environment

Stack

   CocoaPods : 1.13.0
        Ruby : ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21]
    RubyGems : 3.3.3
        Host : macOS 14.3.1 (23D60)
       Xcode : 15.0 (15A240d)
         Git : git version 2.44.0
Ruby lib dir : /Users/cstultz/.rvm/rubies/ruby-3.1.0/lib
Repositories : trunk - CDN - https://cdn.cocoapods.org/

Installation Source

Executable Path: /Users/cstultz/.rvm/gems/ruby-3.1.0/bin/pod

Plugins

cocoapods-deintegrate : 1.0.5
cocoapods-plugins     : 1.0.0
cocoapods-pod-linkage : 0.0.1
cocoapods-search      : 1.0.1
cocoapods-spm         : 0.1.4
cocoapods-trunk       : 1.6.0
cocoapods-try         : 1.2.0
slather               : 2.8.0

Podfile

require '../tools/cocoapods-utilities/Utilities.rb'
plugin 'cocoapods-pod-linkage'
plugin 'cocoapods-spm'

# Check for bundler
verify_bundler_was_used()

workspace 'FooWorkspace'
use_frameworks! :linkage => :dynamic
inhibit_all_warnings!
platform :ios, '16.0'
source 'https://cdn.cocoapods.org/'

install! 'cocoapods', :disable_input_output_paths => true

def test_pods
  pod_module 'TestKit'
end

def spm_pods
  spm_pkg "PackageA",
          :url => "https://github.com/developer/PackageA.git",
          :version => "2.3.2",
          :linkage => :static,
          :linking => {
            # Prefer adding products to the "Link Binary With Libraries" section
            :use_default_xcode_linking => true,
            :linker_flags => ["-ld64"]
          }

  spm_pkg "PackageB",
          :url => "https://github.com/developer/PackageB.git",
          :version => "1.4.1",
          :linkage => :static
end

# You should NOT add or remove pods directly to the targets below! Use the methods above.
abstract_target 'Foo' do

  spm_pods

  target 'Bar' do
    project 'FooWorkspace', 'DebugTeam' => :debug, 'UnitTests' => :debug
  end

  target 'Bar Tests' do
    project 'FooWorkspace, 'DebugTeam' => :debug, 'UnitTests' => :debug
    test_pods
    pod 'Nimble', '~> 11.2.0'
    pod 'Quick', '~> 6.1.0'
    pod 'DGCharts', '5.0'
  end

end

Anything else?

No response

trinhngocthuyen commented 4 months ago

@kalub92 Actually, cocoapods-spm has nothing to do with the module name. It's up to the package author to manage it. Linking frameworks/libraries/packages does not intervene in how the module name is treated.

If the target name is foo-ios, for example, then the module name is foo_ios.

let package = Package(
  ...
  targets: [
    .target(name: "foo-ios") // <-- HERE
  ]
)

(1) Therefore, please first make sure you're using the correct module name in Swift code: import foo_ios

(2) In case you're specifying the correct module name foo_ios and it's still saying No such module 'foo_ios', it might be a linking issue (potentially caused by this plugin). In this case, is it possible to have a demo to reproduce the issue? (you can use the example project for convenience)

Thank you!

kalub92 commented 4 months ago

Thanks for the clarification! The package is defined with the name PackageName-iOS and shows up in Xcode like this: import PackageName_iOS but I'm still seeing the No such module... errors.

I'll try to get a sample project to you tomorrow to investigate! Thank you!

kalub92 commented 4 months ago

I was able to reproduce this! I created a new sample module called SamplePackageB-iOS (link) where the only difference is a hyphenated name. I created a branch off of SamplePackageA (on this branch) that consumes this package instead of the previous module, SamplePackageB.

I forked cocoapods-spm and the example project demonstrates this issue on this branch: kalub92/hyphenated_module_name_bug

First, I had to actually git reset back to https://github.com/trinhngocthuyen/cocoapods-spm/commit/d6e0140a91f37fb69948ac8f215a1fd8a3594201 before the dylib changes you made in order for pod install to work... Might be a separate issue, but anyway – I am consuming my module like so in an abstract target (like the app I'm trying to resolve this for):

FYI, I removed a few dependencies because Orcam was experiencing a compilation error.

def spm_pods
  spm_pkg "SamplePackageA",
          :git => "https://github.com/kalub92/SamplePackageA.git",
          :branch => "withHyphenatedDependency",
          :linkage => :static,
          :linking => {
            # Prefer adding products to the "Link Binary With Libraries" section
            :use_default_xcode_linking => true,
            :linker_flags => ["-ld_classic"]
          }
end

abstract_target 'Example' do

  spm_pods

  target "EX" do
    pod "Logger", :path => "LocalPods/Logger"
    # pod "CommonUI", :path => "LocalPods/CommonUI"
    # pod "Services", :path => "LocalPods/Services"

    spm_pkg "SnapKit",
            :url => "https://github.com/SnapKit/SnapKit.git",
            :version => "5.7.1",
            :products => ["SnapKit-Dynamic"]
    spm_pkg "SwiftUIX", :git => "https://github.com/SwiftUIX/SwiftUIX.git", :tag => "0.1.9"
    spm_pkg "SwiftyBeaver", :git => "https://github.com/SwiftyBeaver/SwiftyBeaver.git", :tag => "2.0.0"
    spm_pkg "opentelemetry-swift",
            :git => "https://github.com/open-telemetry/opentelemetry-swift.git",
            :branch => "main",
            :products => ["OpenTelemetrySdk"]
    spm_pkg "GoogleMaps",
            :git => "https://github.com/googlemaps/ios-maps-sdk.git",
            :version => "8.4.0",
            :products => ["GoogleMaps", "GoogleMapsBase", "GoogleMapsCore"]
    spm_pkg "DebugKit", :path => "LocalPackages/debug-kit"
  end

  target "EXTests" do

  end
end

The pod install command executes successfully, but when I attempt to run either the EX or EX Tests targets, I see this error related to the [CP] Copy Pods Resources build phase:

Screenshot 2024-05-08 at 3 36 30 PM

To see if I could get around that, I deleted that phase manually from both EX and EX Tests targets, cleared out derived data, and cleaned the build folder.

When attempting to compile EX target, I get a duplicate symbols error (gist): Screenshot 2024-05-08 at 4 01 11 PM

When attempting to compile the EX Tests target, I see that mentioned hyphenated No such module... naming issue: Screenshot 2024-05-08 at 3 35 38 PM

trinhngocthuyen commented 4 months ago

Thanks for the reproduction. I managed to make it work on this branch: demo/87. Kindly check this commit for the diff.

Highlights of the change:

Tip: run make ex.install for the shortcut to pod install together with some prebuilt macros (Orcam, MacroCodableKit)

kalub92 commented 4 months ago

I made the changes you suggested:

This does resolve the No such module/Module name is not a valid identifier error, but now I'm seeing this in my project at work:

Missing required module 'ThirdPartyAdapter'

Which is a recursive dependency of the main SPM package I'm trying to import:

SamplePackageA
     ↳ SamplePackageB-iOS
          ↳ ThirdPartyAdapter

I'm working on reproducing this in the sample app project, but haven't had luck yet...

kalub92 commented 4 months ago

I was able to reproduce it in the sample project: link

After closer inspection, I made changes to the following modules to more closely follow the pattern established in our app:

When compiling, I first see this referencing the [CP] Copy Pods Resources build phase: image

Then, after removing that from EX/EX Tests, attempting to compile results in the error I see in my work project: image

trinhngocthuyen commented 4 months ago

I checked out the demo code at the given commit 88f6b6e.

Below is the revision of the packages:

Screenshot 2024-05-11 at 09 51 43

As I mentioned in the earlier comment, the "multiple commands produce..." error is because the same package was added to the main target (which is duplicated). Because this step fails, the subsequent steps might produce false-alarm errors.

Screenshot 2024-05-11 at 09 34 50 Screenshot 2024-05-11 at 09 34 59

Please remove the package from the main target because the package is transitively available when adding it to the Pods project already.

After removing it, the build succeeded from my end.

Screenshot 2024-05-11 at 09 48 41

Do let me know if I missed any steps :)

kalub92 commented 4 months ago

Thanks for the reply! I just removed SamplePackageA from the EX project, which removed it (and SamplePackageAConfig) from Link Binary with Libraries as you'd expect.

Commit: https://github.com/trinhngocthuyen/cocoapods-spm/commit/1fd8bccf77365ce78ac4304a4fd380bca82c1e19

And I don't get the Multiple commands produce... error anymore however, after cleaning the project and deleting derived data I still see the Missing required module 'ThirdPartyAdapter' error.

image

I should add that I'm using Xcode 15.0 and am updating now to 15.3 to see if this is a possible linker bug in 15.0 that was resolved in a recent update.

Update: I upgraded Xcode to 15.3 and still see the same error.

kalub92 commented 4 months ago

Another update: after reading a bit about others experiencing similar problems and found that they avoid exposing transitive dependencies to the developer by using static linkage and the @_implementationOnly modifier to import that dependency.

Even though this is an undocumented modifier, a few large frameworks (Firebase, for example), has been using this for a while: https://github.com/search?q=repo%3Afirebase%2Ffirebase-ios-sdk+%40+_implementationOnly&type=code

It seems that this was formalized in Swift 6.0: https://github.com/apple/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md

Anyway, I made that change here and it resolved the Missing required module issue and allows the app target and test target to compile and run successfully: https://github.com/kalub92/SamplePackageA/blob/6eea4f37ae112c4fb7efabc1b87183622599f84b/Sources/SamplePackageA/SamplePackageA.swift#L2

Not sure if this helps figure out why the test target isn't able to see ThirdPartyAdapter, but figured I'd share!

trinhngocthuyen commented 4 months ago

@kalub92 It's kind of weird as it actually works fine on my end (with the given commit 1fd8bcc). I'm using Xcode 15.3 though. I also verify on another machine by running on CI. And it works (refer to this job: https://github.com/trinhngocthuyen/exp/actions/runs/9095279979/job/24998161460).

My doubt is that the spm cache is not invalidated. Could you please help remove the .spm.pods directory, run pod installation, then try again? Thank you!

kalub92 commented 4 months ago

~@trinhngocthuyen Strange... I did the following:~

~But I'm still seeing the same error.~

⚠️ Update: Figured out why you were seeing it succeed on your end. See comment below.

kalub92 commented 4 months ago

Discovered why it was working on your end – I had made changes to the SamplePackageA and SamplePackageB-iOS which allowed compilation.

Just updated those packages back to their former settings they should have been at:

The behavior where the app target compiles successfully, but the test target does not is back with these changes.

I think it might have something to do with the .mm & .hpp files in ThirdPartyAdapter? It's an adapter that is supposed to work with the provided .xcframeworks in Frameworks/ThirdPartyVendor/iOS. In my work project, that adapter is only imported in SamplePackageA and not externally to it.

trinhngocthuyen commented 4 months ago

Hi @kalub92,

PR #88 should fix the above issue. Kindly help verify with the latest from main. Below are some more details:

Thank you for your collaborative effort! That means a lot :)

kalub92 commented 4 months ago

Thank you so much! This seems to have resolved the Missing required module... error but now that I've gotten past that I am seeing a bunch of Undefined symbols... errors from that adapter when attempting to link my podspec that takes in these packages as dependencies...

I'm seeing this in my work project, but I'm trying to configure my sample packages so that it reproduces the same behavior in the sample app.

trinhngocthuyen commented 4 months ago

"Undefined symbols..." typically means there's a library/framework/object that was not linked to the binary. May I know the undefined symbols? They should give us a clue about which binary was not linked.

kalub92 commented 4 months ago

I'm not able to share the particular symbols from my work project, but was able to reproduce this issue by adding some methods to the ThirdPartyAdapter to wrap around the provided OneSignal.xcframework:

image

This structure very closely resembles how my work project is configured with this dependency.

Please check out the latest commit for my fork and run the sample app and ensure that SamplePackageB-iOS is updated to its latest commit as well: https://github.com/kalub92/SamplePackageB-iOS/commit/064c476b28cdec893fcce53b09337f2dbc611f8a

trinhngocthuyen commented 4 months ago

Thanks for the update. I found the root cause. Working on a fix.

trinhngocthuyen commented 4 months ago

@kalub92 Would you mind checking again with the latest from main (fixed by https://github.com/trinhngocthuyen/cocoapods-spm/pull/93)? Thank you!

kalub92 commented 4 months ago

I'm on the latest and am seeing a new issue that can be reproduced in the cocoapods-spm example app.

We're pulling in Firebase and a few of its products:

  spm_pkg "Firebase",
          :url => "https://github.com/firebase/firebase-ios-sdk.git",
          :version => "8.14.0",
          :products => ["FirebaseAnalytics", "FirebaseCrashlytics"]

When doing this, we get the following error upon pod install:

Errno::ENOENT - No such file or directory @ rb_check_realpath_internal - .spm.pods/packages/metadata/Promises.json
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/helpers/io.rb:7:in `realpath'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/helpers/io.rb:7:in `realpath'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/helpers/io.rb:7:in `symlink'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/swift/package/project_packages.rb:51:in `block in load'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/swift/package/project_packages.rb:38:in `each'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/swift/package/project_packages.rb:38:in `load'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/swift/package/project_packages.rb:12:in `initialize'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver/recursive_target_resolver.rb:21:in `new'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver/recursive_target_resolver.rb:21:in `project_pkgs'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver/recursive_target_resolver.rb:31:in `block in resolve_recursive_targets'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver/recursive_target_resolver.rb:28:in `each'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver/recursive_target_resolver.rb:28:in `resolve_recursive_targets'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver/recursive_target_resolver.rb:15:in `resolve'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver.rb:24:in `each'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/resolver.rb:24:in `resolve'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/patch/installer.rb:59:in `block in resolve_spm_dependencies'
/Users/cstultz/.rvm/gems/ruby-3.1.0/gems/cocoapods-1.14.3/lib/cocoapods/user_interface.rb:64:in `section'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/patch/installer.rb:57:in `resolve_spm_dependencies'
/Users/cstultz/Documents/GitHub/trinhngocthuyen/lib/cocoapods-spm/patch/installer.rb:13:in `block in <class:Installer>'
/Users/cstultz/.rvm/gems/ruby-3.1.0/gems/cocoapods-1.14.3/lib/cocoapods/installer.rb:162:in `install!'
/Users/cstultz/.rvm/gems/ruby-3.1.0/gems/cocoapods-1.14.3/lib/cocoapods/command/install.rb:52:in `run'
/Users/cstultz/.rvm/rubies/ruby-3.1.0/lib/ruby/gems/3.1.0/gems/claide-1.1.0/lib/claide/command.rb:334:in `run'
/Users/cstultz/.rvm/gems/ruby-3.1.0/gems/cocoapods-1.14.3/lib/cocoapods/command.rb:52:in `run'
/Users/cstultz/.rvm/gems/ruby-3.1.0/gems/cocoapods-1.14.3/bin/pod:55:in `<top (required)>'
/Users/cstultz/.rvm/gems/ruby-3.1.0/bin/pod:25:in `load'
/Users/cstultz/.rvm/gems/ruby-3.1.0/bin/pod:25:in `<main>'
/Users/cstultz/.rvm/gems/ruby-3.1.0/bin/ruby_executable_hooks:22:in `eval'
/Users/cstultz/.rvm/gems/ruby-3.1.0/bin/ruby_executable_hooks:22:in `<main>'
kalub92 commented 4 months ago

Updated my previous comment 👆

kalub92 commented 4 months ago

To validate that your fix above worked for the binary name detection and public headers issues I made a few other changes to more closely follow my app's structure:


With that, I am now able to compile the EX app target, but when the app launches, I see the following issue:

dyld[97467]: Library not loaded: @rpath/ogg.framework/ogg
  Referenced from: <2DA628EF-071C-33D8-AA00-7B1A3EBC3599> /Users/cstultz/Library/Developer/Xcode/DerivedData/EX-dvopbqpxtghxchfetmgezzfsuptf/Build/Products/Debug-iphonesimulator/opus.framework/opus
  Reason: tried: '/Users/cstultz/Library/Developer/Xcode/DerivedData/EX-dvopbqpxtghxchfetmgezzfsuptf/Build/Products/Debug-iphonesimulator/ogg.framework/ogg' (no such file), '/Users/cstultz/Library/Developer/Xcode/DerivedData/EX-dvopbqpxtghxchfetmgezzfsuptf/Build/Products/Debug-iphonesimulator/PackageFrameworks/ogg.framework/ogg' (no such file), '/Users/cstultz/Library/Developer/CoreSimulator/Devices/79B9F89A-CC2B-4846-BBA7-84E3AF0DBDA7/data/Containers/Bundle/Application/17870CCD-78D9-41F8-AEC7-90DE0A49E573/Frameworks/ogg.framework/ogg' (no such file), '/Users/cstultz/Library/Developer/Xcode/DerivedData/EX-dvopbqpxtghxchfetmgezzfsuptf/Build/Products/Debug-iphonesimulator/opus.framework/Frameworks/ogg.framework/ogg' (no such file), '/Users/cstultz/Library/Developer/CoreSimulator/Devices/79B9F89A-CC2B-4846-BBA7-84E3AF0DBDA7/data/Containers/Bundle/Application/17870CCD-78D9-41F8-AEC7-90DE0A49E573/EX.app/Frameworks/ogg.framework/ogg' (no such file), '/Users/cstultz/Library/Developer/CoreSimulator/Devices/79B9F89A-CC2B-4846-BBA7-84E3AF0DBDA7/data/Containers/Bundle/Application/17870CCD-78D9-41F8-AEC7-90DE0A49E573/EX.app/Frameworks/ogg.framework/ogg' (no such file), '/Library/Developer/CoreSimulator/Volumes/iOS_21E213/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/ogg.framework/ogg' (no such file)

And when attempting to compile the EXTests target, I am seeing a familiar issue again: Missing required module 'ThirdPartyAdapter'

image

Get the latest changes on my fork (branch: kalub92/hyphenated_module_name_bug) here: https://github.com/kalub92/cocoapods-spm/commit/3a50a4a5292b9006c960dc2ef6284c40e45cb422

kalub92 commented 4 months ago

Hey @trinhngocthuyen - any update on the above issues?

trinhngocthuyen commented 4 months ago

Srr I'm a bit occupied lately. Will look into it this weekend.

kalub92 commented 4 months ago

Srr I'm a bit occupied lately. Will look into it this weekend.

Thank you! I really appreciate it. 😁

trinhngocthuyen commented 3 months ago

@kalub92 I fixed the issue (with Firebase) mentioned in https://github.com/trinhngocthuyen/cocoapods-spm/issues/87#issuecomment-2141028614 in #97. Kindly check again.

Regarding the issue about Library not loaded: @rpath/ogg.framework/ogg, would you mind creating another dedicated issue for better tracking? Thank you!

kalub92 commented 3 months ago

@trinhngocthuyen Thank you – I've confirmed that with #97 I can install Firebase via spm_pkg.

Just raised a new issue for the Library not loaded... bug: https://github.com/trinhngocthuyen/cocoapods-spm/issues/98

Thanks again for all your hard work – this is going to really help my team once it's working for us!

kalub92 commented 3 months ago

Hi @trinhngocthuyen, any update on this issue? 😁

trinhngocthuyen commented 3 months ago

Gonna follow up on the other thread/issue. For this thread, I think I'm gonna close as it's becoming more irrelevant with the original (which was not the case).