diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9bfa65c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v27 + with: + extra_nix_config: | + experimental-features = nix-command flakes + + - uses: cachix/cachix-action@v15 + with: + name: logos-co + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + + - name: Build vanilla Qt plugin test + run: nix build '.#checks.x86_64-linux.vanilla-plugin' diff --git a/flake.lock b/flake.lock index 11fb367..95e9984 100644 --- a/flake.lock +++ b/flake.lock @@ -1,31 +1,8 @@ { "nodes": { - "logos-cpp-sdk": { - "inputs": { - "logos-nix": "logos-nix", - "nixpkgs": [ - "logos-cpp-sdk", - "logos-nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1774988975, - "narHash": "sha256-wJ7Bv3TL754bZcTL2qilI0JahiVag7fLDzw8Y6Qz0pk=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "d633575677a3d19a8bf1d5ff687398dace532938", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, "logos-module": { "inputs": { - "logos-nix": "logos-nix_2", + "logos-nix": "logos-nix", "nixpkgs": [ "logos-module", "logos-nix", @@ -68,24 +45,6 @@ "inputs": { "nixpkgs": "nixpkgs_2" }, - "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_3": { - "inputs": { - "nixpkgs": "nixpkgs_3" - }, "locked": { "lastModified": 1774455309, "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", @@ -132,27 +91,10 @@ "type": "github" } }, - "nixpkgs_3": { - "locked": { - "lastModified": 1759036355, - "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "root": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk", "logos-module": "logos-module", - "logos-nix": "logos-nix_3", + "logos-nix": "logos-nix_2", "nixpkgs": [ "logos-nix", "nixpkgs" diff --git a/flake.nix b/flake.nix index 404dd81..eb173ee 100644 --- a/flake.nix +++ b/flake.nix @@ -3,12 +3,13 @@ inputs = { logos-nix.url = "github:logos-co/logos-nix"; - logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk"; + # Only needed for standalone dev/testing and the convenience lib wrapper. + # When used via logos-module-builder, logosModule is injected by the builder. logos-module.url = "github:logos-co/logos-module"; nixpkgs.follows = "logos-nix/nixpkgs"; }; - outputs = { self, nixpkgs, logos-cpp-sdk, logos-module, ... }: + outputs = { self, nixpkgs, logos-module, ... }: let systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ]; @@ -17,15 +18,32 @@ pkgs = import nixpkgs { inherit system; }; }); - # Import the backend library - backendLib = import ./lib { - inherit nixpkgs logos-cpp-sdk logos-module; + # Raw backend lib — no deps baked in. + # Callers (logos-module-builder) inject logosModule per call. + rawLib = import ./lib { + inherit nixpkgs; inherit (nixpkgs) lib; backendRoot = ./.; }; + + # Convenience wrapper that pre-fills logosModule from this flake's inputs. + # Used for standalone dev/testing. + wrappedLib = rawLib // { + buildPlugin = args: rawLib.buildPlugin (args // { + logosModule = logos-module.packages.${args.pkgs.system}.default; + }); + buildHeaders = args: rawLib.buildHeaders args; + devShellInputs = pkgs: rawLib.devShellInputs pkgs { + logosModule = logos-module.packages.${pkgs.system}.default; + }; + }; + in { - # Export the backend library for use by logos-module-builder - lib = backendLib; + # Default export: wrapped with logosModule pre-filled + lib = wrappedLib; + + # Raw export: no deps — for use by logos-module-builder + rawLib = rawLib; # Provide the cmake module as a package packages = forAllSystems ({ pkgs, ... }: { @@ -36,18 +54,25 @@ default = self.packages.${pkgs.system}.cmake-module; }); + # Tests + checks = forAllSystems ({ pkgs, ... }: { + # Build a vanilla Qt plugin with no Logos SDK deps + vanilla-plugin = import ./tests/test-vanilla-plugin.nix { + inherit pkgs; + backendCommon = rawLib.common; + }; + }); + # Dev shell for working on the backend itself devShells = forAllSystems ({ pkgs, ... }: let - logosSdk = logos-cpp-sdk.packages.${pkgs.system}.default; - logosModule = logos-module.packages.${pkgs.system}.default; + shell = wrappedLib.devShellInputs pkgs; in { default = pkgs.mkShell { - nativeBuildInputs = backendLib.common.commonNativeBuildInputs pkgs; - buildInputs = backendLib.common.commonBuildInputs pkgs; + nativeBuildInputs = shell.nativeBuildInputs; + buildInputs = shell.buildInputs; shellHook = '' - export LOGOS_CPP_SDK_ROOT="${logosSdk}" - export LOGOS_MODULE_ROOT="${logosModule}" + ${shell.shellHook} echo "Logos Qt Plugin Backend development environment" ''; }; diff --git a/lib/common.nix b/lib/common.nix index c1fef90..5b38ecf 100644 --- a/lib/common.nix +++ b/lib/common.nix @@ -1,4 +1,8 @@ # Qt-specific utilities for building Logos plugins +# +# This module only knows about Qt. It does NOT reference logos-cpp-sdk. +# The logosModule dependency (interface.h) is the only Logos-specific dep, +# and it's passed in by the caller. { lib }: { @@ -29,10 +33,11 @@ pkgs.qt6.qtremoteobjects ]; - # CMake flags for Qt plugin builds - commonCmakeFlags = { logosSdk, logosModule }: [ + # CMake flags for Qt plugin builds. + # Only includes logosModule (for interface.h). + # SDK flags are added by the builder layer, not here. + commonCmakeFlags = { logosModule }: [ "-GNinja" - "-DLOGOS_CPP_SDK_ROOT=${logosSdk}" "-DLOGOS_MODULE_ROOT=${logosModule}" ]; diff --git a/lib/default.nix b/lib/default.nix index e59bec9..97db7e9 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,16 +1,16 @@ # Logos Qt Plugin Backend # -# This module exports the backend interface that logos-module-builder uses -# to build Qt 6 plugins. It encapsulates all Qt-specific build logic: -# CMake configuration, Qt dependencies, code generation, and header generation. +# Builds Logos modules as Qt 6 plugins. Encapsulates Qt-specific build logic: +# CMake configuration, Qt dependencies, plugin compilation, and macOS fixups. # -# Backend interface: -# buildPlugin — compile sources + metadata → Qt plugin .so/.dylib -# buildHeaders — introspect compiled plugin → SDK headers for consumers -# devShellInputs — Qt-specific dev shell dependencies -# common — Qt-specific utilities (deps, cmake flags, platform helpers) +# This backend does NOT know about logos-cpp-sdk. It only knows about: +# - Qt (cmake, ninja, qtbase, qtremoteobjects) +# - logosModule (interface.h — the plugin interface contract) # -{ nixpkgs, logos-cpp-sdk, logos-module, lib, backendRoot }: +# The logos-cpp-sdk (generator, SDK lib, headers) is added by the caller +# (logos-module-builder) via extraNativeBuildInputs / extraBuildInputs / env. +# +{ nixpkgs, lib, backendRoot }: let common = import ./common.nix { inherit lib; }; @@ -19,52 +19,56 @@ let in { # Build a Qt plugin from sources. - # Returns: derivation with lib/{name}_plugin.so and include/ (general SDK headers) + # logosModule provides interface.h. Everything else (SDK, generator) comes + # via extraNativeBuildInputs/extraBuildInputs passed by the caller. + # Returns: derivation with lib/{name}_plugin.so buildPlugin = { pkgs, src, config, + logosModule, moduleDeps ? {}, externalLibs ? {}, extraNativeBuildInputs ? [], extraBuildInputs ? [], + extraCmakeFlags ? [], + extraEnv ? {}, preConfigure ? "", postInstall ? "", }: let - logosSdk = logos-cpp-sdk.packages.${pkgs.system}.default; - logosModule = logos-module.packages.${pkgs.system}.default; commonArgs = { pname = "logos-${config.name}-module"; version = config.version; - nativeBuildInputs = common.commonNativeBuildInputs pkgs ++ [ logosSdk ] ++ extraNativeBuildInputs; + nativeBuildInputs = common.commonNativeBuildInputs pkgs ++ extraNativeBuildInputs; buildInputs = common.commonBuildInputs pkgs ++ extraBuildInputs; - cmakeFlags = common.commonCmakeFlags { inherit logosSdk logosModule; }; + cmakeFlags = common.commonCmakeFlags { inherit logosModule; } ++ extraCmakeFlags; env = { - LOGOS_CPP_SDK_ROOT = "${logosSdk}"; LOGOS_MODULE_ROOT = "${logosModule}"; - # Keep LOGOS_MODULE_BUILDER_ROOT for backward compatibility with existing CMakeLists.txt LOGOS_MODULE_BUILDER_ROOT = "${backendRoot}"; - }; + } // extraEnv; meta = with lib; { description = config.description; platforms = platforms.unix; }; }; in mkBuildPlugin.build { - inherit pkgs src config commonArgs logosSdk moduleDeps externalLibs preConfigure postInstall; + inherit pkgs src config commonArgs moduleDeps externalLibs preConfigure postInstall; + logosSdk = null; # not used by buildPlugin.nix directly, kept for compat }; # Generate SDK headers from a compiled plugin. - # Returns: derivation with include/*.h (module-specific API headers for consumers) + # This is a thin wrapper — the actual generator binary and SDK package + # are passed in by the caller. + # Returns: derivation with include/*.h buildHeaders = { pkgs, src, config, pluginLib, + logosSdk, }: let - logosSdk = logos-cpp-sdk.packages.${pkgs.system}.default; commonArgs = { pname = "logos-${config.name}-module"; version = config.version; @@ -78,18 +82,15 @@ in { lib = pluginLib; }; - # Dev shell dependencies for modules using this backend. - # Returns: { nativeBuildInputs, buildInputs, shellHook } - devShellInputs = pkgs: - let - logosSdk = logos-cpp-sdk.packages.${pkgs.system}.default; - logosModule = logos-module.packages.${pkgs.system}.default; - in { + # Dev shell dependencies — Qt only. + # SDK env vars are added by the caller (logos-module-builder). + devShellInputs = pkgs: { + logosModule ? null, + }: { nativeBuildInputs = common.commonNativeBuildInputs pkgs; buildInputs = common.commonBuildInputs pkgs; shellHook = '' - export LOGOS_CPP_SDK_ROOT="${logosSdk}" - export LOGOS_MODULE_ROOT="${logosModule}" + ${if logosModule != null then ''export LOGOS_MODULE_ROOT="${logosModule}"'' else ""} export LOGOS_MODULE_BUILDER_ROOT="${backendRoot}" ''; }; diff --git a/tests/test-vanilla-plugin.nix b/tests/test-vanilla-plugin.nix new file mode 100644 index 0000000..3665e3e --- /dev/null +++ b/tests/test-vanilla-plugin.nix @@ -0,0 +1,74 @@ +# Integration test: build a vanilla Qt plugin using only Qt deps (no logos-cpp-sdk). +# Proves the backend can compile a .so without any Logos SDK dependency. +{ pkgs, backendCommon }: + +let + pluginSrc = ./vanilla-plugin; +in +pkgs.stdenv.mkDerivation { + pname = "logos-plugin-qt-vanilla-test"; + version = "0.0.1"; + + src = pluginSrc; + + nativeBuildInputs = backendCommon.commonNativeBuildInputs pkgs; + buildInputs = backendCommon.commonBuildInputs pkgs; + + dontUseCmakeConfigure = true; + + buildPhase = '' + runHook preBuild + + mkdir -p build + cd build + cmake .. -GNinja + ninja + cd .. + + runHook postBuild + ''; + + doCheck = true; + checkPhase = '' + runHook preCheck + + # Verify the plugin .so was created + PLUGIN="build/vanilla_test_plugin.so" + if [ ! -f "$PLUGIN" ]; then + PLUGIN="build/vanilla_test_plugin.dylib" + fi + + if [ ! -f "$PLUGIN" ]; then + echo "FAIL: Plugin binary not found" + ls -la build/ + exit 1 + fi + + echo "OK: Plugin binary exists at $PLUGIN" + echo "Size: $(stat -c%s "$PLUGIN" 2>/dev/null || stat -f%z "$PLUGIN") bytes" + + # Verify it's a valid shared library + file "$PLUGIN" | grep -q "shared object\|dynamically linked\|Mach-O" || { + echo "FAIL: Plugin is not a valid shared library" + file "$PLUGIN" + exit 1 + } + echo "OK: Plugin is a valid shared library" + + # Verify Qt plugin metadata is embedded + if strings "$PLUGIN" | grep -q "vanilla_test"; then + echo "OK: Qt plugin metadata found in binary" + else + echo "FAIL: Qt plugin metadata not found in binary" + exit 1 + fi + + runHook postCheck + ''; + + installPhase = '' + mkdir -p $out/lib + cp build/vanilla_test_plugin.* $out/lib/ 2>/dev/null || true + echo "Vanilla Qt plugin test passed" > $out/result.txt + ''; +} diff --git a/tests/vanilla-plugin/CMakeLists.txt b/tests/vanilla-plugin/CMakeLists.txt new file mode 100644 index 0000000..ea72729 --- /dev/null +++ b/tests/vanilla-plugin/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.14) +project(VanillaTestPlugin LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core) + +# Copy metadata.json to build dir so Q_PLUGIN_METADATA can find it +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/metadata.json ${CMAKE_CURRENT_BINARY_DIR}/metadata.json COPYONLY) + +add_library(vanilla_test_plugin SHARED + vanilla_test_plugin.h + interface.h +) + +set_target_properties(vanilla_test_plugin PROPERTIES + PREFIX "" + OUTPUT_NAME "vanilla_test_plugin" +) + +target_include_directories(vanilla_test_plugin PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries(vanilla_test_plugin PRIVATE Qt6::Core) diff --git a/tests/vanilla-plugin/interface.h b/tests/vanilla-plugin/interface.h new file mode 100644 index 0000000..02bcb0e --- /dev/null +++ b/tests/vanilla-plugin/interface.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include + +class PluginInterface { +public: + virtual ~PluginInterface() = default; + virtual QString name() const = 0; + virtual QString version() const = 0; +}; + +#define PluginInterface_iid "com.example.PluginInterface" +Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid) diff --git a/tests/vanilla-plugin/metadata.json b/tests/vanilla-plugin/metadata.json new file mode 100644 index 0000000..e4bfc11 --- /dev/null +++ b/tests/vanilla-plugin/metadata.json @@ -0,0 +1,9 @@ +{ + "name": "vanilla_test", + "version": "1.0.0", + "description": "Vanilla Qt plugin for testing the backend", + "type": "core", + "category": "test", + "main": "vanilla_test_plugin", + "dependencies": [] +} diff --git a/tests/vanilla-plugin/vanilla_test_plugin.h b/tests/vanilla-plugin/vanilla_test_plugin.h new file mode 100644 index 0000000..f24267b --- /dev/null +++ b/tests/vanilla-plugin/vanilla_test_plugin.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include "interface.h" + +class VanillaTestPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_PLUGIN_METADATA(IID PluginInterface_iid FILE "metadata.json") + Q_INTERFACES(PluginInterface) +public: + QString name() const override { return "vanilla_test"; } + QString version() const override { return "1.0.0"; } + Q_INVOKABLE QString hello() { return "world"; } +};