bazel-ios / rules_ios

Bazel rules for building iOS applications and frameworks
Apache License 2.0
280 stars 85 forks source link

rules_ios mono-repo for improved developer experience #495

Open jerrymarino opened 2 years ago

jerrymarino commented 2 years ago

A proposal to merge XCHammer core and Tulsi core into rules_ios to improve developer experience, consequently onboard more folks, and set precedent for coming changes.

Background

Originally rules_ios pulled rules from bazelbuild org like rules_apple and Tulsi. These rules are maintained with regard for and optimized to accept up-stream commits from a small handful of companies: e.g. copybara PRs. Using said rules was gainful in the early days of Bazel where the community was much smaller and 1-2 companies took on the majority of maintenance burden and owned architecture decisions. In the early days it was an amazing way to use Bazel with iOS development but came at a cost.

In the bazel-ios org, we're working to make an amazing iOS build system by scaling out contributors and have enlisted multiple industries as maintainers. We have all long outgrown the model where 1 or 2 companies dictated how iOS development tools worked and improvements were made by forking, binary patching, or code injecting. Because Bazel has aten the world/top app list, we now have effectively achieved this goal: more teams are improving rules_ios and iOS developers have successfully migrated to Bazel by using rules_ios.

Mainlining key dependencies to improve them

This change pulls in dependencies to iterate freely on them in tree. The main reasons reasons are architecture limitations, mono-repo developer experience, and diversifying the contributor base amongst multiple maintainers/industries.

Remove architecture restrictions and complexity

We have simplified iOS Bazel builds by reducing the surface area and supported features. By having a smaller amount of test cases we can make sure things like the debugger is compatible with the rules.

We're often focused on making the rules work better with how an exiting Xcode project works and adopting common idioms. Often features in Xcode and core compilers need heavy modification or redesigning to work at scale.

This vertical integration is why you can build with rules_ios, generate an an Xcode project, and LLDB works end to end in. We even test complicated features like this end to end. The users of rules_ios don't have the same problems as other users because of it. Importantly, we're able to create optimizations in both build system and IDE by making assumptions that you're using rules_ios. For instance, index while building V2 and virtualized frameworks. These features have a non trival end to end consideration in the rules, Bazel core, Xcode project, and remote execution configuration.

Aligning with industry standard conventions makes it easier to onboard a dev to a codebase, even when it isn't ideal to implement these features inside of Bazel or bazelbuild or repositories. The key takeaway is that making these features work helped us achieve scale and mass adoption. Said changes are often riddled with tradeoffs and complexity. Therefore, they are often zero-sum with an organization that has already re-designed code to work with Bazel or has it working well.

We are effectively locked into the architecture that bazelbuild org uses which is optimized for both a small handful of companies and upstreaming changes from copybara. In practice, the implementation of these rules has made it very difficult for broad adoption of Bazel and required hacks and forks evolve the state of the art.

Maintainer velocity and alignment

We often change dependencies to work better with the Xcode way. This is at a zero sum where bazelbuild maintainers architecting the software in a way that they can integrate with copybara. This limitation prevents us from evolving the state of the art: often, we have to design features and make tradeoffs in design that aren't aligned with the use cases of the bazelbuild org.

Xcode updates: for varying reasons, a maintainer may need to adopt Xcode sooner than another. We'll have a better shot to be up-to-date by having multiple contributors working to update Xcode in the open in a tested mono-repo. Finally, because we have a subset of supported features and test cases to do things, it's easier to maintain overall and we can more easily land commits upstream which are gated by an open source test suite and several maintainers with aligned use cases.

Mono-repo developer experience

Today it is painful to bump tags, commits, and it is significantly easier to reason about how a change will work in a mono-repo. Even bzlmod is prolific, testing rules_ios end to end is still easier in a mono-repo. Versioning has been a problem in the last several years, and we can side-step it by testing our rules in a single repository.

Conclusion

This proposal improves the developer experience, makes it easier to test, manage users, and consequently enable more iOS developers adopt Bazel.

luispadron commented 2 years ago

Thanks for writing this up Jerry! I think a lot of this makes sense, and having a monorepo certainly helps with upstreaming changes to the underlying rules / tools. I've been trying to contribute more to rules_ios and did find it difficult to understand were using a fork of XCHammer for example. If these all lived in this repository it would make it more obvious what tools we're using and how we can continue to update them.

Some questions:

Overall I'm in favor of this change and agree with a lot of what you laid out.

Some other things to maybe consider:

jerrymarino commented 2 years ago

You've got it!

How would we ensure any improvements to the main XCHammer repo also make it here?

