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 documentation on creating a Swift Package #23

Closed
Jomy10 opened this issue Jan 31, 2022 · 18 comments
Closed

Add documentation on creating a Swift Package #23

Jomy10 opened this issue Jan 31, 2022 · 18 comments

Comments

@Jomy10
Copy link
Contributor

Jomy10 commented Jan 31, 2022

Hi, I'm the one who asked about using Rust in Swift on Reddit.

I've made a Gist that follows your steps, but slightly modified to set up a Swift project with Swift Package Manager (https://gist.github.com/Jomy10/a4873dd43942ed1bf54d387dbc888795).

I've tried to add dependencies to Package.swift, but I don't know how to include them in the compiling process. Swiftc documentation seems to be rather scarce.

Here's an example of the Package.swift file:

// swift-tools-version:5.5
import PackageDescription

let package = Package(
    name: "swift",
    dependencies: [
        .package(url: "/path/to/HelloSwift", .branch("master"))
    ],
    targets: [
        .target(
            name: "swift",
            dependencies: [
                .product(name: "HelloSwift", package: "HelloSwift")
            ]),
        .testTarget(
            name: "swiftTests",
            dependencies: ["swift"]),
    ]
)

Folder structure is also in the gist.

I think it would be great if we could also use Swift Package Manager in the Swift project using the Rust library. Or maybe import the Rust library as a package in Package.Swift.

@chinedufn
Copy link
Owner

chinedufn commented Jan 31, 2022

Hey!

If I'm understanding, you're saying that you'd like to be able to have the Rust side of things get packaged up into a Swift package that you can then import from your Swift project? Or are you saying something else?

I'll read up on Swift packages since I don't know much about them at the moment.

Would using Swift packages be strictly better to what we're currently doing, or are there any downsides?

