xcode-build-server

repository·master·Indexed 21 days ago

https://github.com/solawing/xcode-build-server

A Build Server Protocol implementation that integrates Xcode projects with sourcekit-lsp. It enables full language support (Swift, C, C++, ObjC) in editors like VS Code by extracting compile flags from Xcode build logs via the config and parse commands.

Tokens
1.9K
Snippets
5
Records
9
Agent score
26%

What's inside xcode-build-server

  1. Manually parse Xcodebuild logs

    master

    If you are not using the Xcode GUI to build, you can manually extract compile information from xcodebuild logs. This creates a .compile file and updates buildServer.json with kind: manual.

    Usage

    From a build log file

    xcode-build-server parse [-a] <build_log_file>

    From a piped command

    <command_to_generate_build_log> | xcode-build-server parse [-a]

    Examples

    Using xcodebuild directly:

    xcodebuild -workspace *.xcworkspace -scheme <XXX> -configuration Debug build | xcode-build-server parse [-a]

    Using clipboard (if log is copied from Xcode UI):

    pbpaste | xcode-build-server parse [-a]

    Important Notes

    • First Run: Ensure the log is complete, otherwise files may lack correct flags.
    • Incremental Updates: If you add files or change build settings (SDK, Debug/Release, macros), use the -a flag to append new flags without removing existing ones.
    • Viewing Raw Output: To see xcodebuild output while parsing, use tee: xcodebuild ... | tee build.log; xcode-build-server parse -a build.log >/dev/null 2>&1
    xcodebuild -workspace *.xcworkspace -scheme <XXX> -configuration Debug build | xcode-build-server parse [-a]
  2. Bind to Xcode via config command

    master

    To integrate Xcode with sourcekit-lsp, use the config command in your workspace directory. This creates or updates a buildServer.json file with kind: xcode, instructing the server to watch and use compile flags from the newest Xcode build log.

    Note: The directory where buildServer.json is created must be the root/working directory of your LSP.

    Usage

    Run one of the following commands in your workspace:

    # For workspaces
    xcode-build-server config -workspace *.xcworkspace -scheme <XXX>
    
    # For projects
    xcode-build-server config -project *.xcodeproj -scheme <XXX>
    • *.xcworkspace or *.xcodeproj should be unique. If there is only one, you can omit these flags.
    • -scheme <XXX> can be omitted to automatically bind the latest scheme build result.

    If compile information becomes outdated, simply build your project in Xcode to refresh the flags.

    xcode-build-server config -workspace *.xcworkspace -scheme <XXX>
  3. Install xcode-build-server

    master

    Prerequisites

    • Python 3.9 or newer (standard on recent macOS).

    Installation Methods

    brew install xcode-build-server

    Option 2: Macports

    sudo port install xcode-build-server

    Option 3: Manual Git Installation

    Clone the repository and create a symbolic link to your bin folder:

    git clone "https://github.com/SolaWing/xcode-build-server.git" && ln -s "$PWD"/xcode-build-server/xcode-build-server /usr/local/bin
    brew install xcode-build-server
  4. How the Xcode Build Server manages state and configuration

    master

    The server uses a State object to manage the lifecycle of a build session. It relies on a buildServer.json file located in the project root to configure its behavior.

    Key aspects of state management:

    • Configuration: Loaded from buildServer.json via the ServerConfig class.
    • Compile Files: The server identifies a .compile file (either manually specified or automatically generated by Xcode based on the scheme and build root) to extract compiler flags.
    • Observation: A background thread monitors changes to the configuration file and the compile file. When changes are detected, the server re-initializes its internal state and notifies the LSP client.
    • Thread Safety: State shared between the main thread and the background observation thread is protected by a mainlock. Changes to shared state are performed within sync_compile_file, which blocks other threads to ensure consistency.
  5. Troubleshoot xcode-build-server issues

    master

    Standard Library Loading Failed

    If you encounter errors like Loading the standard library failed, ensure your build toolchain and sourcekit-lsp versions are consistent.

    • Use xcode-select to switch toolchains.
    • Use xcrun sourcekit-lsp to ensure you are using the LSP version corresponding to your active toolchain.

    Cross-file References Not Working

    This is often caused by an incorrect build_root in buildServer.json.

    • Incorrect: "build_root": "/Users/yourusername"
    • Correct: "build_root": "/Users/<yourusername>/Library/Developer/Xcode/DerivedData/<project>-<hash>"

    To fix this, run the following command in the root of your Xcode project:

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

    Outdated Compile Info

    If the LSP stops working after environment changes (new files, switching SDKs, etc.), repeat the config or parse steps to refresh the flags. If using sourcekit-lsp, remember that it uses indexing while building; if definitions/references are missing, perform a full build to update the index.

  6. Configure the server using buildServer.json

    master

    The server uses a buildServer.json file in the project root to define its operating mode. The kind key determines how the server locates compiler information:

    • xcode: The server automatically generates a unique compile file path based on the build_root (hashed) and the specified scheme. This isolates build information for different schemes.
    • Manual: If the kind is not xcode, the server looks for a .compile file in the project root.

    Other relevant configuration keys include build_root (the Xcode build directory) and scheme (the Xcode build scheme).

  7. Use sourceKitOptions to provide compiler flags to LSP

    master

    The server provides compiler arguments and working directory information to the LSP client (like sourcekit-lsp) through the textDocument_sourceKitOptions method. This allows the IDE to understand the build context for specific files.

    When requested, the server:

    1. Resolves the file path from the URI.
    2. Retrieves flags from the .compile file using GetFlags.
    3. If flags are missing for a .swift file, it attempts to infer them using InferFlagsForSwift.
    4. Returns a JSON object containing compilerArguments and workingDirectory.
    {
      "jsonrpc": "2.0",
      "id": 1,
      "result": {
        "compilerArguments": ["-isysroot", "/path/to/sdk", "-fmodules"],
        "workingDirectory": "/path/to/project"
      }
    }
  8. Register files for change tracking

    master

    To ensure the server can notify the LSP when compiler flags change (e.g., after a new build), the client must register files using the textDocument_registerForChanges method.

    • When the action is register, the URI is added to the observed_uri set.
    • When the action is unregister, the URI is removed.

    When the background thread detects a change in the .compile file, the server iterates through all observed_uri entries and sends a build/sourceKitOptionsChanged notification for any files whose flags may have changed.

  9. Implement the Build Server Protocol (BSP) via server_api()

    master

    The server_api() function acts as the primary entrypoint for the Build Server Protocol implementation. It returns a dictionary of functions (mapped from JSON-RPC methods) that the server dispatches to when receiving requests.

    To implement or extend the server, you interact with the functions returned by server_api(). The dispatch mechanism converts JSON-RPC method names (e.g., build/shutdown) into Python function calls (e.g., build_shutdown).

    # The server dispatches calls by replacing '/' with '_'
    # Example: 'build/shutdown' calls 'build_shutdown(message)'
    dispatch = server_api()
    
    # Internal dispatch logic:
    handler = dispatch.get(message["method"].replace("/", "_"))