I believe if we get the core into rules_ios folks may use our optimized version of it.

What does it mean for engineers using those other repositories, should they be informed that it wont be maintained (at least not in that repo anymore)?

The maintainers of those repositories can still support / maintain them or pull our code instead. The issue is that we want to simplify, unify, and redesign the code in a way that won't require us to hard fork. Tulsi was a long running fork because there are many changes not upstream-able and re-architecting it breaks copybara support. rules_apple is in a similar boat.

Are we at all worried about deviating from the rules_apple rulesets and what that might mean in terms of splitting up the community / splitting up our efforts?

Today as it stands, there are 3 Xcode project generators atleast: XCHammer, rules_ios, and Tulsi and I've heard about others. The original proposal is only for Xcode generator unification but I imagine the idea will improve the situation for rules_apple and eventually re-write it in tree / cut the dependency. We had to deeply integrate it into the repository and it is very difficult to update as a result. e.g. look at all the internal imports.

This splitting of the community exists because some folks tweaked their repos to work with Bazel as-is and other folks made different bazel rules to be compatible with how their builds worked. The historical issue is the two approaches are both difficult and add unwanted complexity, abstractions, when people tried to unify. IMHO splitting is beneficial now that both ways have critical mass. The overall Bazel iOS community has multiple and approaches to building an iOS app with open source build technology!

How will we ensure we keep up with issues / PRs etc? Should we set up a working group that can figure out how best to serve the community on this?

Today we use github and iterate on this in the open e.g. this issue πŸ˜‰ . It's TBD but tentatively folks are kicking around making the first ever rules_ios con later this year!

keith commented 2 years ago

Can you clarify how rules_apple and rules_swift would fit into this setup?

ob commented 2 years ago

I'd also want us to think how rules_xcodeproj fits into this picture.

I'm not clear on what the benefits of importing XCHammer and Tulsi into rules_ios are. Maybe it'd be worth elaborating more on what you have in mind... like are you talking about just copying them in and losing the history? Using git submodules? I see we already have them forked under the bazel-ios org, so explaining why that isn't sufficient to iterate on them would be a good start.

Although the rules_ios community has grown, all of us are also part of the broader community of "people who build iOS apps with Bazel" and my main concern would be that this broader community is also small enough that splitting it can divide resources and make it harder for each project to move forward.

jerrymarino commented 2 years ago

Thank you all for the thoughtful comments

Can you clarify how rules_apple and rules_swift would fit into this setup?

This proposal is mainly focused around XCHammer and Tulsi but does set precedent for others. I suspect rules_apple can benefit from the proposal as well: an in-tree version of bundling functionality that drops Copybara compatibility, is fully tested in a mono-repo, and adopts bazel-ios's community based ownership model.

In this case, in-tree rules would be written and tested as an atomic unit instead of separate parts. Folks that pull rules_apple and rules_swift today would instead pull the functionality as a single SHA that was tested as a unit along with the rest of the required functionality. We'll have end to end test coverage and more maintainers on this code. As a Bazel user pulling a single SHA is an improvement from a release and testing perspective over what we have today: a special incantation tens of git commit SHA256's /github URLS to update Xcode and generate a project that maybe work together.

As a developer and maintainer of the rules, it's easier for me to have a common set of test cases at hand, see how it all fits together in the same repo, and explain how something works in the same tree to a colleague.

I'd also want us to think how rules_xcodeproj fits into this picture.

buildbuddy github org has a different Xcode project generator for Bazel. rules_ios originally had a similar thing: create an Xcodegen generator rule like XCHammer and without Tulsi's core. Recently we had a series of PRs agains the XCHammer fork that update it to Bazel 5, fix M1 issues, add rules_ios support, and align to one generator. The additional aspect this proposal adds is a mono-repo and optimizing for community maintainers and diversified ownership.

As a Bazel user, it's easier for me to pull a single tested SHA than worry about what incantation of rules_apple, rules_swift, rules_ios, Xcode generator, and Bazel I need to unlock an Xcode update.

my main concern would be that this broader community is also small enough that splitting it

This proposal fixes splits that exist already in the community: specifically with rules_ios, XCHammer, Tulsi. Mainly, aligning rules_ios to use XCHammer and Tulsi. @thiagohmcruz might be able to weigh in. These changes may break copybara compat by re-architecting aspects of the generator to work better with the rest of the ecosystem

A decent portion of top apps and key contributors in the ecosystem use rules_ios. As a Bazel ecosystem contributor, it's easier to contribute to a mono-repo that the top apps have clearly aligned on.

