Use a Cocoapod from main project into a Swift package - ios

I've developed a simple Swift Package with a UIView to use it in more projects.
I needed in that UIView to use a UIImageView that receives the image from a URL and I want for that to use SDWebImage.
I've added in my Swift Package Package.swift the SDWebImage as dependency and it created a Package.resolved in root directory and also added a Package dependency to SDWebImage:
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MyPacket",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(name: "MyPacket", targets: ["MyPacket"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(name: "SDWebImage", url: "https://github.com/SDWebImage/SDWebImage.git", .upToNextMajor(from: "5.0.0"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "MyPacket",
dependencies: [
.product(name: "SDWebImage", package: "SDWebImage")]
)
]
)
Everything works well until this point.
In my main project I also have SDWebImage as a pod installed and now in main project beside MyPacket in Package Dependencies it also shows SDWebImage as a dependency which means that SDWebImage is both in Podfile and Package Dependencies.
The application works but it throws this warning for every SDWebImage method:
objc[72670]: Class SDWeakProxy is implemented in both /Users/user/Library/Developer/CoreSimulator/Devices/81EA832E-6ED3-4560-8994-298CB8A00D2A/data/Containers/Bundle/Application/B01EFC62-3996-473D-9F8C-6FCCF80BF077/MyMaiProject.app/Frameworks/SDWebImage.framework/SDWebImage (0x10c5cd370)
and /Users/use/Library/Developer/CoreSimulator/Devices/81EA832E-6ED3-4560-8994-298CB8A00D2A/data/Containers/Bundle/Application/B01EFC62-3996-473D-9F8C-6FCCF80BF077/MyMainProject.app/MyMainProject (0x10139aaa0). One of the two will be used. Which one is undefined.
Question
Is there any way to condition the Podfile if the framework is already in Package Manager to not initialise it or to remove the SDWebImage from the MainProject Package Dependencies?
Or is posibile to use the SDWebImage, that is installed in my MainProject with pods, in my Packet that is installed with Package Manager?

Related

Xcode build reports "no such module" for Swift Package Manager packages. Building again and again eventually fixes it

I get "no such module" errors when trying to build after doing a "Clean Build Folder". Repeatedly trying to build eventually results in no errors.
IMPORTANT: The modules that can not be found are local packages with Swift Package Manager, i.e. not downloaded from git. I'm specifying these dependencies like this:
.package(url: "file:../CoreGraphicsExtensions", from: "0.0.0")
As seen in my /UIKitExtensions/Package.swift file listed here:
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "UIKitExtensions",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "UIKitExtensions",
targets: ["UIKitExtensions"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "file:../CoreGraphicsExtensions", from: "0.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "UIKitExtensions",
dependencies: []),
.testTarget(
name: "UIKitExtensionsTests",
dependencies: ["UIKitExtensions"]),
]
)
Here is what I see in Xcode when I am trying to build.
First build...
Second Build...
Third Build...
Fourth Build...
Fifth Build...
... finally it runs. 🥳
But there are some strange warnings about the packages not being used by any target.
How can I get this to build correctly first time after a clean build?
My workspace structure looks like this...
Two projects in the workspace. BlenderViewer has the target I'm building. BlenderViewer has 6 local SPM packages. It also imports the PhyKit project as a framework, no problems there. The SPM packages have some dependencies between each other, but nothing circular.
Everything looks fine to me...
I'm running MacOS 12.5. Xcode 13.4.1. Building for iOS 15.3.
Am I doing something wrong here?
I had the same error with an online package (after removing and re-adding it), and also had no luck with the other suggestions to clean derived data, reset package cache, and resolve dependencies from the command line.
What I found eventually, was that the package in question needed to be manually re-added to the app target in Build Phases > Link Binary with Libraries. This solved the issue.
When you removed derived data or sometimes Xcode's package cache gets confused. This will usually result in weird build errors that can't really be explained. Here is things that might help:
Resetting the Xcode Package Cache: To reset the package cache, open the File menu, navigate to Packages, and click Reset Package Caches. This will delete all local package data and redownload each package from its source online. Wait till all the packages downloads
You can try Xcode CommandLine tool instead:
xcodebuild on the project directory
It usually works without this frustrating issue
I had the same with TestTarget which can't resolve the main target local SPM
finally, fix with this approach
I added all main target dependencies to the test target too and after that find, I had some compiler issues in which the compiler showed me this wrong error, and when errors were fixed this error disappeared

Swift Package Manager - copy files build phase for a dynamic dependency

