Mobile Development 21 min read

Adopting Monorepo and Bazel for Bilibili iOS Development

Bilibili migrated its iOS codebase from a fragmented polyrepo to a unified monorepo and adopted Google‑backed Bazel, enabling clear BUILD files, atomic commits, fast parallel and cached builds, reliable dependency resolution, easy Xcode integration via Tulsi, and extensible custom rules for scalable, maintainable development.

Bilibili Tech
Bilibili Tech
Bilibili Tech
Adopting Monorepo and Bazel for Bilibili iOS Development

Background: Bilibili's iOS codebase originally used a Polyrepo (multirepo) model. As the product grew, the number of repositories exploded, leading to painful code sharing, massive copy‑paste, and high modification costs for core libraries such as the player.

These issues motivated a shift to a Monorepo architecture, where all modules live in a single repository while remaining highly modular.

What is a Monorepo? A true Monorepo is a single repository that contains multiple packages/libraries with explicit dependency relationships. It is not merely a collection of unrelated code (which would be a “Monolith Repo”).

After evaluating several tools, Bilibili chose Bazel as the build system for its iOS Monorepo.

Why Bazel? It is backed by Google, actively maintained, supports multiple languages, and provides fast, correct, and scalable builds.

Example WORKSPACE file:

workspace(name = 'bili-ios')
load('@bazel_tools//tools/build_defs/repo:git.bzl', 'git_repository')

git_repository(
    name = "build_bazel_rules_apple",
    remote = "https://github.com/bazelbuild/rules_apple.git",
    tag = "0.33.0"
)

git_repository(
    name = "build_bazel_apple_support",
    remote = "https://github.com/bazelbuild/apple_support.git",
    tag = "0.13.0"
)

load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies")
apple_rules_dependencies()

git_repository(
    name = "build_bazel_rules_swift",
    remote = "https://github.com/bazelbuild/rules_swift.git",
    tag = "0.27.0",
)

load("@build_bazel_rules_swift//swift:repositories.bzl", "swift_rules_dependencies")
swift_rules_dependencies()

load("@build_bazel_rules_swift//swift:extras.bzl", "swift_rules_extra_dependencies")
swift_rules_extra_dependencies()

Key BUILD files:

# srcs/base/network/BUILD
objc_library(
    name = "BFCNetworking",
    srcs = glob(["**/*.m", "**/*.h"]),
    hdrs = glob(["**/*.h"]),
    deps = [],
    includes = ["include"],
    visibility = ["//visibility:public"],
)
# srcs/app/anime/BUILD
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
    name = "BBAnime",
    srcs = glob(["*.swift"]),
    deps = ["//srcs/base/network:BFCNetworking"],
    visibility = ["//bilianime-shell:__pkg__"],
)
# bilianime-shell/BUILD
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")

swift_library(
    name = "main",
    srcs = ["main.swift"],
    deps = ["//srcs/app/anime:BBAnime"],
)

ios_application(
    name = "bili-anime",
    bundle_id = "tv.danmaku.bilianime",
    app_icons = glob(["Assets.xcassets/AppIcon.appiconset/*.png"]) +
                glob(["Assets.xcassets/AppIcon.appiconset/*.json"]),
    launch_storyboard = "Launch Screen.xib",
    families = ["iphone", "ipad"],
    minimum_os_version = "10.0",
    infoplists = ["info.plist"],
    deps = [":main"],
)

To build the iOS app:

bazel build //bilianime-shell:bili-anime

Bazel resolves the entire dependency graph, compiles each objc_library and swift_library into static .a archives, links them together with resources, and produces the final .ipa package.

Xcode integration : Although Bazel does not use .xcodeproj files directly, the tool Tulsi can generate an Xcode project from the WORKSPACE and target definitions, allowing developers to enjoy normal Xcode indexing, debugging, and run‑configuration.

Benefits of Monorepo + Bazel

1. Manageability

Readable BUILD files describe targets and dependencies clearly.

Source sharing is trivial – moving code to a common directory and updating BUILD files makes a library public.

Atomic commits prevent partial changes across repositories.

Bazel query commands (e.g., bazel query "deps(//bilianime-shell:bili-anime)" ) reveal full dependency trees.

Visibility attributes restrict which targets may depend on a library.

Standardized toolchains simplify onboarding new apps in the monorepo.

Versioning is eliminated for internal libraries; all apps use the latest main branch.

2. Correctness

Bazel loads all relevant BUILD files, constructs an operation graph, and builds targets in dependency order, guaranteeing reproducible results.

3. Speed

Task scheduling exploits parallelism based on the operation graph.

Local cache reuses identical build artifacts.

Remote cache shares artifacts across machines, dramatically reducing build times (e.g., App Store builds ~81 min vs. Ad‑hoc builds ~24 min).

Distributed execution (future work) can spread tasks across multiple machines.

4. Extensibility

Custom rules can be written for languages not yet supported by Bazel.

Code generation pipelines (e.g., protobuf → Swift/Obj‑C) are automated via custom rules, reducing manual errors.

Conclusion: Bazel and the Monorepo model provide Bilibili’s iOS teams with a scalable, fast, and maintainable build infrastructure. The approach balances collaboration (Monorepo) against team autonomy (Polyrepo) and is continuously evolving.

Mobile DevelopmentiOSci/cdMonorepoBuild SystemBazel
Bilibili Tech
Written by

Bilibili Tech

Provides introductions and tutorials on Bilibili-related technologies.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.