diff --git a/.github/workflows/cmake-workflow.yml b/.github/workflows/cmake-workflow.yml index a86e183..e9a0787 100644 --- a/.github/workflows/cmake-workflow.yml +++ b/.github/workflows/cmake-workflow.yml @@ -188,3 +188,13 @@ jobs: EOF cmake -G Ninja -S . -B build -DCAMERAD_SOURCE_DIR="$GITHUB_WORKSPACE" cmake --build build -j"$(nproc)" + + # The reference instrument built the way an instrument repository will, + # so a change that breaks a consumer fails here rather than downstream + - name: Build the reference instrument + shell: bash + run: | + cmake -G Ninja -S examples/reference_instrument -B "$RUNNER_TEMP/reference" \ + -DFETCHCONTENT_SOURCE_DIR_CAMERA_INTERFACE="$GITHUB_WORKSPACE" + cmake --build "$RUNNER_TEMP/reference" -j"$(nproc)" + test -x "$RUNNER_TEMP/reference/camerad" diff --git a/.github/workflows/instrument-ci.yml b/.github/workflows/instrument-ci.yml new file mode 100644 index 0000000..2257db7 --- /dev/null +++ b/.github/workflows/instrument-ci.yml @@ -0,0 +1,56 @@ +name: Instrument CI + +# Called by an instrument repository, which owns its own build but should not +# own a copy of this. Runs in the caller's context, so checkout takes the +# instrument, and the core arrives through its CMake fetch. +# +# jobs: +# build: +# uses: CaltechOpticalObservatories/camera-interface/.github/workflows/instrument-ci.yml@main + +on: + workflow_call: + inputs: + cmake-args: + description: "Extra arguments for the configure step" + required: false + type: string + default: "" + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout the instrument + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential cmake ninja-build \ + libccfits-dev libcfitsio-dev libcurl4-openssl-dev \ + nlohmann-json3-dev libzmq3-dev \ + libboost-thread-dev libboost-chrono-dev + + # Not packaged for common distros, so every consumer builds it + - name: Build and install zmqpp + run: | + git clone --depth 1 https://github.com/zeromq/zmqpp.git /tmp/zmqpp + cmake -S /tmp/zmqpp -B /tmp/zmqpp/build + cmake --build /tmp/zmqpp/build -j"$(nproc)" + sudo cmake --install /tmp/zmqpp/build + sudo ldconfig + + - name: Build + run: | + cmake -G Ninja -S . -B build ${{ inputs.cmake-args }} + cmake --build build -j"$(nproc)" + + # camerad exits 0 with no config and prints nothing, so there is nothing + # here to assert beyond the daemon having been produced. An instrument + # wanting more adds its own job against the emulator. + - name: Check the daemon was built + run: test -x build/camerad diff --git a/CMakeLists.txt b/CMakeLists.txt index 58d714e..8ab9da9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,10 @@ endif() option(CAMERAD_BUILD_TOOLS "Build the emulator, listener, socksend and shm_reader" ${PROJECT_IS_TOP_LEVEL}) option(CAMERAD_INSTALL "Generate install rules" ${PROJECT_IS_TOP_LEVEL}) +# An instrument builds its own daemon from camerad_server, so the core's would +# only collide with it +option(CAMERAD_BUILD_DAEMON "Build the camerad executable" ${PROJECT_IS_TOP_LEVEL}) + add_subdirectory(${PROJECT_BASE_DIR}/utils) add_subdirectory(${PROJECT_BASE_DIR}/common) add_subdirectory(${PROJECT_BASE_DIR}/camerad) diff --git a/camerad/CMakeLists.txt b/camerad/CMakeLists.txt index 2837294..a9deef0 100644 --- a/camerad/CMakeLists.txt +++ b/camerad/CMakeLists.txt @@ -194,17 +194,14 @@ find_package(Threads) # ---------------------------------------------------------------------------- # build the camera daemon # ---------------------------------------------------------------------------- -add_executable(camerad +# The server and its whole link interface, so a project consuming this one +# builds its own daemon by linking this and supplying Camera::Interface::create() +add_library(camerad_server STATIC ${CAMERAD_DIR}/camerad.cpp ${CAMERAD_DIR}/camera_server.cpp - ${INSTRUMENT_REGISTRATION} - ${INSTRUMENT_OBJECTS} ) -# ---------------------------------------------------------------------------- -# link everything -# ---------------------------------------------------------------------------- -target_link_libraries(camerad +target_link_libraries(camerad_server PUBLIC network utilities logentry @@ -220,6 +217,15 @@ target_link_libraries(camerad ${ZMQ_LIB} ) +if (CAMERAD_BUILD_DAEMON) + add_executable(camerad + ${INSTRUMENT_REGISTRATION} + ${INSTRUMENT_OBJECTS} + ) + + target_link_libraries(camerad camerad_server) +endif() + if (CAMERAD_INSTALL) install(TARGETS camerad RUNTIME DESTINATION ${CAMERAD_INSTALL_BINDIR}) endif() diff --git a/examples/reference_instrument/CMakeLists.txt b/examples/reference_instrument/CMakeLists.txt new file mode 100644 index 0000000..7141fd9 --- /dev/null +++ b/examples/reference_instrument/CMakeLists.txt @@ -0,0 +1,33 @@ +# ---------------------------------------------------------------------------- +# @file examples/reference_instrument/CMakeLists.txt +# @brief how an instrument repository consumes camerad +# +# Copy this into a new instrument repository as its top-level CMakeLists.txt. +# Nothing here reaches into the core: it fetches camerad, links camerad_server, +# and supplies Camera::Interface::create(). +# ---------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.24) + +project(reference_instrument CXX) + +include(FetchContent) + +# A consumer pins a tag here. The core's own CI points this at its working tree +# with -DFETCHCONTENT_SOURCE_DIR_CAMERA_INTERFACE, so a change that breaks a +# consumer fails there rather than in every instrument later. +FetchContent_Declare(camera_interface + GIT_REPOSITORY https://github.com/CaltechOpticalObservatories/camera-interface + GIT_TAG main +) + +set(CONTROLLER archon CACHE STRING "Controller this instrument uses") + +FetchContent_MakeAvailable(camera_interface) + +add_executable(camerad + reference_instrument.cpp + reference_interface_factory.cpp +) + +target_link_libraries(camerad PRIVATE camerad_server) diff --git a/examples/reference_instrument/README.md b/examples/reference_instrument/README.md new file mode 100644 index 0000000..bcf968f --- /dev/null +++ b/examples/reference_instrument/README.md @@ -0,0 +1,21 @@ +# Reference instrument + +The smallest thing that builds against camerad, and the starting point for a new +instrument repository. + +An instrument owns its interface class, the translation unit defining +`Camera::Interface::create()`, its ACF and cfg, and its own `CMakeLists.txt`. It +fetches camerad and links `camerad_server`, which carries the daemon and its +whole link interface, so the instrument supplies only what is specific to it. + +```bash +cmake -S . -B build +cmake --build build +``` + +That produces a `camerad` running this instrument. Copy the four files into a +new repository and pin `GIT_TAG` to a release of the core. + +The core's own CI builds this with +`-DFETCHCONTENT_SOURCE_DIR_CAMERA_INTERFACE` pointed at its working tree, so a +change that breaks a consumer fails there rather than in every instrument later. diff --git a/examples/reference_instrument/reference_instrument.cpp b/examples/reference_instrument/reference_instrument.cpp new file mode 100644 index 0000000..ba11957 --- /dev/null +++ b/examples/reference_instrument/reference_instrument.cpp @@ -0,0 +1,14 @@ +/** + * @file reference_instrument.cpp + * @brief the smallest instrument that builds against camerad + */ + +#include "reference_instrument.h" + +namespace Camera { + + void ReferenceInstrument::configure_instrument() { + logwrite("Camera::ReferenceInstrument::configure_instrument", ""); + } + +} diff --git a/examples/reference_instrument/reference_instrument.h b/examples/reference_instrument/reference_instrument.h new file mode 100644 index 0000000..6d280fc --- /dev/null +++ b/examples/reference_instrument/reference_instrument.h @@ -0,0 +1,17 @@ +/** + * @file reference_instrument.h + * @brief the smallest instrument that builds against camerad + */ +#pragma once + +#include "archon_interface.h" + +namespace Camera { + + /// Instrument adding nothing to the Archon interface, as a starting point + class ReferenceInstrument : public ArchonInterface { + public: + void configure_instrument() override; + }; + +} diff --git a/examples/reference_instrument/reference_interface_factory.cpp b/examples/reference_instrument/reference_interface_factory.cpp new file mode 100644 index 0000000..9d2eb90 --- /dev/null +++ b/examples/reference_instrument/reference_interface_factory.cpp @@ -0,0 +1,18 @@ +/** + * @file reference_interface_factory.cpp + * @brief the seam between camerad and an instrument + * + * camerad calls this to obtain its interface, so every instrument supplies one + * translation unit defining it. + */ + +#include "reference_instrument.h" +#include "camera_interface.h" + +namespace Camera { + + std::unique_ptr Interface::create() { + return std::make_unique(); + } + +}