I'm not clear on what the benefits of importing XCHammer and Tulsi into rules_ios are.

TDLR is it's easier for owners to iterate on and test end to end as a unit, bump a single sha instead of tens, unify efforts and diversify ownership of XCHammer/Tulsi amongst multiple maintainers/companies/industries.

The main issue with making a complex rule like a generator is having to accept PRs in the open source that lack all test cases. By developing as a unit we can more easily achieve this and have better end to end testing.

Finally, like other rules, versioning all required functionality to build a Swift/ObjC app into one mono-repo fixes many issues that exist Bazel ecosystem and further aligns key maintainers. Finally, if you need to fork it, you can fork a single repo instead of forking 10 and easily test your fork end to end.

keith commented 2 years ago

and adopts bazel-ios's community based ownership model.

To be clear rules_apple, rules_swift, apple_support, and tulsi are all community owned as well. To clarify any misconceptions I've documented the process to become a maintainer here: https://github.com/bazelbuild/rules_apple/blob/master/MAINTENANCE.md#how-to-become-a-maintainer

In this case, in-tree rules would be written and tested as an atomic unit instead of separate parts.

If you're interested in something like this I'm sure we could setup some CI integration on the other repos that automatically ran upstream rules_ios CI on your contributions to those repos. I definitely see the value in a single atomic change being fully tested downstream, but I think if you copy those rules into this repo and "drop copybara support" what I think you're saying is you're creating a hard fork for rules_ios?

The additional aspect this proposal adds is a mono-repo and optimizing for community maintainers and diversified ownership.

Maybe @brentleyjones can comment here but I know with the initial interest around rules_xcodeproj there are some folks contributing there as well, I imagine as that project matures it be community maintained similar to rules_apple and friends.

which is optimized for both a small handful of companies

The upstream rules are definitely not intended to be optimizing for a small handful of companies. I'm definitely supportive of moving things into those rules that are generally applicable! I've mentioned this in the past with headermap support, and maybe there are some other clear things that rules_ios does that also fall into that category

and upstreaming changes from copybara.

Are there examples of major breaking changes you would like us to make that have been blocked on this? We have been considering being less strict about taking upstream changes, but also so far I think we've tried to balance divergence reasonable, so if there's a case where you don't think we've made the right call I'd love that example.

jerrymarino commented 2 years ago

what I think you're saying is you're creating a hard fork for rules_ios

Yeah I'm proposing that we copy in a subset of XCHammer and Tulsi into the tree and align on that instead of keeping a fork of Tulsi, a fork of XCHammer, and rules_ios's other Xcodegen rule. People can pull a tested and up-to-date version instead of rebuilding their own. rules_ios makes Bazel for iOS an easier sell πŸ˜‰

To be clear rules_apple, rules_swift, apple_support, and tulsi are all community owned as well. To clarify any misconceptions I've documented the process to become a maintainer here: https://github.com/bazelbuild/rules_apple/blob/master/MAINTENANCE.md#how-to-become-a-maintainer

IMO we'd still need to fix the iteration speed issues that moving it in addresses: e.g. complexity issues, lack of API, dropping capybara compatibility, CI automation, and removing micro-repo layout.

I'm sure we could setup some CI integration on the other repos that automatically ran upstream rules_ios CI on your contributions to those repos

Bazel For iOS is expensive and harder to maintain because of the millions of untested feature permutations in Tulsi, rules_apple, or rules_swift. IMHO there isn't enough code amongst Tulsi, rules_ios, rules_apple, rules_swift, and xchammer have so many separate repos, CI processes, and contributor models. rules_ios doesn't need all the complex configuration that integrating the bazelbuild repos requires and the code will fundamentally simpler because we can remove a large portion of it and related abstractions required in rules_ios to use these repos.

The upstream rules are definitely not intended to be optimizing for a small handful of companies.

After "handing it off" historically the idea was Copybara PRs sent to a separate branch and 1-2 blessed maintainers cherry-picked them to the main branch. Because of this velocity of that repo rules slowed because it was 1-2 companies maintaining it. I imagine the maintainers isolated contributors in other ways. People have had to do everything ad-hoc or in forks.

Maintainers in this bazel-ios org have been continuously unable to upstream changes to bazelbuild org even to add for support basic stuff like Xcode updates. I suspect the unmerged PRs outpaced the ones we landed and then implemented in ad-hoc ways elsewhere. The ad-hoc patching is expensive for the community who wants to be on the latest tech: e.g. to use Bazel with M1 l was originally required to do it an edge-case riddled hacky way in multiple forks to pull it off or change rules_ios in a way that doesn't work with Bazel core. Case in point, the bazelbuild org maintainers didn't need support for M1 out of the gate and they weren't structured for mass contribution and wouldn't accept our changes.

