bazel-skylib

repository·main·Indexed 19 days ago

https://github.com/bazelbuild/bazel-skylib

A collection of Starlark functions and build rules providing common utilities for manipulating collections, file paths, and other data types within the Bazel build domain. It includes modules for dictionary and collection manipulation, cross-platform file and directory copying rules (copy_file, copy_directory), and common build setting rules for boolean, integer, and string flags.

Tokens
18.9K
Snippets
90
Records
110
Agent score
65%

What's inside bazel-skylib

  1. Overview of Skylib unit testing modules

    main

    Skylib provides four specialized modules for different types of testing in Bazel:

    • unittest: For testing ordinary Starlark functions.
    • analysistest: For testing analysis phase behavior (e.g., verifying a rule's providers or registered actions).
    • loadingtest: For testing loading phase behavior (e.g., macros and native.*).
    • asserts: Contains the assertion functions used within tests.
  2. Use common build setting rules from skylib

    main

    Skylib provides rules to define various types of build settings (flags and settings). These rules return a BuildSettingInfo provider containing the value of the build setting.

    Key distinction:

    • *_flag rules: Can be set via the command line (e.g., --//my/setting=value).
    • *_setting rules: Cannot be set via the command line; they are used for internal or fixed values.

    For label-typed settings, use the native Bazel label_flag and label_setting rules instead of these skylib rules.

  3. Use the Gazelle language extension for .bzl files

    main

    The Gazelle language extension in this directory allows the Gazelle generator to automatically parse bzl_library targets for all .bzl files within a repository.

    When running Gazelle with this extension:

    1. It identifies and parses valid bzl_library targets for every .bzl file.
    2. It automatically includes a deps entry in the generated BUILD files that tracks every .bzl file loaded into the primary file via load statements.

    This is particularly useful for automating the generation of documentation (e.g., using stardoc) for your .bzl files, as it ensures dependencies and targets are correctly mapped.

  4. How to structure a unit test implementation

    main

    A standard unit test implementation function must follow this lifecycle:

    1. Initialize: Call unittest.begin(ctx) as the first step. This returns a test environment struct used to collect failures.
    2. Assert: Use asserts.* functions or unittest.fail to validate logic. Pass the env returned by begin to these functions.
    3. Finalize: Call unittest.end(env) at the end of the function. This returns a list of providers required to register the test result automatically.
    def _your_test(ctx):
      env = unittest.begin(ctx)
    
      # Assert statements go here
    
      return unittest.end(env)
  5. How to write an analysis test

    main

    An analysis test verifies the behavior of a "real" rule target by examining and asserting on the providers it produces.

    Workflow:

    1. Define an implementation function that takes ctx.
    2. Call analysistest.begin(ctx) to initialize the test environment.
    3. Perform assertions using analysistest or asserts functions, passing the environment object.
    4. Call analysistest.end(env) to log results and return the necessary providers.
    5. Use analysistest.make() to wrap the implementation function into a rule.

    Note: Test rule names should end in _test.

    def _your_test(ctx):
      env = analysistest.begin(ctx)
    
      # Assert statements go here
    
      return analysistest.end(env)
    
    your_test = analysistest.make(_your_test)
  6. How the `sets` module works

    main

    The sets module provides common hash-set algorithms. Sets are implemented as structs containing values as keys in a dictionary, which requires all elements to be hashable.

    To work with sets, you can:

    • Create them using sets.make() or sets.make([elements]).
    • Check if an object is a set using types.is_set() from types.bzl.
    • Convert a set back to a list using sets.to_list(my_set).
    • Mutate sets using sets.insert() or sets.remove().
    load("@bazel_skylib//lib:new_sets.bzl", "sets")
    
    # Create a set with initial values
    my_set = sets.make(["a", "b", "c"])
    
    # Convert to list
    my_list = sets.to_list(my_set)
  7. Load and use Skylib modules in BUILD or .bzl files

    main

    Skylib organizes its Starlark functions into "modules". Each module is a struct containing related functions. To use them, load the specific .bzl file and access functions by dotting into the exported struct name.

    Example of loading the paths and shell modules:

    load("@bazel_skylib//lib:paths.bzl", "paths")
    load("@bazel_skylib//lib:shell.bzl", "shell")
    
    p = paths.basename("foo.bar")
    s = shell.quote(p)
  8. Configure the Gazelle plugin for bazel-skylib

    main

    Skylib includes a Gazelle plugin that can automatically generate bzl_library entries in your build files.

    1. In your WORKSPACE file, load and call the plugin workspace and setup functions:
    load("@bazel_skylib_gazelle_plugin//:workspace.bzl", "bazel_skylib_gazelle_plugin_workspace")
    
    bazel_skylib_gazelle_plugin_workspace()
    
    load("@bazel_skylib_gazelle_plugin//:setup.bzl", "bazel_skylib_gazelle_plugin_setup")
    
    bazel_skylib_gazelle_plugin_setup()
    1. In your BUILD.bazel file, include the plugin in your gazelle_binary definition:
    load("@bazel_gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle", "gazelle_binary")
    
    gazelle(
        name = "gazelle",
        gazelle = ":gazelle_bin",
    )
    
    gazelle_binary(
        name = "gazelle_bin",
        languages = DEFAULT_LANGUAGES + [
            "@bazel_skylib_gazelle_plugin//bzl",
        ],
    )
  9. Set up bazel-skylib in your WORKSPACE

    main

    To use Skylib modules (like unittest) in your Bazel project, you must initialize the workspace in your WORKSPACE file. This ensures that necessary toolchains are correctly configured.

    Refer to the specific release notes for your version, but the standard setup is:

    load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
    
    bazel_skylib_workspace()
  10. Troubleshoot missing toolchains for unittest

    main

    If you encounter an error stating no matching toolchains found for types @bazel_skylib//toolchains:toolchain_type while using unittest, it indicates that the Skylib workspace has not been initialized.

    Solution: Ensure you have added the following to your WORKSPACE file:

    load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
    
    bazel_skylib_workspace()
    ERROR: While resolving toolchains for target //foo:bar: no matching toolchains found for types @bazel_skylib//toolchains:toolchain_type
    ERROR: Analysis of target '//foo:bar' failed; build aborted: no matching toolchains found for types @bazel_skylib//toolchains:toolchain_type
  11. Use copy_directory_action in custom rules

    main

    The copy_directory_action function is a public API helper designed to be used within your own rule implementations. It creates an action to copy a directory from src to dst.

    Parameters:

    • ctx: The rule context.
    • src: The directory to copy (can be a source directory or a TreeArtifact).
    • dst: The destination directory. Must be a TreeArtifact.
    • is_windows: If set to True, it creates an cmd.exe action to avoid a Bash dependency on Windows. Defaults to False.
    load("@bazel_skylib//rules:copy_directory.bzl", "copy_directory_action")
    
    def my_custom_rule_impl(ctx):
        # ... implementation ...
        copy_directory_action(
            ctx = ctx,
            src = ctx.attr.src_dir,
            dst = ctx.attr.out_dir,
            is_windows = ctx.os.name == "windows",
        )
  12. Check set properties: contains and length

    main

    Use these methods to inspect a set:

    • sets.contains(a, e): Returns True if element e exists in set a.
    • sets.length(s): Returns the number of elements in set s.
    load("@bazel_skylib//lib:new_sets.bzl", "sets")
    
    s = sets.make(["apple", "banana"])
    
    sets.contains(s, "apple")  # True
    sets.length(s)              # 2