I'm working on an iOS application, and the core of it we are going to open source as a standalone Swift Package for other developers.
The Swift Package depends on Sodium (https://github.com/jedisct1/swift-sodium). There is an issue with this library when using SPM, that during archive (and sometimes during compile locally in Xcode) that it will fail to find the embedded CLibrary, making it impossible to release to the app store if using this package, or having it embedded in your own. (This is recorded as a bug in SPM / Xcode but will take sometime to fix)
Someone made a Fork that solves some of the problems here. They solved the issue by splitting the Clibrary out as a separate .xcframework hosted somewhere else. Then making a new Package.swift that imports it as an external dependency. This updated Sodium Package has been set to type dynamic.
Including this in my Swift package, and then including my package in my iOS application ... it builds fine, runs in Xcode fine, runs on simulators fine, throws no errors when exporting or upload to TestFlight.
However after downloading from TestFlight, I get this error saying the Sodium framework can't be found
Termination Description: DYLD, dyld: Using shared cache: <hash> | dependent dylib `#rpath/Sodium.framework/Sodium` not found for <path-to-ipa>/<appname> tried but didn't find <path-to-sodium>
Using other dependency management tools, the solution is usually to add a "Copy Files Build Phase" to the iOS application's xcode project and add the framework. If I try to do this in my Xcode project, I can't find the Sodium framework, and I can't find a way to tell SPM to copy the framework inside the Package.swift.
How do I update my Package.swift so that it copies this framework for any app that uses it?
If thats not possible, how do I copy it inside the Xcode project? Its not visible in the dropdown
Currently my Package.swift looks like this:
import PackageDescription
let package = Package(
name: "<package-name>",
platforms: [.iOS(.v14)],
products: [
.library(name: "<package-name>", targets: ["<package-name>"]),
],
dependencies: [
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.2.1"),
.package(name: "Sodium", url: "https://github.com/junelife/swift-sodium.git", .branch("spm"))
],
targets: [
.target(
name: "<package-name>",
dependencies: [
"Sodium",
"BigInt",
]
),
.testTarget(
name: "<package-name>Tests",
dependencies: ["<package-name>"]
),
]
)
Workaround for now:
If I import the dynamic package into the iOS app as well, I get the option to "Embed and sign" next to the Sodium framework under Frameworks, Libraries, and Embedded Content under the general tab of the target. Which forces it into the bundle.
Note:
I attempted to make my Package Dynamic, to see if including it would embed everything automatically and avoid having to give users instructions to users to embed it separately. But it didn't work. It only embedded my package and not the dynamic dependency.
If anyone has a way to modify a Package.swift to force it to embed dynamic dependencies ... or knows how to clean up this mess entirely. Please comment

Swift Package Manager and IBDesignable Error

I'm working on adding Swift Package Manager support to an existing iOS library that I have that already supports CocoaPods and Carthage. I was able to able to setup the Package.swift file and create the package and push those changes to the repo. It compiles fine in Xcode and builds fine on the command line.
The issue I'm having is when I add it as a dependency to a test application, the IBDesignable stuff won't compile, and errors with a Build Failed flag. The message states:
The built product "" does not exist in the file system. Make sure your built products contains either an application or a framework product.
I have tried cleaning, deleting derived data, and removing/re-adding the package to the project.
My Package.swift file is as follows:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "package_name",
platforms: [
.iOS(.v11)
],
products: [
.library(
name: "package_name",
targets: ["target_name"])
],
targets: [
.target(
name: "target_name"
)
])
Is there something I'm missing or a setting that needs to be set in order for it to function properly?
Thanks in advance for all the help.

How to integrate SwiftLint with an iOS app using Swift Package Manager?

