Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cmake/compiler_options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,20 @@ elseif(MORPH_COMPILER_FAMILY STREQUAL "Clang")
# headers.
-Wno-thread-safety-negative
)
# Clang 23's lifetime-safety analysis, which -Weverything enables. It
# raises three kinds of diagnostic here, none a defect: suggestions to
# annotate a declaration (including in Qt's generated moc code, which
# we cannot annotate), reports that an existing MORPH_LIFETIMEBOUND
# cannot be verified, and false invalidations -- `out.push_back(...)`
# on a `std::vector<T>& out` parameter is reported as using `out`
# after invalidating it. The analysis is new and does not yet follow
# this code; MORPH_LIFETIMEBOUND keeps stating the contracts for the
# call-site checks that predate it. Older clang has none of these
# groups. -Wno-lifetime-safety alone is not enough: the suggestions
# and the lifetimebound check are separate groups under -Weverything.
_morph_clang_suppression_if_supported(-Wno-lifetime-safety)
_morph_clang_suppression_if_supported(-Wno-lifetime-safety-suggestions)
_morph_clang_suppression_if_supported(-Wno-lifetime-safety-lifetimebound-violation)

# (g) AppleClang only, deliberately: the one place where the macOS
# toolchain's -Weverything set diverges from the Clang the CI legs use,
Expand Down
3 changes: 3 additions & 0 deletions cmake/morph_add_rung.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,12 @@ function(morph_add_rung)
set_source_files_properties("${_qml_file}" PROPERTIES QT_RESOURCE_ALIAS "${_qml_name}")
endforeach()
qt_add_library(ladder_${_rung}_qml STATIC)
# The output directory ends in the URI so the build tree is a valid
# QML import path for this module, which qmllint relies on.
qt_add_qml_module(ladder_${_rung}_qml
URI ${_qml_uri}
VERSION 1.0
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${_qml_uri}"
QML_FILES ${_qml_files}
)
target_link_libraries(ladder_${_rung}_qml PUBLIC morph_forms_moduleplugin Qt6::Quick Qt6::Qml)
Expand Down
7 changes: 7 additions & 0 deletions examples/bank/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ qt_add_executable(bank_gui
main.cpp
)

# The module's .qml files live in qml/, so it needs a qmldir there too for
# them to resolve the module's own types; Qt versions that predate the policy
# do not know it.
if(QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif()

qt_add_qml_module(bank_gui
URI BankGui
VERSION 1.0
Expand Down
10 changes: 10 additions & 0 deletions examples/forms/gui_qml/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@

qt_add_library(lab_forms_demo_module STATIC)

# The module's .qml files live in qml/, so it needs a qmldir there too for
# them to resolve the module's own types; Qt versions that predate the policy
# do not know it.
if(QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif()

# The output directory ends in the URI so the build tree is a valid QML import
# path for this module, which qmllint relies on.
qt_add_qml_module(lab_forms_demo_module
URI LabFormsDemo
VERSION 1.0
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/LabFormsDemo"
QML_FILES
qml/Main.qml
qml/AppShell.qml
Expand Down
4 changes: 4 additions & 0 deletions examples/lims/gui/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include <memory>
#include <optional>

#include "desktop_session.hpp"
#include "gui/app_context.hpp"
#include "lims/db/database.hpp"
#include "result_qml_bridge.hpp"
Expand Down Expand Up @@ -88,6 +89,9 @@ int main(int argc, char** argv) {
// between them — are built here, once, and live until the process
// exits. A `Remote` context is not usable the line after its
// constructor returns, which is what `onReady` is for.
if (!serverUrl) {
::lims::gui::installDesktopSession(ctx.bridge());
}
sampleBridge = std::make_unique<lims::gui::SampleBridge>(ctx.bridge(), ctx.executor());
resultBridge = std::make_unique<lims::gui::ResultBridge>(ctx.bridge(), ctx.executor());

Expand Down
34 changes: 34 additions & 0 deletions examples/lims/gui_lib/desktop_session.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <morph/core/bridge.hpp>
#include <morph/session/session.hpp>
#include <string>
#include <string_view>

/// @file
/// The session the single-user desktop client dispatches under.
///
/// Every mutating lims action refuses an empty principal
/// (`lims::requirePrincipal`), so a client that builds its bridges without a
/// session can do nothing but read. In local mode no authorizer runs, so a
/// bare principal is the whole session; a fixed one is enough because a
/// single-process desktop deployment has no second user to tell apart.
///
/// It lives here rather than in `gui/main.cpp` so the tests can install the
/// same session the executable does, and an action dispatched through it is
/// checked end to end.

namespace lims::gui {

/// @brief The principal the local desktop client dispatches under.
inline constexpr std::string_view kDesktopPrincipal = "demo";

/// @brief Installs the local desktop client's session on @p bridge, before
/// any handler is built against it.
/// @param bridge The bridge every desktop handler dispatches through.
inline void installDesktopSession(::morph::bridge::Bridge& bridge) {
bridge.setDefaultSession(::morph::session::Context{.principal = std::string{kDesktopPrincipal}});
}

} // namespace lims::gui
14 changes: 14 additions & 0 deletions examples/lims/tests/test_lims_presenters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <morph/session/session.hpp>
#include <string>

#include "desktop_session.hpp"
#include "result_presenter.hpp"
#include "sample_presenter.hpp"
#include "testkit/backend_rig.hpp"
Expand Down Expand Up @@ -130,6 +131,19 @@ TEST_CASE("SamplePresenter routes registration and every lifecycle transition",
CHECK_FALSE(presenter.busy());
}

TEST_CASE("The local desktop session registers a client and a sample", "[lims][gui][presenter]") {
// The bridge is set up exactly as gui/main.cpp sets up its own in local
// mode, so a client that ships without a session fails here.
DbFixture fixture;
auto rig = std::make_unique<BackendRig>(Mode::Local, 1);
lims::gui::installDesktopSession(rig->bridge(0));
lims::gui::SamplePresenter presenter{rig->bridge(0), rig->executor()};

const auto registered = registerSampleVia(presenter);
CHECK(registered.state == lims::SampleState::Registered);
CHECK(registered.id.hasValue());
}

TEST_CASE("SamplePresenter surfaces a model refusal as a displayable message", "[lims][gui][presenter]") {
DbFixture fixture;
auto rig = makeAuthedRig("alice");
Expand Down
10 changes: 10 additions & 0 deletions src/qt/forms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,19 @@

qt_add_library(morph_forms_module STATIC)

# The module's .qml files live in qml/, so it needs a qmldir there too for
# them to resolve the module's own types; Qt versions that predate the policy
# do not know it.
if(QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif()

# The output directory ends in the URI so the build tree is a valid QML import
# path for this module, which qmllint relies on.
qt_add_qml_module(morph_forms_module
URI MorphForms
VERSION 1.0
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/MorphForms"
QML_FILES
qml/JsonExact.js
qml/DynamicForm.qml
Expand Down
Loading