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.
What's inside 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.
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.Define and identify build targets with BuildTarget
A
BuildTargetrepresents 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
BuildTargetobject 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; }- BuildTargetIdentifier: A unique identifier for a target using a URI. Clients should not infer metadata from the URI structure; they should use the
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.RustPackage interface
TheRustPackageinterface 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; }Implement the BSP server startup command (argv)
The
argvfield in the connection file specifies the command to start the BSP server.Client Requirements:
- If the first element of
argvis 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
stdinfor incoming messages andstdoutfor responses. - Must use
stderrfor reporting execution progress. - May use the invocation to spawn a daemon or install the tool if missing.
- If the first element of
Manage Build Server lifecycle (Initialize, Shutdown, Exit)
The BSP lifecycle follows a pattern similar to LSP to ensure orderly startup and shutdown:
- Initialization: After the
initializerequest, the client sends thebuild/initializednotification. The server can use this to start intensive computations (e.g., dependency resolution). This is sent only once. - Shutdown: The client sends the
build/shutdownrequest. The server should stop its operations but must not exit yet to ensure the response is delivered. - Exit: The client sends the
build/exitnotification. The server should exit with code 0 ifbuild/shutdownwas previously received, otherwise with code 1.
- Initialization: After the
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.Install bsp4j Java library
Thech.epfl.scala:bsp4jmodule is a Java library available from Maven Central. It depends on theeclipse/lsp4jlibrary.// 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"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 theargvinvocation as a system process. The client communicates by writing to the process's system input (stdin) and listening to its system output (stdout). If theargvinvocation fails, the client must display thestderroutput to the user.Categorize build targets using BuildTargetTag
UseBuildTargetTagto 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"; }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:
- Fail gracefully.
- Prompt the user to manually run the build tool command that generates the connection details.
- Manually detect the project's build tool, execute the generation command, and then connect.