I'm creating a new iOS app in Xcode 11 (beta 5) and I'd like to try using Swift Package Manager instead of CocoaPods for managing dependencies.
A common pattern when using SwiftLint and CocoaPods is to add SwiftLint as a dependency and then add a build phase to execute ${PODS_ROOT}/SwiftLint/swiftlint; this way all developers end up using the same version of SwiftLint.
If I try to add SwiftLint as a SwiftPM dependency in Xcode, the executable target that I need is disabled:
I was able to fake it by creating a dummy Package.swift with no products or targets and running swift run swiftlint in my build phase, but it feels hacky and weird:
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "dummy-package",
products: [],
dependencies: [
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.34.0")
],
targets: []
)
Is there a way do this without creating a dummy package? Or is Swift Package Manager just not the right tool for this particular use case?
All methods of abusing iOS code dependency managers to run build tools are hacky and weird.
The correct way to version SPM-compliant tool dependencies is to use Mint: A package manager that installs and runs Swift CLI packages. See also Better iOS projects: How to manage your tooling with mint.
Not perfect solution, but it works. I've found it here
Modify target section of Package.swift
targets: [
// 1. Specify where to download the compiled swiftlint tool from.
.binaryTarget(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.47.1/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "cdc36c26225fba80efc3ac2e67c2e3c3f54937145869ea5dbcaa234e57fc3724"
),
// 2. Define the SPM plugin.
.plugin(
name: "SwiftLintPlugin",
capability: .buildTool(),
dependencies: ["SwiftLintBinary"]
),
// 3. Use the plugin in your project.
.target(
name: "##NameOfYourMainTarget",
dependencies: ["SwiftLintPlugin"] // dependency on plugin
)
]
Create Plugins/SwiftLintPlugin/SwiftLintPlugin.swift
import PackagePlugin
#main
struct SwiftLintPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
return [
.buildCommand(
displayName: "Running SwiftLint for \(target.name)",
executable: try context.tool(named: "swiftlint").path,
arguments: [
"lint",
"--in-process-sourcekit",
"--path",
target.directory.string,
"--config",
"\(context.package.directory.string)/.swiftlint.yml" // IMPORTANT! Path to your swiftlint config
],
environment: [:]
)
]
}
}
I use xcodegen to genreate a Xcode project that has the ability to run scripts. This lets me see swiftlint warnings in Xcode while developing packages.
This tool creates a Xcode project from a project.yml definition. In that definition, you can add a script that runs swiftlint as a post compile task. Example.
Advantages of this method:
swiftlint warnings in Xcode.
Xcode settings beyond what SPM offers.
Disadvantages:
You rely on a third-party tool that could break or go away. However, you can drop this dependency at any time and go back to edit the Package.swift in Xcode.
You need to learn to write project.yml files.
If you use SPM bundles you need to generate the bundle accessor yourself.
A word about generating a bundle accessor. This is needed when working from a Xcode project because only SPM generates the file resource_bundle_accessor.swift to the project. If you already compiled after opening the Package.swift with Xcode, the file should be here:
find ~/Library/Developer/Xcode/DerivedData* -iname resource_bundle_accessor.swift
You can add it to the project, but if you are creating a framework, the bundle accessor can be as simple as:
import class Foundation.Bundle
// This file is only used when using a xcodegen-generated project.
// Otherwise this file should not be in the path.
private class BundleFinder {}
extension Foundation.Bundle {
static var module = Bundle(for: BundleFinder.self)
}

Use swift package manager on existing xcode project

