Build Server Protocol

website·Indexed 30 days ago

https://build-server-protocol.github.io/

A protocol for IDEs and build tools that defines a standard interface for build servers and clients. It complements the Language Server Protocol (LSP) and includes specifications for server lifetime, capabilities, and versioning, with language-specific extensions for Rust, Python, Scala, C++, Java, JVM, Maven, Cargo, and sbt.

Tokens
11.7K
Snippets
58
Records
72
Agent score
72%

What's inside BSP

  1. Overview of the Build Server Protocol (BSP)

    The Build Server Protocol (BSP) is a standardized protocol that enables IDEs and build tools to communicate. It allows IDE clients to abstract over different build tools, providing a consistent way to handle directory layouts, external dependencies, compilation, testing, and debugging. BSP is designed to be complementary to the Language Server Protocol (LSP); while LSP abstracts programming languages, BSP abstracts the build systems.
  2. Understand the Build Server Protocol (BSP) architecture

    The Build Server Protocol (BSP) is designed to reduce the integration effort between IDEs (clients) and build tools (servers) by defining a common set of functionality. It is language-agnostic and can be used independently or in conjunction with the Language Server Protocol (LSP), where an LSP server may also act as a BSP client.
  3. Define and identify build targets with BuildTarget

    A BuildTarget represents metadata about an artifact (e.g., a library, test, or binary). It is designed to contain information that is computationally cheap to retrieve.

    • BuildTargetIdentifier: A unique identifier for a target using a URI. Clients should not infer metadata from the URI structure; they should use the BuildTarget object instead.
    • BuildTarget: Contains the target's ID, display name, base directory, tags for categorization, language IDs (defined by LSP), upstream dependencies, and target-specific capabilities.
    export interface BuildTargetIdentifier {
      /** The target's Uri */
      uri: URI;
    }
    
    export interface BuildTarget {
      /** The target's unique identifier */
      id: BuildTargetIdentifier;
    
      /** A human readable name for this target. */
      displayName?: string;
    
      /** The directory where this target belongs to. */
      baseDirectory?: URI;
    
      /** Free-form string tags to categorize or label this build target. */
      tags: BuildTargetTag[];
    
      /** The set of languages that this target contains. (LSP IDs) */
      languageIds: LanguageId[];
    
      /** The direct upstream build target dependencies of this build target */
      dependencies: BuildTargetIdentifier[];
    
      /** The capabilities of this build target. */
      capabilities: BuildTargetCapabilities;
    
      /** Kind of data to expect in the `data` field. */
      dataKind?: BuildTargetDataKind;
    
      /** Language-specific metadata about this target. */
      data?: BuildTargetData;
    }
  4. Understand the relationship between BSP and LSP

    The Build Server Protocol (BSP) and Language Server Protocol (LSP) are complementary. LSP manages communication between an editor (client) and a language server. BSP manages communication between a language server (acting as a client) and a build server.

    In a typical workflow, a language server may use BSP to query a build tool for metadata (such as a module's classpath) to fulfill an LSP request (such as textDocument/definition) sent by the editor.

  5. RustPackage interface

    The RustPackage interface represents a Cargo package, which is a bundle of one or more crates (at most one library crate, but any number of binary crates). It contains metadata about the package's identity, version, edition, features, and the targets it produces.
    export interface RustPackage {
      id: string;
      rootUrl: URI;
      name: string;
      version: string;
      origin: RustPackageOrigin;
      edition: RustEdition;
      source?: string;
      resolvedTargets: RustTarget[];
      allTargets: RustTarget[];
      features: FeaturesDependencyGraph;
      enabledFeatures: Set<Feature>;
      cfgOptions?: RustCfgOptions;
      env?: EnvironmentVariables;
      outDirUrl?: URI;
      procMacroArtifact?: URI;
    }
  6. Implement the BSP server startup command (argv)

    The argv field in the connection file specifies the command to start the BSP server.

    Client Requirements:

    • If the first element of argv is a relative path, the client must prepend the workspace base directory.
    • The command must be invoked in the workspace base directory.
    • The command must be invoked with the client's environment variables.

    Build Tool Requirements:

    • Must create a fresh BSP connection every time it is invoked.
    • Must use stdin for incoming messages and stdout for responses.
    • Must use stderr for reporting execution progress.
    • May use the invocation to spawn a daemon or install the tool if missing.
  7. Manage Build Server lifecycle (Initialize, Shutdown, Exit)

    The BSP lifecycle follows a pattern similar to LSP to ensure orderly startup and shutdown:

    1. Initialization: After the initialize request, the client sends the build/initialized notification. The server can use this to start intensive computations (e.g., dependency resolution). This is sent only once.
    2. Shutdown: The client sends the build/shutdown request. The server should stop its operations but must not exit yet to ensure the response is delivered.
    3. Exit: The client sends the build/exit notification. The server should exit with code 0 if build/shutdown was previously received, otherwise with code 1.
  8. Use BSP to decouple language servers from build tool integrations

    Instead of implementing custom integrations for every supported build tool to extract metadata, language servers (e.g., Metals, Dotty IDE) can implement a single BSP client. This allows them to extract build metadata from any build tool that implements a BSP server. Conversely, a new build tool only needs to implement a BSP server to provide out-of-the-box support for all BSP-compliant language servers.
  9. Install bsp4j Java library

    The ch.epfl.scala:bsp4j module is a Java library available from Maven Central. It depends on the eclipse/lsp4j library.
    // Gradle
    compile group: 'ch.epfl.scala', name: 'bsp4j', version: '2.2.0-M4.TEST'
    <!-- Maven -->
    <dependency>
        <groupId>ch.epfl.scala</groupId>
        <artifactId>bsp4j</artifactId>
        <version>2.2.0-M4.TEST</version>
    </dependency>
    // sbt
    libraryDependencies += "ch.epfl.scala" % "bsp4j" % "2.2.0-M4.TEST"
  10. Initiate a BSP server process from a connection file

    Once a valid BSP connection file is located, the client connects to the server by executing the argv invocation as a system process. The client communicates by writing to the process's system input (stdin) and listening to its system output (stdout). If the argv invocation fails, the client must display the stderr output to the user.
  11. Categorize build targets using BuildTargetTag

    Use BuildTargetTag to define the nature of a build target. This helps IDEs determine how to treat the target (e.g., whether to include it in a global build or ignore it).
    export namespace BuildTargetTag {
      export const Application = "application";
      export const Benchmark = "benchmark";
      export const IntegrationTest = "integration-test";
      export const Library = "library";
      export const Manual = "manual"; // Should only be invoked manually
      export const NoIde = "no-ide";   // Should be ignored by IDEs
      export const Test = "test";
    }
    
  12. Discover and connect to BSP servers using connection files

    BSP clients connect to servers by locating connection files. The discovery process must follow a specific priority: first search the 'bsp user location', and only if no suitable file is found, search the 'system location'.

    If multiple connection files are found, clients should use the connection metadata to filter for the desired server. If ambiguity remains, the client may prompt the user to select a server or allow the user to select multiple connections to run separate BSP builds simultaneously.

    If no connection file is found, the client may:

    1. Fail gracefully.
    2. Prompt the user to manually run the build tool command that generates the connection details.
    3. Manually detect the project's build tool, execute the generation command, and then connect.