know with the initial interest

I think it's amazing that other people are adding more rules to the ecosystem - the existence of alternatives and startups in the space further legitimizes the Bazel community! AFAIK making another bazel to pbxproj generator pattern does split the community further.

ob commented 2 years ago

While I agree with the desire to simplify and make the rules easier to develop on and maintain, I'm not sure having hard forks of dependencies is the way to go. Maybe you're seeing it differently than I am though... would it help to make a list of what we hope to accomplish that is more concrete? E.g. you mention:

we'd still need to fix the iteration speed issues that moving it in addresses: e.g. complexity issues, lack of API, dropping capybara compatibility, CI automation, and removing micro-repo layout.

Would it help to be more explicit about what PRs we've submitted that didn't get merged? Or what complexity we hope to eliminate? Have we tried proposing a better API for consumers of rules_apple/rules_swift? Or asking for some private providers to be made public?

I'm also a bit confused about:

Bazel For iOS is expensive and harder to maintain because of the millions of untested feature permutations in Tulsi, rules_apple, or rules_swift.

Do you mean we'd just delete this features from rules_swift? How do we decide which features to keep and which to remove?

Maintainers in this bazel-ios org have been continuously unable to upstream changes to bazelbuild org even to add for support basic stuff like Xcode updates.

It would help to have some examples of this. For instance the M1 stuff that you mention...

FWIW, I'm not trying to be difficult... in my experience hard forks end up being maintenance burdens so I'd want us to consider this carefully.

jerrymarino commented 2 years ago

Thank you for the thoughtful comments!

Do you mean we'd just delete this features from rules_swift? How do we decide which features to keep and which to remove?

For sure and unifying the existing iOS rule ecosystem to a subset mono-repo is a direct goal! We can define what's in-scope in with principals. IMO if something is used by us, has an open test case on rules_ios CI, it's beneficial to have in this repo. Otherwise fine/ideal to consider removing it. I believe such principals are necessary to maintain velocity and easily accept PRs.

Importantly, if we keep our approach aligned with how people build iOS software, then it continues to benefit us and Bazel at scale. We can employ an interface to integrate other ecosystems.

I'm not sure having hard forks of dependencies is the way to go.

I agree with you 100%. The intention behind this proposal is removing hard forks of Tulsi and XCHammer. If the updated/maintained version is moved in tree, I'd suggest to redirect/archive the originals and use our community mono-repo. We cannot force hands, however we can continue to gain momentum by making rules_ios amazing from a build performance, maintainability, and contribution perspective πŸš€

FWIW, I'm not trying to be difficult... in my experience hard forks end up being maintenance burdens

bazelbuild org has the opposite contributor model of what I am proposing. Dare I call status quo closed development/ open source. Today the maintenance burden is on the community ( e.g. mainly one person I owe tremendous gratitude ) to keep parity with and cherry pick Copybara PRs. In other-words, we are already forked. Pulling the dependencies in-tree allows us to drop un-needed-by-rules-ios-complexity and make it easier to update and integrate with for the community.

jerrymarino commented 2 years ago

Have we tried proposing a better API for consumers of rules_apple/rules_swift? Or asking for some private providers to be made public?

I would advocate to simplify the code instead. Bazel has rules, actions, and build files as a general way to compile code. Going beyond this adds more opinions and complexity to bag.

The closest thing we've seen is the ecosystem using native providers in Bazel core. These providers maintain a lot of fields and require refactoring issues on all consumers: take apple_common.ObjcProvider as one attempt. Case in point, in Bazel 5 there was a copybara PR that broke the way we used module_map field and caused issues when updating to Bazel 5. You could have argued for rules_ios's current design it should have not landed, but at the end of day it's just two peoples different opinions, and the owner of the repo has the last say.

We can always bridge external rules into rules_ios. For swift we'd need to rely on SwiftInfo provider, or just make a BUILD file that compiles code with our own rule.

Would it help to be more explicit about what PRs we've submitted that didn't get merged?

This proposal improves the developer experience by bringing it into a mono-repo and not having to worry about breaking ad-hoc test case when you submit. I think it's somewhat ortoganl

jerrymarino commented 2 years ago

For now, we have an issue to address removing forks of XCHammer and Tulsi: https://github.com/bazel-ios/rules_ios/issues/498

Most of the discourse here is on rules_apple and rules_swift so will leave this open incase anyone suggestions or proposals about rules_apple or rules_swift πŸš€