im new to the swift and xcode world, so i'm having a problem trying to integrate a package to my project.
I want to add Alamofire dependency, with the following commands:
Inside my root project folder:
swift init
this creates the Package.swift file, i add the dependency inside, run then:
swift build
Everything seems to be ok, but im my project when i try to import my library:
import Alamofire
I get an error, it says that the module is not recognized.
So my question here is, what is the correct steps to integrate Package Manager and a dependency on a existing project without crashing everything.
UPDATE:
swift build
outputs:
Resolved version: 4.3.0
Compile Swift Module 'Alamofire' (17 sources)
Compile Swift Module 'Sample' (1 sources)
And my Package.swift is:
import PackageDescription
let package = Package(
name: "Sample",
dependencies: [
.Package(url: "https://github.com/Alamofire/Alamofire.git", majorVersion: 4)
]
)
If you're using an Xcode project, you don't need (and shouldn't use) a Package.swift, just click the plus icon in Swift Packages in Xcode, and add the GitHub URL of the Swift Package, and the library will also be added to your target automatically (follow the GIF below, or click Add icon in image here):
Extra info
Inconsistency problem: You can't maintain both an Xcode project and a Swift.package for the same targets. They do not synchronize, and will become inconsistent, so depending on which tools, you'll get different builds: confusing. You used to be able to create a xcodeproj based on Package.swift using swift package generate-xcodeproj, but this is deprecated. Changes you make to this Xcodeproj didn't get reflected in the original Package.swift).
xcodebuild vs. swift build: Conveniently, if there is no xcodeproj in the same directory as your Package.swift, xcodebuild will auto-generate schemes for you to use, so you don't have to use swift build. For example, run xcodebuild -list to see the list of schemes generated from your Package.swift file, then use one of these schemes. Unconveniently, there isn't a way/ config to make xcodebuild use Package.swift.
Swift Package Manager is a standalone tool which allows managing dependencies and building projects without Xcode. It can generate Xcode projects for you with swift package generate-xcodeproj.
However, at the moment, Swift Package Manager only has support for building projects for macOS and Linux platforms. The only way to build projects for iOS, tvOS and watchOS is using Xcode, which includes the SDKs needed for these platforms.
There are ways to use Swift Packages Manager to manage dependencies for iOS/tvOS/watchOS, but it is not easy and requires manual work. If you are interested, take a look at https://github.com/j-channings/swift-package-manager-ios
Other than that, I'd recommend using Carthage or CocoaPods.
Update for Xcode 11
Swift Package Manager is now integrated into Xcode 11. You can add your package by going to "File" then "Swift Packages" then "Add Package Dependency..." Paste the repository's URL into the field above then click "next". Xcode will walk you through the rest of the steps. You can learn more at this WWDC talk.
Swift Package Manager(SPM)
[iOS Dependency manager]
Consume: [SPM manage dependency]
Produce: If you are developing library/module(modularisation) you should take care of supporting it
Package.swift - manifest file.
It is a hardcoded name.
Should be placed on the same level or higher, in other cases:
target '<target_name>' in package '<package_name>' is outside the package root
Package.swift example
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "PackageName1",
//Supported platforms
platforms: [
.iOS(.v11)
],
//Product list - executable binary
products: [
.library(
name: "LibraryName1",
targets: ["TargetName1"]),
],
//Additional SPM dependencies declaration(packages) which are used in targets
dependencies: [
//Local package path
.package(name: "PackageName2", path: "../Feature1")
//Local package URL
.package(name: "PackageName2", url: "file:///some_local_path", .branch("master"))
//Remote package URL
.package(name: "PackageName2", url: "https://github.com/user/repo", .branch("master")),
],
targets: [
//Source code location. implicitly `Sources/<target_name>`(Sources/TargetName1) or explicitly `path:`
.target(
name: "TargetName1",
dependencies: [
//using dependencies from package.targets and package.dependencies
//package.targets
"LibraryName2"
//package.dependencies
.product(name: "PM2LibraryName1", package: "PackageName2")
]),
path: "Sources/TargetName1"
]
)
Observations for Swift dependency
[SWIFT_MODULE_NAME, PRODUCT_NAME, EXECUTABLE_NAME] == package.targets.target.name
package.products.library.name is used only for Xcode representation
Library is used[About]
When target has a dependency on another target source code will be included into single library with a kind of explicit multi module from .modulemap[About] and access can be thought
import Module1
import Module2
Swift library converts into single .o format[About] file and .swiftmodule[About]
Swift library exposes .modulemap umbrella.h[About] for exposing module for Objective-C consumer thought #import SomeModule;
- You are able to work with `Package.swift` in Xcode if double click on it
.swiftpm/xcode with .xcworkspace will be generated
- When you work with `Package.swift` you are able to check it, just save this file
- When you work with `Package.swift` you should specify schema and device(platform)
- When you build `Package.swift`
- library and .swiftmodule is located:
<path_to_derived_data>/DerivedData/<folder_name_where_Package.swift_located>-<randomizer>/Build/Products/<platform>
//e.g.
/Users/alex/Library/Developer/Xcode/DerivedData/someFolder-ccivqbbjujxuvdcxtuydyqfeepfx/Build/Products/Debug-iphonesimulator
- .modulemap and umbrella.header is located:
<path_to_derived_data>/DerivedData/<folder_name_where_Package.swift_located>-<randomizer>/Build/Intermediates.noindex/<project_name>.build/<platform>/<target_name>.build/<.modulemap> and plus /Objects-normal/<arch>/<tarhet_name-Swift.h>
- When you build consumer with `Package.swift` results files will be located:
<path_to_derived_data>/DerivedData/<target_name>-<randomizer>/Build/Products/<platform>
- When you work with consumer of `Package.swift` SPM clones it into
<path_to_derived_data>/DerivedData/<target_name>-<randomizer>/SourcePackages/checkouts
package.products.library.targets
You are able to specify several targets. It means that it is possible to use several modules from single executable binary(a kind of umbrella library)
producer:
package.products.library.targets: ["TargetName1", "TargetName2"]
consumer:
1. adds single library
2. several imports
import TargetName1
import TargetName2
package.targets.target.dependencies
If your target has dependency. You will have the same effect - Umbrella library.
1.Add another target from the same package
For example Explicit Dependency[About] is used. Important: use the same names(module and SPM target). If you don't set dependency at package.targets.target.dependencies you get next error
Undefined symbol
2.Add product from another package. All targets from the other package will be exposed
2.1 Local package path
You can not use on consumer side. But it allows you to debut it at least
package <package_name_1> is required using a revision-based requirement and it depends on local package <package_name_2>, which is not supported
2.2 Local package URL
Don't use space( ) in path or you get
The URL file:///hello world/some_local_path is invalid
If you don't specify // swift-tools-version:<version> you get:
package at '<some_path>' is using Swift tools version 3.1.0 which is no longer supported; consider using '// swift-tools-version:5.3' to specify the current tools version
*Do not forget commit your changes and update[About] before testing it

Resources