AliSoftware / OHHTTPStubs

Stub your network requests easily! Test your apps with fake network data and custom response time, response code and headers!
MIT License
5.03k stars 601 forks source link

Adding Swift Package Manager Support #314

Closed jeffctown closed 4 years ago

jeffctown commented 5 years ago

Updating gitignore. Adding Package.swift Adding Swift tests, getting all builds working. Downgrading swift package version to be compatible with Xcode 9.1. Disabling xcode 9.1 tests and redirect tests in other xcode versions. Changing iPhone types depending on Xcode version. Passing conditional compilation flags to swift test to disable redirect tests in CI. Bumping podspec version. Cleaning up podspec. Updating example projects. Adding SPM Example. Moving Example Stubs to shared folder. Adding building example apps to CI. Adding Carthage builds to CI. Updating rake file for SPM tests. Passing Swift Versions to carthage. Updating SPM example to use URLSession instead of URLConnection. Updating documentation.

Checklist

Description

This PR is for adding Swift Package Manager support.

Motivation and Context

Apple integrated Swift Package Manager into Xcode 11 this year and these are the changes to support it.

File Structure:

The apple docs tell us to have the Package.swift at the root of the repository.

To turn your existing component into a Swift package, you don’t need to create a new Swift package from scratch. Instead, keep your existing project and add a README.md and a Package.swift file inside the root directory of your library project to turn your library into a Swift package.

While SPM does not require a specific structure, OHHTTPStubs's SPM support will be easier to maintain if we conform to the standard style of foldering in SPM: Sources and Tests at the root of the repo, and then folders matching the names of the targets inside of those. This required moving most of the files up a level by removing the OHHTTPStubs folder that was previously at the root of the project and also renaming UnitTests to become Tests.

The pod specs were updated for the new file structure. To validate them, I ran a pod install in the ObjC and Swift Example projects, I verified that the same files exist, their visibility did not change, and that the example projects still work as expected.

The OHHTTPStub project was also updated for the new file structure. To validate it, I ran unit tests on several Xcode versions (9.1, 10.1, 10.2, 11).

Package Naming:

I went through several revisions on the right design for the package targets.

Original Design

I originally went down the path of trying to recreate the subspecs that exist in our Cocoapods integration. This was a bit painful, but worked. Once I started on the SPM example project, I saw the awkwardness of having to import each of the targets to my swift example code.

Screen Shot 2019-08-10 at 8 12 49 PM
Second Design

I then decided to modify the package design to be 2 targets: OHHTTPStubs and OHHTTPStubsSwift, but ran into problems with this approach also.

As soon as I created the OHHTTPStubs target, I started receiving warnings about certain headers not being included in our OHHTTPStubs.h umbrella header. SPM started seeing this existing file as our umbrella header and wasn't generating one on its own anymore. I was tempted to rename our OHHTTPStubs.h file to be something else to get SPM to create an umbrella header again, but wanted to retain backwards compatibility with this PR.

Final Design

I then changed the design to be 2 targets: OHHTTPStubsCore and OHHTTPStubsSwift.

Importing these in Swift will look like this:

Screen Shot 2019-08-12 at 8 48 54 AM

Example Projects:

CI Support:

I added a lot more (is this overkill?) travis tasks in this PR. These are the new tasks:

Other:

sunshinejr commented 5 years ago

Hey @jeffctown, this is awesome, thanks for taking care of that! I've created a WIP PR in Moya to test the integration and it's building and testing correctly! 🎉

The only problem I'm seeing currently is that you would need a conditional import that depends on package manager:

#if canImport(OHHTTPStubs)
import OHHTTPStubs
#elseif canImport(OHHTTPStubsSwift)
import OHHTTPStubsCore
import OHHTTPStubsSwift
#endif

ideally I'd like to not care about that and just do the usual import:

import OHHTTPStubs

I've read that you had problems with umbrella header - have you looked at how guys at Quick did the integration? They have a base and then Quick target that depends on that base. Maybe that would help?

jeffctown commented 5 years ago

@sunshinejr - That's amazing feedback! Thanks for trying out the changes so quickly. It's great to know that everything is working as expected.

I totally agree that having a cleaner import would be nice, and I am willing to give the umbrella header stuff another shot to see if I can work around it.

I'll also look at what Quick is doing. From a quick glance, i'm not sure that we could do the same as them. It looks like they're not adding any Objective-C in their iOS target, but I don't know if OHHTTPStubs could work without swizzling. With the current limitation in SPM of "no mixed language targets" we may have to have 2 imports. I'll look further into it though.

nacho4d commented 5 years ago

Can SWIFT_VERSION be changed to 4.2 or 5.x everywhere? I see there are still old SWIFT_VERSION = 3.0 occurrences and having this complicates installation with SPM and Carthage.

jeffctown commented 5 years ago

Where do you see that @nacho4d ?

nacho4d commented 5 years ago

@jeffctown Sorry, my mistake. I re-checked and I was trying to build the wrong version. No problem here.

JoeMatt commented 5 years ago

This works for me using swift test!

In Package.swift you can test now with,

    dependencies: [
        .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", Package.Dependency.Requirement.branch("feature/spm-support"))
    ],
    targets: [
        .target(
            name: "MyFramework",
            dependencies: []),
        .testTarget(
            name: "MyFrameworkTests",
            dependencies: ["MyFramework", "OHHTTPStubsSwift"]),
AliSoftware commented 5 years ago

Regarding the import problem, note that OHHTTPStubs was created long ago, before Swift existed but also even before modular headers were a thing.That also led to some issues and challenges in with umbrella headers (#127 + #131) and <> vs "" imports back in the day, probably similar to the ones that were mentioned in this PR

Especially, even before Swift came around, when modular headers were added in ObjC, I started having some challenges, and I think the main reason was because the name of our module (OHHTTPStubs – coming from the name of the .framework itself) and the name of the main class (@interface OHHTTPStubs) were the same.

Another side effect of that btw is that it's impossible to specify the module in a fully-qualified name for this lib (like when you use Swift.Error as a fully-qualified name containing the name of the module – Swift in front of the type to disambiguate) because the compiler will always try to interpret OHHTTPStubs.foo as a reference to a static function or variable on the OHHTTPStubs class, not as a fully-qualified reference of a global function on the module.

So users of the lib can't use OHHTTPStubs.stub() to refer to the global method provided by OHHTTPStubsSwift extension and to disambiguate with any potential other stub() method they could have in their own codebase, because the compiler will interpret that as "the stub static method on the OHHTTPStubs class instead and spit an error (at least last time I tried a while ago that's what happened)


All this to say that having the module and the main class have the same name leads to issues in various parts. So maybe before we expose OHHTTPStubs to the SPM world it would be the right occasion to do some renaming?

Hoping that having a different name for the main class and the package/framework itself would get rid of all those issues altogether, avoid the need for the custom .modulemap and umbrella header we currently had to write in the repo back then, and get back to more modern conventions

Sure that would break the API (which is why I've postponed any renaming until now) by renaming the class. But maybe it's time, instead of continuing dragging that legacy, and I think that would not have a big impact on current users of the library anyway since I think most of our users use the Swift API not the ObjC API, so don't even refer to the OHHTTPStubs class/singleton at call sites in their code anyway.

mikelupo commented 5 years ago

Olivier, I still use objective c. But do not let that stop you from bettering the lib for the sake of a wider audience. We will adapt!

Thanks for this great library!

Mike

On Aug 21, 2019, at 1:38 PM, Olivier Halligon notifications@github.com wrote:

Regarding the import problem, note that OHHTTPStubs was created long ago, before Swift existed but also even before modular headers were a thing.That also led to some issues and challenges in with umbrella headers (#127 + #131) and <> vs "" imports back in the day, probably similar to the ones that were mentioned in this PR

Especially, even before Swift came around, when modular headers were added in ObjC, I started having some challenges, and I think the main reason was because the name of our module (OHHTTPStubs – coming from the name of the .framework itself) and the name of the main class (@interface OHHTTPStubs) were the same.

Another side effect of that btw is that it's impossible to specify the module in a fully-qualified name for this lib (like when you use Swift.Error as a fully-qualified name containing the name of the module – Swift in front of the type to disambiguate) because the compiler will always try to interpret OHHTTPStubs.foo as a reference to a static function or variable on the OHHTTPStubs class, not as a fully-qualified reference of a global function on the module.

So users of the lib can't use OHHTTPStubs.stub() to refer to the global method provided by OHHTTPStubsSwift extension and to disambiguate with any potential other stub() method they could have in their own codebase, because the compiler will interpret that as "the stub static method on the OHHTTPStubs class instead and spit an error (at least last time I tried a while ago that's what happened)

All this to say that having the module and the main class have the same name leads to issues in various parts. So maybe before we expose OHHTTPStubs to the SPM world it would be the right occasion to do some renaming?

Hoping that having a different name for the main class and the package/framework itself would get rid of all those issues altogether, avoid the need for the custom .modulemap and umbrella header we currently had to write in the repo back then, and get back to more modern conventions

Sure that would break the API (which is why I've postponed any renaming until now) by renaming the class. But maybe it's time, instead of continuing dragging that legacy, and I think that would not have a big impact on current users of the library anyway since I think most of our users use the Swift API not the ObjC API, so don't even refer to the OHHTTPStubs class/singleton at call sites in their code anyway.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

AliSoftware commented 5 years ago

Related to the issue I describe above with module name being the same as main class name: https://twitter.com/harlanhaskins/status/1168406417113378816?s=21

jeffctown commented 5 years ago

I've gotten a bit busy at work preparing for iOS 13, but I hope to get this worked out soon.

@AliSoftware I appreciate the input. I agree renaming the main class would make things easier to maintain, and now is probably a great time to do that.

I'm leaning towards the OHHTTPStubs class being renamed OHHTTPStub. Any objections here? I'd really like to stay away from the (somewhat common) practice of adding Manager to the end of it to become OHHTTPStubsManager.

AliSoftware commented 5 years ago

So, correct me if I'm wrong but in think the class is now only used by the ObjC interface/API and people using Swift don't usually use the class directly right?

So that means the requirement for the new class name still needs to be unique (as ObjC don't have the concept of module namespaces that would allow disambiguation like in Swift) right?

Saying this because I was thinking of HTTPStubs or even just Stubs … but that might violate that requirement of trying to have a unique name (not risking to clash with an existing symbol in the client app) for ObjC…

txaiwieser commented 5 years ago

updates?

bielikb commented 4 years ago

@AliSoftware HTTPStubs sounds reasonable to me, Stubs is too ambiguous on the other hand

CatalinMustata commented 4 years ago

I'm having a very strange issue with this. When I use is as a dependency to my own framework, everything is just fine. However, when I use my framework as a dependency to my application, SPM complains that my framework contains an incompatible dependency and it points to OHHTTPStubs.

I have yet to figure out what triggers it, but any pointers would help.

EDIT: Deleting the Package.resolved file from my framework seemed to fix it, so I'll write it off as a SPM bug.

EDIT 2: Figured out the issue, with help from the Swift Forums. It seems you cannot add a dependency to a project (my own framework in this case) which has a dependency targeting a branch. While completely logical, it wasn't very obvious (especially given the vague error message).

All in all, please merge this as soon as possible :D

jakeholland commented 4 years ago

Just looking for an update on the progress of this. Is there anything I can help out with?

jeffctown commented 4 years ago

This PR has gone on a bit longer than I would like, so I'd love to finish this soon.

@AliSoftware - After a GitHub search, there are actually lots of people using this class from Swift. I agree that we will need to keep the main class name unique, for Objective-C reasons.

I went ahead with pushing a few more changes:

Screen Shot 2019-10-23 at 8 22 54 AM

Remaining Tasks:

@jakeholland - if you're willing to grab any of those ^^, feel free.

@sunshinejr - I spent a while trying to figure out how to combine the SPM targets, so that people would only need to import one target module when using SPM. The real problem here is that SPM doesn't support mixed languages in one target, so we would have to consolidate this repo to use one language, Objective-C or Swift. The swizzling we use depends heavily on the Objective-C runtime, and we are using a lot of swift only classes in our swift files (AnyHashhable, etc). I don't see a reasonable way to convert the Objective-C to Swift, or the Swift to Objective-C, so I think we will have to live with requiring 2 imports when using SPM.

jeffctown commented 4 years ago

I went ahead with the dropping of OH from class names, but the diff is pretty large. I'd love any thoughts on it.

rynaardb commented 4 years ago

Any plans on getting this PR merged in anytime soon?

AliSoftware commented 4 years ago

I originally thought the class rename was good thing, but after creating it I'm not so sure anymore. I created that as a second PR, because it's a HUGE change

How would SPM work if we don't do the renaming though? Will the hack using the custom modulemap still work — contrary to our initial beliefs and original issues we had when we stated that SPM support? I think I remember that the renaming was originally decided also mainly because having module and class name being the same created issues with this SPM port, isn't that the case anymore? (If so, that's great and let's avoid the renaming then!)

jeffctown commented 4 years ago

Lol. You're right; my memory is failing me. After reading (my own) comments above we did run into issues with the module name.

The other PR is up to date and works with the rename.

rynaardb commented 4 years ago

@jeffctown Thanks and great job! 👌

jeffctown commented 4 years ago

Thanks @rynaardb and everyone else who provided feedback. I won't have another huge PR open for 6 months anytime soon. 🤣