Also, if you could explain your Swift+Rust use case that would be great - just so that I can better understand when and how Swift packages are typically used (I'm new to Swift).

Thanks for opening this issue!

@chinedufn
Copy link
Owner

chinedufn commented Jan 31, 2022

Alright I read up on Swift packages on modules and have a basic understanding.

It seems like if I had to find the closest Rust ecosystem version of them..:

Swift Package == Cargo crate
Swift Module == Rust module

I know they aren't the same.. I'm just going for a rough understanding of what these Swift code organization tools are.


Ok so I'd sum up my current thoughts as:

  • I don't yet understand what your end goal is and would love an explanation / some context around how you're using Swift+Rust so that I can better understand why you want to be able to generate a Swift package.

@Jomy10
Copy link
Contributor Author

Jomy10 commented Jan 31, 2022

I do think that compiling to a Swift package could be a huge plus. Right now we can compile Swift code using swiftc and linking the rust library. This is great for cli's. However, if we could either compile the Rust code to a Swift library, or compile the Swift project that includes the Rust code to a Swift library, I think this could be a huge benefit.

If our Swift code is in package form, we can import it in every Swift project (as long as we have compiled our rust code to the right targets, of course).

Swift packages are similar to Rust crates, you can create one with mkdir myPackage && cd $_ && swift package init --type library (Swift Package Manager docs).

Other projects or packages can then depend on the package by specifying the package in the Package.swift file (see above for example of importing a package named HelloSwift in the package named swift).

I guess this would be similar to any other Swift package that wraps a C api (Can't remember a good, simple example, but here is one for LLVM: https://github.com/llvm-swift/llvmswift).

Really love this project, if you need any help, I would love to contribute where I can.


EDIT: I'll also do some research myself and will let you know if I get some working prototype

@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 1, 2022

So far I've had no luck, my knowledge of C and headers is quite limited, though.

So far I have found the following:

@chinedufn
Copy link
Owner

chinedufn commented Feb 1, 2022

Thanks the gist and for all of the links. I've read through them and they were helpful as I start to get familiar with Swift packages / modules / etc.

Awesome work and research so far..


Really love this project, if you need any help, I would love to contribute where I can.

I'd love for you to contribute! Awesome, going forwards I'll start creating more issues and you can get involved wherever you like. Figuring out this Swift Package stuff might be a great place to start.


Alright.. I did some more reading and thinking and here's where I'm at.

  • We want people to be able to expose their Swift+Rust libraries as Swift Packages, Cocoapods, Carthage "packages" (not sure what they're really called), or any format that they want.

  • A good starting point would be to add documentation on how to take the current output of swift-bridge-build and turn it into a package yourself.

    • This will teach us about how to create packages and how they should be shaped. For example, right now we emit everything into the global module so everything has access to the SwiftBridgeCore.swift file that we generate. So we'd need to figure out how leveraging SwiftBridgeCore looks in a package world.. Or if it even still makes sense in a package world.
  • Then in the future we might decide to have swift-bridge-build know about some of these target formats so that it can emit a package for you based on some configuration values. Or we might decide that that is out of scope for the library. Who knows.. needs more thinking..


So, here's a potential path forwards (which I think you've already made progress on in your gist.)

  • Write a book chapter on how to take the Swift and C headers that swift-bridge-build currently outputs and bundle them up into a Swift package.

  • Open a pull request with wherever you get stuck (i.e. I think you were stuck on linking in the Rust library).

  • I'm comfortable with linking, so I can try to add a commit to the PR that adds linking related stuff to the chapter to finish up that piece (unless you end up figuring it out beforehand of course).

  • We clean up the PR and merge it. Now people have instructions on how to manually create their own Swift package. Then.. in the future.. we can think through whether or not we want swift-bridge-build to be able to automatically output a package (perhaps as a flag.. or at the default behavior.. who knows).


On the flip side.. we'll also want to eventually explore how to expose Swift + Rust code as a Cargo crate.. (first as documentation.. then later explore having swift-bridge-build emit a crate.. But that isn't important for this issue. Just jotting it down so we remember for the future.

@chinedufn chinedufn changed the title Including Dependencies in Swift project Creating and importing a Swift Package Feb 1, 2022
@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 1, 2022

Awesome. I'll be looking through the code of this package to get a better understanding of how this all works. After that I can have another attempt at converting to a Swift package.

@chinedufn
Copy link
Owner

Nice! Just let me know whenever you have any questions and I'll try to add answers to the currently empty internal design chapter.

@chinedufn chinedufn changed the title Creating and importing a Swift Package Creating a Swift Package Feb 2, 2022
@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 9, 2022

Ok, I have started looking at the internals of the project. I just wanted to finish a few of my own projects first before I tackled this.

So if I understand correctly, the swift-bridge-build crate generates the .h and .swift files found in the generated folder.

With swiftc we link those files and the .a file generated by cargo to the compiled swift program. So, if I figure out how to link all of those to an XCFramework, we have a standalone Swift package. I've been doing some research on XCFrameworks and will continue doing that now.

@chinedufn
Copy link
Owner

Awesome!!

I'm not familiar with XCFramework but in general that all sounds about right.

@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 10, 2022

So far, I have packaged a system library and a c source file into a Swift package. I've had no luck with including a static library, though.

If you have any ideas on including the rust library, let me know, because so far I've had no luck.

@chinedufn
Copy link
Owner

I think this might walk you through the entire process of going from Rust code to Swift package -> https://betterprogramming.pub/from-rust-to-swift-df9bde59b7cd

Let me know if that helps?

@chinedufn
Copy link
Owner

A quote from Apple's documentation https://developer.apple.com/documentation/swift_packages/distributing_binary_frameworks_as_swift_packages :

In addition, binary dependencies are only available for Apple platforms, which limits the audience for your Swift package.

At the very least I think we'll want our own documentation to make it very clear to people that if they're creating a Swift package it can only be used on Apple hardware.

Which I'd imagine that the vast majority of users will be just fine with.
And we'll still support other output formats for people that aren't targeting Apple hardware (such as what we have right now with just spitting out files).

Not important right now.. Just dropping it here for us to keep in mind as we think through this stuff.

@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 11, 2022

I think this might walk you through the entire process of going from Rust code to Swift package -> https://betterprogramming.pub/from-rust-to-swift-df9bde59b7cd

Judging by the linked repo, I’m almost there with linking an XCFramework to Swift.

A quote from Apple's documentation

I’ve come across that as well, so unfortunately this approach will indeed not work for non-Apple devices. I haven’t come across any other solutions to this. I guess we will have to wait until some cross-platform solution comes around.

@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 11, 2022

Aha! Seems like all I was missing was a modulemap file. I'll clean up what I have now and try it from scratch again.

I'll update you soon

@Jomy10
Copy link
Contributor Author

Jomy10 commented Feb 12, 2022

Ok, here's what I have. Source code on this branch.

1. Rust

I have a Rust project with these functions:

fn print_hello_rust() {
    println!("Hello from Rust!");
}

fn is_from_rust() -> bool {
    true
}

fn get_hello_rust() -> String {
    String::from("Hello Rust!")
}

Build the project like you would do usually with the build.rs file specified in the book.

2. XCFramework

The next step is to make the XCFramework.

Framework
├── include
│   ├── SwiftBridgeCore.h
│   ├── module.modulemap
│   └── rust.h
├── ios
│   └── librust.a
├── macos
│   └── librust.a
└── simulator
    └── librust.a

We have a folder for each target we build for our rust project and copied both header files as well.

We can then build the XCFramework

xcodebuild -create-xcframework \
    -library simulator/librust.a \
    -headers include \
    -library ios/librust.a \
    -headers include \
    -library macos/librust.a \
    -headers include \
    -output rust.xcframework

The order of the targets is important

Note: we could also build the framework without this command I think, as it is just a folder containing the different targets and a .plist file

3. Swift package

Now, we build the swift package that incorporates the XCFramework.

We copy the .xcframework file from the previous step to the root of this folder and add it as a binaryTarget to our Package.swift as well as include it as a dependency.

let package = Package(
    name: "package",
    products: [
        .library(
            name: "package",
            targets: ["package"]),
    ],
    dependencies: [

    ],
    targets: [
        .binaryTarget(
            name: "Rust",
            path: "rust.xcframework"
        ),
        .target(
            name: "package",
            dependencies: ["Rust"]),
    ]
)

Then, we copy the generated swift files to the Sources/{package_name} folder. In the swift file containing the functions, we make the functions public and add import {XCFramework name} (in this case import Rust).

Now, we should also make the methods in SwiftBridgeCore.swift public so we can call get_hello_rust().toString() in this example to convert a RustString to a String in swift, for example.

4. Testing

After the previous steps, we now have a Swift package that can be used in any project targeting MacOs or iOS (we can of course target other Apple devices as well if we build those targets).

Tested on an executable on MacOS and on the simulator for iOS, and a physical device.

Next steps

I can add a chapter to the book explaining the whole process in detail (basically the above, but a little more "cleaned up"). Let me know if you have any comments on the above.

Next would be generating a Swift package with the build file? I think you know best on how we should implement this into swift-bridge-build.

@chinedufn chinedufn changed the title Creating a Swift Package Add documentation on creating a Swift Package Feb 13, 2022
@chinedufn
Copy link
Owner

chinedufn commented Feb 13, 2022

Awesome work!

Adding a chapter to the book sounds great. My main feedback would be to call the crate something other than rust (such as my-rust-lib and call the package something other than package (such as my-rust-package) so that people can more easily keep track of what these artifacts are.


In terms of generating a package from programatically.. let's create a new issue for that. But yeah I'm happy to help figure out a good starting point for that. Thanks for making so much progress already!

@chinedufn
Copy link
Owner

Closed by #30

@josephg
Copy link

josephg commented Aug 16, 2022

Hey! just want to say thankyou so much for putting this together. Packaging my code as a swift package is way cleaner for my use case, and I appreciate the effort making it easy! Lots of gratitude for swift-bridge and this PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants