cppdap: C++ Implementation of the Debug Adapter Protocol

repository·main·Indexed 41 days ago

https://github.com/google/cppdap

A C++11 library providing type-safe structures and a type-safe API for building Debug Adapter Protocol (DAP) clients and servers. It includes full DAP specification coverage, support for custom protocol messages, and flexible JSON serialization options including nlohmann/json, RapidJSON, and JsonCpp.

Tokens
2.8K
Snippets
11
Records
17
Agent score
51%

What's inside cppdap

  1. Build cppdap on Linux and macOS

    main

    To build cppdap on Linux or macOS, use CMake to generate build files and then use make.

    1. Create a build directory and run CMake:
      mkdir build
      cd build
      cmake .. [FLAGS]
    2. Build the project:
      make

    Available CMake flags:

    • -DCPPDAP_BUILD_TESTS=1: Builds the cppdap unit tests.
    • -DCPPDAP_BUILD_EXAMPLES=1: Builds the cppdap examples.
    • -DCPPDAP_INSTALL_VSCODE_EXAMPLES=1: Installs examples as Visual Studio Code extensions.
    • -DCPPDAP_WARNINGS_AS_ERRORS=1: Treats all compiler warnings as errors.
    cd <path-to-cppdap>
    mkdir build
    cd build
    cmake .. -DCPPDAP_BUILD_TESTS=1
    make
  2. Fetch dependencies for cppdap

    main

    cppdap uses git submodules for its primary dependencies: nlohmann/json and googletest. You must initialize these submodules before building.

    To fetch the dependencies, run:

    cd <path-to-cppdap>
    git submodule update --init
  3. Integrate cppdap into your CMake project

    main

    You can include cppdap in your own project using add_subdirectory(). This defines the cppdap library target, which you can then link to your own targets.

    Basic Integration

    set(CPPDAP_DIR <path-to-cppdap>)
    add_subdirectory(${CPPDAP_DIR})
    
    target_link_libraries(<target> cppdap)

    Customizing Dependency Paths

    You can specify custom paths for third-party libraries by setting these variables before calling add_subdirectory():

    • CPPDAP_THIRD_PARTY_DIR: The root directory for third-party libraries (defaults to ${CPPDAP_DIR}/third_party).
    • CPPDAP_JSON_DIR: Path to the JSON library (defaults to ${CPPDAP_THIRD_PARTY_DIR}/json).
    • CPPDAP_GOOGLETEST_DIR: Path to googletest (defaults to ${CPPDAP_THIRD_PARTY_DIR}/googletest).
    set(CPPDAP_DIR <path-to-cppdap>)
    set(CPPDAP_THIRD_PARTY_DIR <third-party-root-directory>)
    set(CPPDAP_JSON_DIR        <path-to-nlohmann-json>)
    set(CPPDAP_GOOGLETEST_DIR  <path-to-googletest>)
    
    add_subdirectory(${CPPDAP_DIR})
    target_link_libraries(<target> cppdap)
  4. Configure JSON library for cppdap

    main

    While cppdap defaults to nlohmann/json, you can use alternative JSON serialization libraries by setting the following CMake cache variables:

    • CPPDAP_USE_EXTERNAL_NLOHMANN_JSON_PACKAGE
    • CPPDAP_USE_EXTERNAL_RAPIDJSON_PACKAGE (for RapidJSON)
    • CPPDAP_USE_EXTERNAL_JSONCPP_PACKAGE (for JsonCpp)
  5. Connect a Session to a Reader and Writer

    main

    After creating a session, you must bind it to communication channels using connect(). This method requires a std::shared_ptr<dap::Reader> and a std::shared_ptr<dap::Writer>. Once connected, the session can begin receiving and sending DAP messages.

    session->connect(reader, writer);
  6. Send a DAP Request

    main

    To send a command to the debug adapter, use the send overload that accepts TypeInfo for both the request and the expected response. This method automatically manages sequence numbers and registers a GenericResponseHandler to catch the eventual response.

    Arguments:

    • requestTypeInfo: The dap::TypeInfo describing the request structure.
    • responseTypeInfo: The dap::TypeInfo describing the expected response structure.
    • request: A pointer to the data object to be serialized.
    • responseHandler: A callback invoked when the response arrives.
    session->send(requestTypeInfo, responseTypeInfo, requestData, [](const void* data, const dap::Error* error) {
        if (error) {
            // Handle error
        } else {
            // Handle successful response data
        }
    });
  7. Write content using ContentWriter

    main

    The ContentWriter class provides an interface for writing data to a dap::Writer. It is initialized with a std::shared_ptr<Writer>.

    Key methods:

    • isOpen(): Returns true if the writer is currently open.
    • close(): Closes the writer.
    • write(const std::string&): Writes the provided string to the underlying writer. Returns true if successful.
    #include "dap/content_stream.h"
    #include "dap/session.h"
    
    // Assuming writer_ptr is a valid std::shared_ptr<dap::Writer>
    dap::ContentWriter content_writer(writer_ptr);
    
    if (content_writer.isOpen()) {
        bool success = content_writer.write("your content here");
        content_writer.close();
    }
  8. Send a DAP Event

    main

    To broadcast an event, use the send overload that accepts TypeInfo and an event object. This method automatically assigns a sequence number and serializes the event body.

    Arguments:

    • typeinfo: The dap::TypeInfo describing the event.
    • event: A pointer to the data object to be serialized as the event body.
    session->send(eventTypeInfo, eventData);
  9. Start processing DAP messages

    main

    To begin the asynchronous processing of incoming messages, call startProcessingMessages(). This method spawns internal threads to:

    1. Read messages from the connected reader.
    2. Dispatch payloads (requests, events, or responses) to their registered handlers.

    You can optionally provide a ClosedHandler callback that will be executed when the reader is closed and the processing loop terminates.

    session->startProcessingMessages([]() {
      // Handle session closure
    });
  10. Use StringBuffer for in-memory I/O

    main

    The dap::StringBuffer class provides an in-memory buffer that implements both the dap::Reader and dap::Writer interfaces. It allows you to write data into a buffer and subsequently read it back, making it useful for simulating I/O streams or handling data in memory.

    Key behaviors:

    • Writing: You can write raw bytes or std::string objects. Each write operation is tracked as a 'chunk'.
    • Reading: Reading consumes data from the front of the buffer. If you read more than the current chunk size, it moves to the next chunk.
    • Lifecycle: Use StringBuffer::create() to instantiate it. Once close() is called, the buffer is no longer writable and reading will return 0.
    #include "dap/string_buffer.h"
    
    // Create a new buffer
    auto buffer = dap::StringBuffer::create();
    
    // Write data
    std::string data = "Hello, DAP!";
    buffer->write(data);
    
    // Read data back
    char read_buf[12];
    size_t bytes_read = buffer->read(read_buf, 12);
    
    // Get the remaining content as a string
    std::string remaining = buffer->string();