agents.md 26 KB

Jules.md - Context for AI Assisted Development

This document provides essential context and guidelines for Jules, an AI software engineering agent, to effectively understand, navigate, and make changes within the firebase-ios-sdk repository. It highlights key aspects of the development environment, coding practices, and architectural patterns.

Setup Commands

To contribute to or develop within the firebase-ios-sdk repository, an understanding of the following setup is beneficial.

Prerequisites

  1. Xcode: The development environment relies on Xcode 16.2 or above.
  2. Command-Line Tools:
    • clang-format: Used for C, C++, and Objective-C code formatting. Version 20 is specifically mentioned.
    • mint: Used to install and run Swift command-line tools, including swiftformat for Swift code styling.
    • Note: The build environment uses clang-format (version 20) and mint (for swiftformat) for code styling. Ensure any generated or modified code adheres to these styling requirements.

Development Workflows

You can develop Firebase libraries using either Swift Package Manager (SPM) or CocoaPods.

1. Swift Package Manager (SPM)

Swift Package Manager (SPM) is a primary development workflow. The project is typically opened via the Package.swift file at the repository root.

  • When working with SPM, specific library schemes (e.g., FirebaseAuth, FirebaseFirestore) are used to build and develop individual modules.
  • For test-related tasks using SPM, be aware of the ./scripts/setup_spm_tests.sh script, which configures Xcode to show schemes for testing individual packages. This script may need to be invoked if tests need to be run or modified in an SPM context.

    ./scripts/setup_spm_tests.sh
    

2. CocoaPods

CocoaPods (v1.12.0+) is another key development workflow.

  • The cocoapods-generate plugin is used to create a development workspace from a .podspec file.
  • Development workspaces for individual libraries (e.g., FirebaseStorage) are generated using a command like:

    pod gen FirebaseStorage.podspec --local-sources=./ --auto-open --platforms=ios
    
    • Replace FirebaseStorage.podspec with the podspec of the library you intend to develop. Podspecs are located in the root directory.
    • The --local-sources=./ flag tells CocoaPods to use the local checkout of the SDK.
    • --platforms can be set to ios, macos, or tvos. Note that for Xcode 10.2+, multi-platform CocoaPods workspaces might have issues, so generating for a single platform is advised by README.md.
    • If the CocoaPods cache is outdated, you might need to run pod repo update first.
  • Understanding this command structure is important if tasks involve setting up or modifying a CocoaPods-based development environment for a specific module.

  • Mac Catalyst Development:

    1. Run pod gen as above for ios.
    2. In the generated project, check the "Mac" box in the host app's "Build Settings" under Deployment.
    3. Configure signing for the host app in "Signing & Capabilities."
    4. In the "Pods" project, add signing to the host app and unit test targets.
    5. Alternatively, disable code signing by adding a user-defined setting CODE_SIGNING_REQUIRED with value NO to each relevant target's build settings.

Code Styling

Code consistency is maintained by clang-format (for C-based languages) and swiftformat (for Swift). Any code generated or modified must adhere to these standards.

  • The ./scripts/style.sh script is used to apply formatting. If a task involves code changes, this script (or the underlying tools) should be used to ensure compliance:

    ./scripts/style.sh {path_to_changed_files_or_branch_name}
    

    For example:

    ./scripts/style.sh FirebaseStorage/Sources/
    # OR
    ./scripts/style.sh my-feature-branch
    

    Running ./scripts/style.sh with no arguments will format all eligible files in the repository.

  • Failures in CI style checks often indicate that code modifications were not formatted correctly with these tools.

Firestore Specific Development

Firestore has a self-contained Xcode project. For details, refer to Firestore/README.md.

Testing

Thorough testing is essential for maintaining the quality and stability of the Firebase SDK.

General Guidelines

  • Write Tests:
    • When fixing a bug, add a test to prevent regressions.
    • When adding a new feature, include tests to validate the new or modified APIs.
  • Tests as Documentation: Well-written tests can serve as examples of how to use an API.
  • Code Coverage: Aim for good code coverage to ensure all critical paths are tested.

Running Tests

The primary method for running tests is through Xcode, after setting up your development environment using either Swift Package Manager or CocoaPods.

