Starlark Documentation

repository·master·Indexed 25 days ago

https://github.com/bazelbuild/starlark

Starlark is a deterministic, hermetic configuration language and Python dialect designed for build systems like Bazel. It provides safe, parallelizable scripting capabilities by enforcing rules such as data freezing for thread-safety, static name resolution, and restrictions on top-level control flow and unbounded loops. The language is designed for short-lived programs that express configuration without external side effects, featuring a minimal set of concepts without user-defined types, inheritance, or exceptions.

Tokens
16.3K
Snippets
45
Records
74
Agent score
83%

What's inside Starlark

  1. Overview of Starlark

    master

    Starlark (formerly Skylark) is a deterministic, hermetic, and parallel-friendly configuration language. It is a dialect of Python, featuring a highly readable syntax, dynamic typing, high-level data types, first-class functions with lexical scope, and garbage collection.

    Key characteristics include:

    • Deterministic evaluation: Executing the same code twice yields the same results.
    • Hermetic execution: Code cannot access the file system, network, or system clock, making it safe to execute untrusted code.
    • Parallel evaluation: Modules can be loaded in parallel because shared data is immutable.
    • Simplicity: Designed to be easy to read and write with a minimal number of concepts.
  2. Overview of Starlark language characteristics

    master

    Starlark is an untyped, dynamic configuration language that is a strict subset of Python. It is designed for short-lived programs that express configuration without external side effects.

    Key characteristics include:

    • Deterministic and Hermetic: Executing the same file with the same interpreter always yields the same result. By default, user code cannot interact with the environment.
    • Safe for Parallelism: Shared data structures become immutable due to freezing, preventing data races in highly parallel applications.
    • Finite Execution: To ensure termination, the language does not allow recursion or unbounded loops.
    • Simplicity: There are no user-defined types, inheritance, reflection, or exceptions.
  3. Understand the scope of the Starlark design process

    master

    The Starlark design process covers the core language and its evaluation, but excludes specific Bazel rules and language-specific APIs. Use this to determine if a change follows the Starlark process or the standard Bazel review process.

    In Scope

    • The Starlark language specification.
    • Evaluation of BUILD and .bzl files.
    • Functions, objects, and methods available in BUILD and .bzl files (excluding native rules and language-specific APIs).
    • Examples: glob(), rule(), ctx.actions(), depset().

    Out of Scope

    • Rules built in Bazel (e.g., cc_library, java_binary(), genrule(), repository_rule()).
    • Language-specific APIs (e.g., js_common, java_common, apple_common).
    • Evaluation of WORKSPACE files.
  4. Understand Starlark Lexical Elements

    master

    Starlark syntax is a strict subset of Python syntax, meaning tools designed for Python AST can often be used with Starlark files. A Starlark program consists of one or more modules, each defined by a single UTF-8-encoded text file.

    Key lexical components include:

    • White space: Spaces, tabs, carriage returns, and newlines. While spaces/tabs delimit tokens within a line, newlines and leading spaces are significant for indentation.
    • Comments: Started by # and extending to the end of the line.
    • Identifiers: Sequences of Unicode letters, decimal digits, and underscores (_) that do not start with a digit.
    • Keywords: Reserved words that cannot be used as identifiers (e.g., def, if, return, load).
  5. Identify Starlark built-in data types

    master

    Starlark supports several core built-in data types. Understanding these is essential for knowing which operations (like arithmetic or indexing) are available to your variables.

    Core types include:

    • NoneType: The type of None.
    • bool: Boolean values (True, False).
    • int: Signed integers of arbitrary magnitude.
    • float: IEEE 754 double-precision floating-point numbers.
    • string: Unicode text (UTF-8 or UTF-16).
    • bytes: Byte strings for binary data.
    • list: Mutable sequences of values.
    • tuple: Unmodifiable sequences of values.
    • dict: Associative mappings (key-value pairs).
    • set: Collections of unique values.
    • function: Function objects.
  6. Understand Starlark module execution and immutability

    master

    Each Starlark file defines a module, which is a mapping of global variable names to their values. When a file is executed (directly or via load), a new Starlark thread is created to execute all top-level statements.

    Key execution rules:

    • Control Flow: Since if and for statements cannot exist outside of functions, execution flows strictly from top to bottom.
    • Immutability: Once execution reaches the end of the file, module initialization is complete and all global variables are frozen. Subsequent mutation of these globals is impossible.
    • Isolation: Module initialization always occurs in a new thread. Therefore, thread state is never carried from a higher-level module into a lower-level one, ensuring initialization is independent of the caller.
  7. Use pass statements for empty code blocks

    master

    A pass statement does nothing. Use it when the syntax requires a statement but no behavior is required, such as the body of a function that does nothing or as a placeholder in a loop.

    def noop():
       pass
    
    def list_to_dict(items):
      # Convert list of tuples to dict
      m = {}
      for k, m[k] in items:
        pass
      return m
  8. Understand Starlark mutability and freezing

    master

    Starlark supports parallel evaluation, so it enforces thread-safety through data freezing. While lists and dicts are mutable during local evaluation, they are frozen once they become visible to another execution thread (e.g., when exported from a module). Once frozen, any attempt to mutate them (like calling .append()) will result in a runtime error.

    Additionally, a function can only mutate a variable if it is called within the same module before that variable is frozen. If an external module calls a function that attempts to mutate a local variable, a runtime error occurs.

  9. Use Bytes Literals

    master

    Bytes literals denote a bytes value and are created by prefixing string literals with b. They support raw prefixes (br or rb). Non-escaped text in a bytes literal is UTF-8 encoded.

    Key differences from strings:

    • Octal/Hex escapes: Can specify any byte value from \000/\x00 to \377/\xFF.
    • Unicode escapes: \uXXXX or \UXXXXXXXX denote the byte sequence of the UTF-8 encoding of that code point.
    b"abc"
    b"""abc"""
    br"abc"
    rb"abc"
  10. Use parenthesized expressions and tuples

    master

    Parentheses can be used for clarity or to override default operator precedence. Empty parentheses or parentheses containing a comma-separated list of expressions yield a tuple.

    Note: Starlark (like Python 3) does not allow unparenthesized tuples or lambda expressions as the operand of a for clause in a comprehension.

    1 + 2 * 3 + 4                   # 11
    (1 + 2) * (3 + 4)               # 21
    
    ()                              # (), the empty tuple
    (1,)                            # (1,), a tuple of length 1
    (1, 2)                          # (1, 2), a 2-tuple or pair
    
    # Error cases in comprehensions:
    [2*x for x in 1, 2, 3]         # parse error: unexpected ','
    [2*x for x in lambda: 0]       # parse error: unexpected 'lambda'
  11. Use zero-based and half-open indexing

    master

    Starlark uses zero-based indexing and half-open indexing for subsequences.

    Basic Indexing

    • The first element is index 0.
    • The last element of a sequence of length n is n-1.
    • Accessing an index out of range for a single element (e.g., a[i]) raises an error.

    Subsequences (Slicing)

    For a[i:j], the first index i is inclusive and the second index j is exclusive. The length of the resulting subsequence is j - i.

    Negative Indices

    Negative integers can be used to address elements from the end of the sequence:

    1. The length of the sequence is added to the negative value.
    2. For subsequences, if the resulting index is still negative, it is truncated to 0. If it is greater than the length n, it is truncated to n.

    Note: Truncation does not apply to individual element indexing (e.g., a[-6] may still raise an error if the index is out of bounds).

    "hello"[0]          # "h"
    "hello"[4]          # "o"
    "hello"[5]          # error: index out of range
    "hello"[1:4]        # "ell"
    "hello"[1:]         # "ello"
    "hello"[:4]         # "hell"
    "hello"[-1]         # "o"
    "hello"[-3:-1]      # "ll"
    "hello"[-1000:1000] # "hello"
    "hello"[-6]         # error: index out of range