7 Circles SPM or How to Make a Modular Application on Swift Package Manager

Thanks to Jackie Zhao @jiaweizhao for the photo on Unsplash
Thanks to Jackie Zhao @jiaweizhao for the photo on Unsplash

, , . , , , . — SPM, .





, :  “ SPM?”. . , SPM :





  • SPM .xcodeproj ( );





  • . , , ;





  • Linux/Windows;





  • xcode .





?

Package.swift , , .





, :





import PackageDescription
 
let package = Package(
    // 1.   
    name: "Resources",
    // 2. ,    
    platforms: [
        .iOS(.v11),
    ],
    // 3. ,       
    //     ,     Firebase SPM 
    products: [
        .library(
            name: "Resources",
            //    
          	//    nil - SPM      
            //       .static
            type: .dynamic,
            targets: ["Resources"]),
    ],
    // 4.      ,
  	//    ,    targets
    dependencies: [
        // name -   ( 1),     ,  
        .package(name: "R.swift.Library", url: "https://github.com/mac-cain13/R.swift.Library", .branch("master")),
        //    
        .package(path: "../Core")
    ],
    targets: [
        //         
        //       
      	//  Sources/_   
      	//         Sources,   "path:"
        .target(
            name: "Resources",
            dependencies: [
                //          
                // name( 3), package( 1)
                .product(name: "RswiftDynamic", package: "R.swift.Library")
            ],
            resources: [
                //         
                //      Sources/_/___
                //   ,   
                .process("Resources")
            ])
    ]
)

      
      



  ,   ( , ). workspace, drag and drop Project navigator.





, , . swift-tools-version:5.3, Xcode Version 12.2





1. .

SPM. - . : , .





2. SPM .

, , , . , - , : , (, checkout) .





3. R.swift SPM.

.xcodeproject , R.swift , .





XcodeGen . swift package generate-xcodeproj, .xcodeproj SPM.





, . , , R.swift. , : 





error: [R.swift] Project file at 'file:///Users/.../Resources.xcodeproj/' could not be parsed, is this a valid Xcode project file ending in *.xcodeproj?
      
      



XcodeEdit — R.swift . exec , .xcodeproj. :





pathtoexec/XcodeEdit-Example Resources.xcodeproj











Fatal error: 'try!' expression unexpectedly raised an error: XcodeEdit_Example.AllObjectsError.fieldMissing(key: "buildRules"): file XcodeEdit_Example/main.swift, line 21
      
      



:





sed -i '' -e 's/isa = "PBXNativeTarget";/isa = "PBXNativeTarget";buildRules = ();/' Resources.xcodeproj/project.pbxproj
      
      



, . generate-xcodeproj . R.swift .xcodeproj/.pbproject, .





ruby gem xcodeproj, . , .xcodeproj XcodeGen.





XcodeGen:





#   xcodeproj
name: Resources
targets:
 Resources:
   #      ,    
   type: framework
   platform: iOS
   # Root folder    
   sources:
     - Sources

      
      



.xcodeproj :





xcodegen generate --spec Resources.yml







.xcodeproj , R.swift:





#  xcodeproj
generateXcodeProject() {
 xcodegen generate --spec Resources.yml
}
 
#  buildSettings    xcodebuild ,  ,    
#         
getBuildSettings() {
 xcodebuild -project "Resources.xcodeproj" -target "Resources" -showBuildSettings > buildSettings.txt
}
 
#  enviroment    R.swift    
parseEnvironmentVariables() {
 export SRCROOT="$(cat buildSettings.txt | grep -m1 "SRCROOT" | sed 's/^.*= //' )"
 export TARGET_NAME="$(cat buildSettings.txt | grep -m1 "TARGET_NAME" | sed 's/^.*= //' )"
 export PROJECT_FILE_PATH="$(cat buildSettings.txt | grep -m1 "PROJECT_FILE_PATH" | sed 's/^.*= //' )"
 export TARGET_NAME="$(cat buildSettings.txt | grep -m1 "TARGET_NAME" | sed 's/^.*= //' )"
 export PRODUCT_BUNDLE_IDENTIFIER="$(cat buildSettings.txt | grep -m1 "PRODUCT_BUNDLE_IDENTIFIER" | sed 's/^.*= //' )"
 export PRODUCT_MODULE_NAME="$(cat buildSettings.txt | grep -m1 "PRODUCT_MODULE_NAME" | sed 's/^.*= //' )"
 export TEMP_DIR="$(cat buildSettings.txt | grep -m1 "TEMP_DIR" | sed 's/^.*= //' )"
 export BUILT_PRODUCTS_DIR="$(cat buildSettings.txt | grep -m1 "BUILT_PRODUCTS_DIR" | sed 's/^.*= //' )"
 export DEVELOPER_DIR="$(cat buildSettings.txt | grep -m1 "DEVELOPER_DIR" | sed 's/^.*= //' )"
 export SOURCE_ROOT="$(cat buildSettings.txt | grep -m1 "SOURCE_ROOT" | sed 's/^.*= //' )"
 export SDKROOT="$(cat buildSettings.txt | grep -m1 "SDKROOT" | sed 's/^.*= //' )"
 export PLATFORM_DIR="$(cat buildSettings.txt | grep -m1 "PLATFORM_DIR" | sed 's/^.*= //' )"
 export INFOPLIST_FILE="$(cat buildSettings.txt | grep -m1 "INFOPLIST_FILE" | sed 's/^.*= //' )"
 export SCRIPT_INPUT_FILE_COUNT=1
 export SCRIPT_INPUT_FILE_0="$TEMP_DIR/rswift-lastrun"
 export SCRIPT_OUTPUT_FILE_COUNT=1
 export SCRIPT_OUTPUT_FILE_0="$SRCROOT/Sources/Resources/Generated/R.generated.swift"
}
 