1. Swift Package Manager (SPM)

  1. Enable Test Schemes: If you haven't already, run the script from the repository root:

    ./scripts/setup_spm_tests.sh
    
  2. Run Tests:

    • In Xcode, select the scheme corresponding to the library or test suite you want to run (e.g., FirebaseFirestoreTests).
    • Choose the target platform (run destination) along with the scheme.
    • Press ⌘U or click the "play" arrow in the project navigation bar to run the tests.
    • Note: CONTRIBUTING.md mentions that not all test schemes might be configured to run with SPM.
Environment Variables in SwiftPM

The following are environment variables to modify sources of code that can help with testing specific frameworks or dealing with version mismatches.

  • FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: When passed, sets the dependency on the https://github.com/google/GoogleAppMeasurement.git package to use the main branch. This is done to keep the Firebase package's main branch building when it depends on unreleased changes in the GoogleAppMeasurement package's main branch. Use it when you run into version issues.
  • FIREBASECI_USE_LOCAL_FIRESTORE_ZIP: When passed, looks for a local FirebaseFirestoreInternal.xcframework framework at the root of the repo to resolve the Firestore target. Used for testing incombination with scripts/check_firestore_symbols.sh.
  • FIREBASE_SOURCE_FIRESTORE: When passed, builds Firestore's large C++ dependencies (abseil, gRPC, BoringSSL) from source rather than, by default, using existing binaries.

To enable an env var within Xcode, quit the current Xcode instance, and open it from the command line, passing each desired env var with the --env argument.

open --env FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT --env FIREBASE_SOURCE_FIRESTORE \
Package.swift

To unset the env vars, quit the running Xcode instance.

2. CocoaPods

  1. Generate Workspace: After generating a workspace using pod gen ... as described in the "Setup Commands" section.
  2. Run Tests:
    • Open the generated .xcworkspace.
    • Select the appropriate scheme for the unit tests of the library (e.g., FirebaseStorage-Unit-unit).
    • Press ⌘U or click the "play" arrow to run tests.

Code Coverage

  • Enable in Xcode:
    1. Go to Product → Scheme ➞ Edit Scheme... (or use ⌥⌘U).
    2. Select the "Test" action in the sidebar.
    3. Go to the "Options" tab.
    4. Check the "Code Coverage" box.
  • Coverage Report Tool: The repository includes a tool for generating more detailed coverage reports. Refer to its documentation: scripts/code_coverage_report/README.md.

Handling GoogleService-Info.plist in Tests

Configuration files like GoogleService-Info.plist are necessary for Firebase SDKs to connect to a backend project. How these are handled in tests varies:

  • Unit Tests:
    • Unit tests should generally not rely on a live backend or a real GoogleService-Info.plist.
    • Where configuration is needed (e.g., for FirebaseApp.configure()), tests often use mock or fake GoogleService-Info.plist files with dummy values. These are typically included directly in the test target's resources. Be aware of this pattern if tests seem to require Firebase app initialization.
  • Integration Tests & Sample Apps:
    • Integration tests and sample apps, which do interact with live backends, require valid GoogleService-Info.plist files.
    • In the CI (Continuous Integration) environment, these files are usually provided securely (e.g., via environment variables, mounted secrets, or dedicated build steps that place the correct plist). As an AI agent, you typically won't manage these CI secrets directly.
    • If integration tests fail due to configuration issues, it might relate to how the CI environment provides this plist.
    • For local execution of sample apps or some integration tests (if specifically requested for a task), a valid plist obtained from the Firebase console for a test project would be needed, as described in the original README.md. However, direct interaction with the Firebase console to download plists is outside typical AI agent operations unless explicitly guided.

Product-Specific Testing Notes

  • Firebase Database:
    • Integration tests can run against a local Database Emulator or a production instance.
    • Emulator: Run ./scripts/run_database_emulator.sh start before executing tests.
    • Production: Provide a valid GoogleService-Info.plist (copied to FirebaseDatabase/Tests/Resources/GoogleService-Info.plist) and ensure security rules are public during the test run.
  • Firebase Storage:
    • For integration tests, follow instructions in FirebaseStorage/Tests/Integration/StorageIntegration.swift.
  • Push Notifications (General for Messaging, etc.):
    • Cannot be tested on the iOS Simulator; requires a physical device.
    • Requires specific App ID provisioning in the Apple Developer portal.
    • Upload your APNs Provider Authentication Key or certificate to the Firebase Console.
    • Ensure the test device is registered in your Apple Developer account.

