diff --git a/GaudiKernel/include/Gaudi/Concepts.h b/GaudiKernel/include/Gaudi/Concepts.h new file mode 100644 index 0000000000000000000000000000000000000000..c443698a70f97a0cde8a9ba694b3ac54c00f1618 --- /dev/null +++ b/GaudiKernel/include/Gaudi/Concepts.h @@ -0,0 +1,22 @@ +/***********************************************************************************\ +* (c) Copyright 2025 CERN for the benefit of the LHCb and ATLAS collaborations * +* * +* This software is distributed under the terms of the Apache version 2 licence, * +* copied verbatim in the file "LICENSE". * +* * +* In applying this licence, CERN does not waive the privileges and immunities * +* granted to it by virtue of its status as an Intergovernmental Organization * +* or submit itself to any jurisdiction. * +\***********************************************************************************/ +#pragma once + +#include <concepts> + +class InterfaceID; + +namespace Gaudi { + template <typename T> + concept IsInterface = requires { + { T::interfaceID() } -> std::same_as<const InterfaceID&>; + }; +} // namespace Gaudi diff --git a/GaudiKernel/include/GaudiKernel/ServiceHandle.h b/GaudiKernel/include/GaudiKernel/ServiceHandle.h index 20c4aea33a2c70741f87313c3cfa152f2fcc0dad..067b9da8d5544a3f45a3b3daad70f30f2ad7eac3 100644 --- a/GaudiKernel/include/GaudiKernel/ServiceHandle.h +++ b/GaudiKernel/include/GaudiKernel/ServiceHandle.h @@ -1,5 +1,5 @@ /***********************************************************************************\ -* (c) Copyright 1998-2024 CERN for the benefit of the LHCb and ATLAS collaborations * +* (c) Copyright 1998-2025 CERN for the benefit of the LHCb and ATLAS collaborations * * * * This software is distributed under the terms of the Apache version 2 licence, * * copied verbatim in the file "LICENSE". * @@ -12,6 +12,7 @@ #define GAUDIKERNEL_SERVICEHANDLE_H // Includes +#include <Gaudi/Concepts.h> #include <GaudiKernel/Bootstrap.h> #include <GaudiKernel/GaudiException.h> #include <GaudiKernel/GaudiHandle.h> @@ -94,9 +95,27 @@ public: protected: /** Do the real retrieval of the Service. */ - StatusCode retrieve( T*& service ) const override { + StatusCode retrieve( T*& service ) const override { return i_retrieve( service ); } + + /// retrieve the service for ServiceHandles<ISomeInterfaces> + template <Gaudi::IsInterface I = T> + StatusCode i_retrieve( I*& service ) const { const ServiceLocatorHelper helper( *serviceLocator(), GaudiHandleBase::messageName(), this->parentName() ); - return helper.getService( GaudiHandleBase::typeAndName(), true, T::interfaceID(), (void**)&service ); + return helper.getService( GaudiHandleBase::typeAndName(), true, I::interfaceID(), (void**)&service ); + } + + /// retrieve the service for ServiceHandles<ActualService> + template <typename I = T> + requires( !Gaudi::IsInterface<I> ) + StatusCode i_retrieve( I*& service ) const { + IService* svc = nullptr; + return i_retrieve( svc ).andThen( [&] { + service = dynamic_cast<I*>( svc ); + if ( !service ) + throw GaudiException( "unable to dcast Service " + this->typeAndName() + " to " + + System::typeinfoName( typeid( T ) ), + this->typeAndName() + " retrieve", StatusCode::FAILURE ); + } ); } // /** Do the real release of the Service */ diff --git a/GaudiTestSuite/CMakeLists.txt b/GaudiTestSuite/CMakeLists.txt index f5d318ac9c605cd2bcdafca9ed9f8b38a0421ef2..3207800958976efb250832db6d4cd8cbfd36ccf4 100644 --- a/GaudiTestSuite/CMakeLists.txt +++ b/GaudiTestSuite/CMakeLists.txt @@ -108,6 +108,7 @@ gaudi_add_module(GaudiTestSuiteComponents src/ToolHandles/FloatTool.cpp src/NTuple/NTupleWriterProducers.cpp src/NTuple/NTupleWriterImpls.cpp + src/UseSvcWithoutInterface.cpp LINK GaudiKernel Gaudi::Functional GaudiUtilsLib diff --git a/GaudiTestSuite/src/UseSvcWithoutInterface.cpp b/GaudiTestSuite/src/UseSvcWithoutInterface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3460ffa5cf3f93862f6b3932baf2f81564828118 --- /dev/null +++ b/GaudiTestSuite/src/UseSvcWithoutInterface.cpp @@ -0,0 +1,45 @@ +/***********************************************************************************\ +* (c) Copyright 2025 CERN for the benefit of the LHCb and ATLAS collaborations * +* * +* This software is distributed under the terms of the Apache version 2 licence, * +* copied verbatim in the file "LICENSE". * +* * +* In applying this licence, CERN does not waive the privileges and immunities * +* granted to it by virtue of its status as an Intergovernmental Organization * +* or submit itself to any jurisdiction. * +\***********************************************************************************/ +#include <Gaudi/Algorithm.h> +#include <Gaudi/Functional/Consumer.h> +#include <GaudiKernel/Service.h> + +namespace Gaudi::TestSuite { + class SvcWithoutInterface : public Service { + public: + using Service::Service; + + StatusCode initialize() override { + return Service::initialize().andThen( [this] { info() << "initialized" << endmsg; } ); + } + + void doSomething() const { info() << "doing something" << endmsg; } + }; + DECLARE_COMPONENT( SvcWithoutInterface ) + + class UseSvcWithoutInterface : public Gaudi::Functional::Consumer<void()> { + public: + UseSvcWithoutInterface( const std::string& name, ISvcLocator* svcLoc ) + : Consumer( name, svcLoc ), m_svc( "Gaudi::TestSuite::SvcWithoutInterface/SvcWithoutInterface", name ) {} + StatusCode initialize() override { + return Algorithm::initialize() + .andThen( [this] { info() << "initializing..." << endmsg; } ) + .andThen( [this] { m_svc->doSomething(); } ) + .andThen( [this] { info() << "initialized" << endmsg; } ); + } + void operator()() const override {} + + private: + // Add your member variables here + ServiceHandle<SvcWithoutInterface> m_svc; + }; + DECLARE_COMPONENT( UseSvcWithoutInterface ) +} // namespace Gaudi::TestSuite diff --git a/GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py b/GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..5a4c268c4b887ee1721b08b176cd6107c1037210 --- /dev/null +++ b/GaudiTestSuite/tests/pytest/handles/test_svc_without_interface.py @@ -0,0 +1,53 @@ +##################################################################################### +# (c) Copyright 2025 CERN for the benefit of the LHCb and ATLAS collaborations # +# # +# This software is distributed under the terms of the Apache version 2 licence, # +# copied verbatim in the file "LICENSE". # +# # +# In applying this licence, CERN does not waive the privileges and immunities # +# granted to it by virtue of its status as an Intergovernmental Organization # +# or submit itself to any jurisdiction. # +##################################################################################### + +from GaudiTesting import NO_ERROR_MESSAGES, GaudiExeTest + + +def config(): + from GaudiConfig2 import Configurables as C + + conf = [] + alg = C.Gaudi.TestSuite.UseSvcWithoutInterface("UseSvcWithoutInterface") + conf.append(alg) + + appMgr = C.ApplicationMgr( + TopAlg=[alg], + EvtMax=1, + EvtSel="NONE", + ) + conf.append(appMgr) + + msgsvc = C.MessageSvc(Format="% F%25W%S%7W%R%T %0W%M") + conf.append(msgsvc) + + return conf + + +class Test(GaudiExeTest): + command = ["gaudirun.py", f"{__file__}:config"] + + reference = {"messages_count": NO_ERROR_MESSAGES} + + def test_stdout(self, stdout: bytes): + extracted_lines = [ + line.split(None, 2) + for line in stdout.splitlines() + if line.startswith(b"UseSvcWithoutInterface") + or line.startswith(b"SvcWithoutInterface") + ] + expected_lines = [ + [b"UseSvcWithoutInterface", b"INFO", b"initializing..."], + [b"SvcWithoutInterface", b"INFO", b"initialized"], + [b"SvcWithoutInterface", b"INFO", b"doing something"], + [b"UseSvcWithoutInterface", b"INFO", b"initialized"], + ] + assert extracted_lines == expected_lines