#    
rswift() {
 R.swift generate --accessLevel public "$SCRIPT_OUTPUT_FILE_0"
}
 
#     R.swift     
# Bundle.module -   SPM,         
replaceRSwiftHostingBundle() {
 sed -i '' -e 's/Bundle(for: R.Class.self)/Bundle.module/' ./Sources/Resources/Generated/R.generated.swift
}
 
mkdir Sources/Resources/Generated
generateXcodeProject
getBuildSettings
parseEnvironmentVariables
rswift
replaceRSwiftHostingBundle

      
      



4. Cocoapods SPM .

, Pod fat , SPM . proxy - XCFramework. Pod, SPM — , XCFramework





Pod .framework, -.





, XCFramework. fat , Pod- 2 — . . , XCFramework arm64 x86_64. , lipo -info pathtoframework



.





#  2      
cp -a YandexMapsMobile YandexMapsMobile_sim
cp -a YandexMapsMobile YandexMapsMobile_device
 
cd YandexMapsMobile_sim/YandexMapsMobile.framework/Versions/A
#         fat       
lipo -thin x86_64 YandexMapsMobile -output YandexMapsMobile_x86_64
#  universal framework-a  
#         
#    (i386),        thin
#     create
lipo -create YandexMapsMobile_x86_64 -output YandexMapsMobile_sim
rm -rf YandexMapsMobile YandexMapsMobile_x86_64
mv YandexMapsMobile_sim YandexMapsMobile
cd ../../../..
 
#   ,    
cd YandexMapsMobile_device/YandexMapsMobile.framework/Versions/A
lipo -thin arm64 YandexMapsMobile -output YandexMapsMobile_arm64
lipo -create YandexMapsMobile_arm64 -output YandexMapsMobile_device
rm -rf YandexMapsMobile YandexMapsMobile_arm64
mv YandexMapsMobile_device YandexMapsMobile
cd ../../../..
 
#   xcframework
xcodebuild -create-xcframework -framework YandexMapsMobile_sim/YandexMapsMobile.framework -framework YandexMapsMobile_device/YandexMapsMobile.framework -output YandexMapsMobile.xcframework
      
      



XCFramework, binaryTarget. , :





let package = Package(
    name: "YandexMapsMobileWrapper",
    platforms: [
        .iOS(.v11),
    ],
    products: [
        .library(
            name: "YandexMapsMobileWrapper",
            type: .static,
            //     XCFramework   
            targets: ["YandexMapsMobileWrapper"]),
    ],
    dependencies: [
    ],
    targets: [
        //    ,       url
        .binaryTarget(name: "YandexMapsMobileBinary", path: "YandexMapsMobile.xcframework"),
        //    
        .target(
            name: "YandexMapsMobileWrapper",
            dependencies: [
                .target(name: "YandexMapsMobileBinary"),
            ],
            linkerSettings: [
                .linkedFramework("CoreLocation"),
                .linkedFramework("CoreTelephony"),
                .linkedFramework("SystemConfiguration"),
                .linkedLibrary("c++"),
                .unsafeFlags(["-ObjC"]),
            ]),
    ]
)

      
      



5. SPM (Bundle.module).

, , , yourpackagename_yourpackagename, , - . , , .





private class BundleFinder {}
 
 
//    SPM ,  2 
public extension Bundle {
    
    //  №1,  ,    
    static var resourceBundle: Bundle = {
 
        let bundleName = "Resources_Resources"
        let candidates = [
            Bundle.main.resourceURL,
            Bundle(for: BundleFinder.self).resourceURL,
            Bundle.main.bundleURL,
            //  №2,         
            Bundle(for: BundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent(),
        ]
        for candidate in candidates {
            let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
            if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
                return bundle
            }
        }
        fatalError("unable to find bundle named \(bundleName)")
    }()
    
}
      
      



6. Resolve Swift Packages .

SPM , . SPM , Xcode . Xcode .





7. Other Linker Flags SPM.

, . — ObjC . likerSettings: [.unsafeFlags([“ObjC”])]



. , unsafeFlags, , -, branch . 





Swift Package Manager —  . , -, . , SPM workaround, ?





, !








All Articles