API Surface

Designing and maintaining a consistent and developer-friendly API is critical for the Firebase SDK. Adherence to established guidelines is important for a good user experience and should be followed when tasks involve API modifications.

Guiding Principles for New APIs

The primary reference for API design is docs/firebase-api-guidelines.md. Key principles for new API designs include:

  • Swift-First: New APIs should be designed and implemented in Swift. Objective-C APIs require strong justification. Generated Swift interfaces for Objective-C APIs should be manually refined for Swift idioms if necessary.
  • Swift Code Samples: Swift code samples should be prioritized in API proposals and documentation.
  • Async/Await: New asynchronous APIs should use Swift's async/await. Callback-based APIs require justification (though callbacks are still appropriate for event handlers).

    // Preferred
    public func fetchData() async throws -> Data { /* ... */ }
    
    // Pre-Swift Concurrency (No longer preferred for new async APIs)
    public func fetchData(completion: (Data?, Error?) -> Void) { /* ... */ }
    
  • Sendable Types: New APIs should be Sendable where applicable, to allow safe use in concurrent contexts (e.g., within a Task).

  • Access Control:

    • public access level should be used for new Swift APIs rather than open.
    • New Swift classes should generally be final to prevent subclassing, unless subclassing is an intended design feature.
  • API Availability:

    • The @available attribute is used to specify platform and version compatibility (e.g., @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)).
    • The minimum supported versions for SPM are often tied to Analytics/Crashlytics. For other APIs, the product's CocoaPods podspec is the reference for versioning in @available.
  • Constants:

    • Constants in Swift should be defined within case-less enums (prevents instantiation).
    • The "k" prefix for Swift constants should be avoided.

      public enum NetworkConstants {
      public static let httpPostMethod = "POST"
      }
      
  • Minimize Optionals: While Swift's optionals provide type safety for nullability, their overuse should be avoided to prevent complicating call sites.

  • Structs vs. Enums for Extensibility:

    • For sets of values that might expand in the future, structs with static factory methods are preferred over enums. Adding a case to a public Swift enum is a breaking change.
    • See docs/firebase-api-guidelines.md (links to PRs #13728, #13976) for examples.
  • Avoid Any, AnyObject, NS-Types: Any, AnyObject, or Objective-C NS-prefixed types (like NSString, NSDictionary) should not be used in public Swift APIs. Swift native types (String, Dictionary) are preferred, and heterogeneous collections should be modeled with enums or structs for type safety.

  • Documentation: New APIs must be documented using Swift-flavored Markdown. Xcode's ⌥ ⌘ / shortcut can generate documentation block structure.

  • Naming Conventions:

    • Clarity and expressiveness should be prioritized (e.g., fetchMetadata() over fetch() or getMetadata()).
    • Consistency with existing Firebase APIs must be maintained.
    • Refer to Swift's API Design Guidelines.
  • Error Handling:

    • APIs that can fail should be marked with throws.
    • Public error types conforming to Swift's Error protocol should be defined and thrown.
  • Objective-C (If Necessary):

    • Defining typedefs in public Objective-C headers should be avoided as they obscure the underlying type in the generated Swift interface. Full types should be used.

Header Management and Imports

The document HeadersImports.md provides detailed guidelines. Key points include:

  • Public Headers:
    • Define the library's public API.
    • Located in FirebaseFoo/Sources/Public/FirebaseFoo/.
    • Additions are minor version updates; changes/deletions are major.
  • Public Umbrella Header:
    • A single header including the library's full public API (e.g., FirebaseFoo/Sources/Public/FirebaseFoo/FirebaseFoo.h).
  • Private Headers:
    • Located in FirebaseFoo/Sources/Private/.
    • Available to other libraries within the firebase-ios-sdk repo but NOT part of the public API.
    • For CocoaPods, these are included in source_files, not private_header_files.
  • Interop Headers:
    • Special private headers for defining interfaces between libraries (see Interop/FirebaseComponentSystem.md).
  • Private Umbrella Header:
    • Includes public API + private APIs for other in-repo libraries. Package manager complexities should be localized here.
  • Library Internal Headers:
    • Only used by the enclosing library, located among its source files.
  • Import Styles:
    • Within the same library: Use repo-relative paths (e.g., #import "FirebaseFoo/Sources/Internal/MyInternalHeader.h").
      • Exception: Public headers importing other public headers from the same library should use unqualified imports (#import "AnotherPublicHeaderInFoo.h") to avoid module collisions.
    • Private Headers from other libraries: Import the private umbrella header (e.g., #import "FirebaseCore/Extension/FirebaseCoreInternal.h").
    • External Dependencies:

      #if SWIFT_PACKAGE
      @import GTMSessionFetcherCore;
      #else
      #import <GTMSessionFetcher/GTMSessionFetcher.h>
      #endif
      

This structure ensures that APIs are well-defined, discoverable, and maintainable across different build systems and for various Firebase products.

Best Practices

Adhering to best practices ensures code quality, maintainability, and a positive developer experience for both contributors and users of the Firebase SDK.

  • Contribution Guidelines: The CONTRIBUTING.md file contains detailed information on the development workflow, pull requests, coding styles, and environment setup, which should be adhered to.
  • Code of Conduct: All contributions and interactions must align with the Code of Conduct.
  • Swift-First Development: New features and APIs should prioritize Swift, as outlined in the API guidelines. Objective-C is used only with strong justification.
  • Modularity: The SDK's modular design, with each Firebase product in its own library/pod (e.g., Auth, Firestore, Storage), promotes separation of concerns. Changes should generally be focused within a product's dedicated directory.
  • Dependency Management:
    • Internal dependencies (between Firebase pods) are managed via version specifications in .podspec files or Package.swift.
    • External dependencies are also declared here. The impact of adding new external dependencies requires careful consideration.
  • Central FirebaseApp (FIRApp):
    • FirebaseApp (Swift) / FIRApp (Objective-C) is the central class for initializing and configuring Firebase.
    • It provides access to project-level configurations from GoogleService-Info.plist.
  • Component System (FIRComponent):
    • An internal component system (FIRComponent, FIRComponentContainer) is used for registering and discovering Firebase services at runtime.
    • This enables a decoupled architecture. New top-level Firebase pods often require registration with this system (see docs/AddNewPod.md and FIRApp.m).
  • GoogleService-Info.plist (in relation to SDK code):
    • While handling GoogleService-Info.plist in tests has specific considerations (see "Handling GoogleService-Info.plist in Tests"), SDK code itself relies on FirebaseApp to provide project configurations derived from such a file.
  • -ObjC Linker Flag:
    • Awareness of the -ObjC "Other Linker Flags" setting is important, as it's necessary for applications using Firebase Analytics (and often transitively, other Firebase SDKs) to correctly link Objective-C categories.
  • Changelogs: CHANGELOG.md files (root and product-specific) must be updated with meaningful descriptions of changes for any pull request.
  • Minimize Breaking Changes: Breaking changes are avoided if possible and require careful consideration, typically aligning with major version releases.
  • Platform Support: Different levels of support exist for Apple platforms (macOS, Catalyst, tvOS, visionOS, watchOS). @available and conditional compilation (#if os(iOS)) are used. visionOS and watchOS are community-supported.
  • Header Hygiene: Guidelines in HeadersImports.md must be followed strictly for compatibility.
  • Testing: Comprehensive testing (unit, integration) is a core best practice. Tests must cover new code and bug fixes.

Adherence to these practices helps maintain the quality and robustness of the Firebase Apple SDKs.

Common Patterns

Recognizing the following common design and implementation patterns, prevalent throughout the Firebase Apple SDKs, facilitates understanding the existing codebase and developing new features consistently.

  • Async/Await and Sendable for Swift:
    • New asynchronous Swift code predominantly uses async/await for cleaner, more readable control flow.
    • Types involved in concurrent operations are increasingly expected to conform to Sendable to ensure thread safety, especially with Swift 6's stricter concurrency checking.
  • Case-less Enums for Constants:

    • Swift code uses case-less enums to group related constants, preventing instantiation and providing a clear namespace.

      public enum StorageConstants {
      public static let maxUploadRetryTime: TimeInterval = 600.0
      public static let defaultChunkSize: Int64 = 256 * 1024
      }
      
  • Structs for Extensible Enum-like Values:

    • When a set of predefined values (akin to enum cases) might need to be expanded without causing breaking changes, structs with static factory methods are often used. This avoids the issue where adding a new case to a public enum breaks client switch statements. (See docs/firebase-api-guidelines.md for context).
  • Protocol Buffers (Protobuf / Nanopb):

    • Several modules (e.g., Crashlytics, Firestore, InAppMessaging, Performance, Messaging) utilize Protocol Buffers for data serialization.
    • This involves .proto files defining data structures and scripts (e.g., generate_protos.sh) for compilation into Swift or Objective-C (often Nanopb for C/Objective-C). Due to their complexity and impact on build processes, introducing Protobufs to new areas of the SDK should be avoided unless that area already heavily relies on them or it's a specific requirement for interacting with an existing Protobuf-based system.
  • API Visibility Layers (Public/Private/Internal):

    • A clear distinction is maintained for API visibility:
      • Public: APIs intended for end-users, found in Sources/Public/ directories. Changes here are subject to semantic versioning.
      • Private: APIs intended for use by other Firebase SDKs within this repository but not for public consumption. Often found in Sources/Private/ or Interop/ directories.
      • Internal/Project: Code and headers used only within the same library/module. These are typically co-located with the module's other source files.
  • Umbrella Headers:

    • Public Umbrella Header: Each library typically has a main public header (e.g., FirebaseFoo.h) that includes all other public headers for that library, simplifying imports for Objective-C users.
    • Private Umbrella Header: Some libraries may have an internal or extension umbrella header (e.g., FirebaseCoreInternal.h) to expose necessary private APIs to other Firebase libraries.
  • Module-Specific Prefixes (Objective-C):

    • Objective-C class names are typically prefixed to avoid collisions (e.g., FIR for Firebase Core, FST for Firestore, FIRCLS for Crashlytics). This is less of a concern in Swift due to namespacing with module names.
  • Error Handling Conventions:

    • Swift: Adopts the Error protocol, with specific error enums/structs per module.
    • Objective-C: Uses NSError** parameters and standard error domains (often module-specific).
  • Delegate Pattern (Objective-C & Swift):

    • Commonly used for callbacks and event notifications, especially in older Objective-C APIs or when multiple distinct events need to be communicated.
  • Completion Handlers (Callbacks):

    • While async/await is preferred for new Swift asynchronous operations, completion handlers remain prevalent in many existing Objective-C and Swift APIs. These typically take (ResultType?, Error?) or similar parameters.
  • Singletons and Shared Instances:

    • Many Firebase services offer a shared instance or singleton accessor (e.g., FirebaseApp.app(), Firestore.firestore(), Auth.auth()).
  • Resource Bundles:

    • SDKs requiring UI components or other resources (e.g., Firebase In-App Messaging) may package them into .bundle files.
  • Use of dispatch_once / Lazy Initialization for Singletons (Objective-C):

    • This is a common Objective-C pattern for ensuring singleton instances are initialized safely and only once. Swift employs static let properties for a similar outcome.

Understanding these patterns is key to navigating the codebase and contributing effectively.

External Dependencies

The Firebase Apple SDK integrates several external open-source libraries to provide its functionality. These dependencies are managed as part of the overall SDK build and release process, primarily through CocoaPods and Swift Package Manager configurations.

Examples of such dependencies include (but are not limited to):

  • gRPC (for Firestore and other components)
  • LevelDB (for Firestore local persistence)
  • nanopb (for Protocol Buffer serialization in C/Objective-C contexts)
  • abseil-cpp (common C++ libraries)
  • GTMSessionFetcher (for networking)

When working on tasks for this repository, do not introduce new external dependencies unless explicitly instructed to do so by the user. Adding and managing dependencies in a large SDK like this involves careful consideration of binary size, licensing, potential conflicts, and long-term maintenance. The existing dependencies are curated and managed by the Firebase team. If a task seems to require functionality that might suggest a new library, discuss alternatives or confirm the need for a new dependency with the user first.

Updating agents.md

This document (agents.md) is intended as a living guide to assist AI agents (like Jules) in understanding and contributing to the firebase-ios-sdk repository. As the SDK evolves, new patterns may emerge, best practices might be updated, or setup procedures could change.

Guideline for Future Tasks: If, during the course of completing new tasks, you identify:

  • New common patterns or architectural decisions.
  • Changes to the build, test, or setup process.
  • Updates to API design philosophies or best practices.
  • Any other information that would be beneficial for an AI agent to have context on this repository.

Please consider updating this agents.md file to reflect those new findings. Keeping this document current will improve the efficiency and accuracy of future AI-assisted development.