diff --git a/Cargo.lock b/Cargo.lock index 71cba132a6..a6836b91b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,59 @@ dependencies = [ "score_log", ] +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "cc" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "containers" version = "0.0.1" @@ -17,6 +70,102 @@ dependencies = [ "score_log", ] +[[package]] +name = "cxx" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824894a4a85dca76d4c95c2b9098c036f5a29f627b30c12780774f6654e60974" +dependencies = [ + "cc", + "cxx-build", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ae0b651ea5b0000b19513aef5a03f194d7e3486f2d9258b658da8677fe9036" +dependencies = [ + "cc", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "scratch", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb05f91d3fb8435d9bab6ac5ce6ac1868be774325fb7fb2a91be39393b21388e" +dependencies = [ + "clap", + "codespan-reporting", + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf293202e0e3e98495785745389e8d0755b217e66f19194a5c695c25e03282ef" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.199" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca001d746947c7249ed9d332a10f7a59daedbafeb0ec68c5c18a7db7a93f6ccc" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "libc" version = "0.2.189" @@ -38,7 +187,16 @@ version = "0.0.1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", ] [[package]] @@ -119,7 +277,7 @@ dependencies = [ "quote", "score_log", "score_log_fmt", - "syn", + "syn 2.0.119", ] [[package]] @@ -128,9 +286,51 @@ version = "0.0.1" dependencies = [ "quote", "stdout_logger", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", ] +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "stdout_logger" version = "0.0.1" @@ -138,6 +338,19 @@ dependencies = [ "score_log", ] +[[package]] +name = "stop_token" +version = "0.0.1" +dependencies = [ + "cxx", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.119" @@ -149,6 +362,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync" version = "0.0.1" @@ -157,6 +381,15 @@ dependencies = [ "score_log", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thread" version = "0.0.1" @@ -170,3 +403,33 @@ name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 1c3ac392c1..e8cfa33526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ default-members = [ "score/testing_macros", "score/language/rust/libcpp", "score/language/rust/libcpp_derive", + "score/language/rust/stop_token", "score/result/rust", "score/language/rust/memres", ] @@ -44,6 +45,7 @@ members = [ "score/testing_macros", "score/language/rust/libcpp", "score/language/rust/libcpp_derive", + "score/language/rust/stop_token", "score/result/rust", "examples/log_builtin", "examples/log_custom", diff --git a/score/language/rust/stop_token/BUILD b/score/language/rust/stop_token/BUILD new file mode 100644 index 0000000000..4b9838033a --- /dev/null +++ b/score/language/rust/stop_token/BUILD @@ -0,0 +1,54 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@cxx.rs//tools/bazel:rust_cxx_bridge.bzl", "rust_cxx_bridge") +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") +load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") + +cc_library( + name = "stop_token_adapter", + srcs = ["cpp/stop_token_adapter.cpp"], + hdrs = ["cpp/stop_token_adapter.h"], + features = COMPILER_WARNING_FEATURES, + deps = ["@score_baselibs//score/language/futurecpp"], +) + +rust_cxx_bridge( + name = "stop_token_bridge", + src = "src/lib.rs", + deps = [":stop_token_adapter"], +) + +rust_library( + name = "stop_token", + srcs = ["src/lib.rs"], + visibility = ["//visibility:public"], + deps = [ + ":stop_token_bridge", + "@cxx.rs//:cxx", + ], +) + +alias( + name = "rust", + actual = ":stop_token", + visibility = ["//visibility:public"], +) + +rust_test( + name = "stop_token_test", + crate = ":stop_token", + features = ["link_std_cpp_lib"], + tags = ["unit"], +) diff --git a/score/language/rust/stop_token/Cargo.toml b/score/language/rust/stop_token/Cargo.toml new file mode 100644 index 0000000000..3d1660f238 --- /dev/null +++ b/score/language/rust/stop_token/Cargo.toml @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +[package] +name = "stop_token" +description = "Rust bindings for score::cpp::stop_token and score::cpp::stop_source." +version.workspace = true +authors.workspace = true +readme.workspace = true +edition.workspace = true +license-file.workspace = true + +[lib] +path = "src/lib.rs" + +[dependencies] +cxx = "1.0.186" + +[lints] +workspace = true diff --git a/score/language/rust/stop_token/cpp/stop_token_adapter.cpp b/score/language/rust/stop_token/cpp/stop_token_adapter.cpp new file mode 100644 index 0000000000..21fda1150e --- /dev/null +++ b/score/language/rust/stop_token/cpp/stop_token_adapter.cpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +#include "score/language/rust/stop_token/cpp/stop_token_adapter.h" + +namespace score::language::rust::stop_token +{ + +bool StopTokenStopRequested(const score::cpp::stop_token& token) noexcept +{ + return token.stop_requested(); +} + +bool StopTokenStopPossible(const score::cpp::stop_token& token) noexcept +{ + return token.stop_possible(); +} + +bool StopTokenEqual(const score::cpp::stop_token& lhs, const score::cpp::stop_token& rhs) noexcept +{ + return lhs == rhs; +} + +bool StopSourceStopRequested(const score::cpp::stop_source& source) noexcept +{ + return source.stop_requested(); +} + +bool StopSourceStopPossible(const score::cpp::stop_source& source) noexcept +{ + return source.stop_possible(); +} + +bool StopSourceRequestStop(const score::cpp::stop_source& source) noexcept +{ + // request_stop() only updates the shared stop state atomically. + return const_cast(source).request_stop(); +} + +std::shared_ptr StopSourceGetToken(const score::cpp::stop_source& source) noexcept +{ + return std::make_shared(source.get_token()); +} + +bool StopSourceEqual(const score::cpp::stop_source& lhs, const score::cpp::stop_source& rhs) noexcept +{ + return lhs == rhs; +} + +std::shared_ptr MakeDefaultStopToken() noexcept +{ + return std::make_shared(); +} + +std::shared_ptr MakeStopSource() noexcept +{ + return std::make_shared(); +} + +} // namespace score::language::rust::stop_token diff --git a/score/language/rust/stop_token/cpp/stop_token_adapter.h b/score/language/rust/stop_token/cpp/stop_token_adapter.h new file mode 100644 index 0000000000..0ab6333197 --- /dev/null +++ b/score/language/rust/stop_token/cpp/stop_token_adapter.h @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +#ifndef SCORE_LANGUAGE_RUST_STOP_TOKEN_CPP_STOP_TOKEN_ADAPTER_H +#define SCORE_LANGUAGE_RUST_STOP_TOKEN_CPP_STOP_TOKEN_ADAPTER_H + +#include + +#include + +namespace score::language::rust::stop_token +{ + +/// @brief Returns whether stop has been requested on a stop token. +bool StopTokenStopRequested(const score::cpp::stop_token& token) noexcept; + +/// @brief Returns whether stop remains possible on a stop token. +bool StopTokenStopPossible(const score::cpp::stop_token& token) noexcept; + +/// @brief Returns whether two stop tokens share the same stop state. +bool StopTokenEqual(const score::cpp::stop_token& lhs, const score::cpp::stop_token& rhs) noexcept; + +/// @brief Returns whether stop has been requested on a stop source. +bool StopSourceStopRequested(const score::cpp::stop_source& source) noexcept; + +/// @brief Returns whether a stop source has associated stop state. +bool StopSourceStopPossible(const score::cpp::stop_source& source) noexcept; + +/// @brief Requests stop through a stop source. +bool StopSourceRequestStop(const score::cpp::stop_source& source) noexcept; + +/// @brief Returns a token associated with a stop source. +std::shared_ptr StopSourceGetToken(const score::cpp::stop_source& source) noexcept; + +/// @brief Returns whether two stop sources share the same stop state. +bool StopSourceEqual(const score::cpp::stop_source& lhs, const score::cpp::stop_source& rhs) noexcept; + +/// @brief Creates a default-constructed stop token. +std::shared_ptr MakeDefaultStopToken() noexcept; + +/// @brief Creates a stop source with a new stop state. +std::shared_ptr MakeStopSource() noexcept; + +} // namespace score::language::rust::stop_token + +#endif // SCORE_LANGUAGE_RUST_STOP_TOKEN_CPP_STOP_TOKEN_ADAPTER_H diff --git a/score/language/rust/stop_token/src/lib.rs b/score/language/rust/stop_token/src/lib.rs new file mode 100644 index 0000000000..e5554d66b6 --- /dev/null +++ b/score/language/rust/stop_token/src/lib.rs @@ -0,0 +1,172 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +//! Lifetime-safe Rust bindings for C++ `score::cpp::stop_token` and +//! `score::cpp::stop_source`. + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("score/language/rust/stop_token/cpp/stop_token_adapter.h"); + + /// Opaque C++ `score::cpp::stop_token`. + #[namespace = "score::cpp"] + type stop_token; + + /// Opaque C++ `score::cpp::stop_source`. + #[namespace = "score::cpp"] + type stop_source; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopTokenStopRequested"] + fn stop_token_stop_requested(token: &stop_token) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopTokenStopPossible"] + fn stop_token_stop_possible(token: &stop_token) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopTokenEqual"] + fn stop_token_equal(lhs: &stop_token, rhs: &stop_token) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopSourceStopRequested"] + fn stop_source_stop_requested(source: &stop_source) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopSourceStopPossible"] + fn stop_source_stop_possible(source: &stop_source) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopSourceRequestStop"] + fn stop_source_request_stop(source: &stop_source) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopSourceGetToken"] + fn stop_source_get_token(source: &stop_source) -> SharedPtr; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "StopSourceEqual"] + fn stop_source_equal(lhs: &stop_source, rhs: &stop_source) -> bool; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "MakeDefaultStopToken"] + fn make_default_stop_token() -> SharedPtr; + + #[namespace = "score::language::rust::stop_token"] + #[cxx_name = "MakeStopSource"] + fn make_stop_source() -> SharedPtr; + } + + // This forces CXX to generate glue code for the shared pointers. + impl SharedPtr {} + impl SharedPtr {} +} + +/// Opaque view of C++ `score::cpp::stop_token`. +pub use ffi::stop_token as StopToken; + +/// Opaque view of C++ `score::cpp::stop_source`. +pub use ffi::stop_source as StopSource; + +/// Creates an owned, default-constructed token without associated stop state. +pub fn make_default_stop_token() -> cxx::SharedPtr { + ffi::make_default_stop_token() +} + +/// Creates an owned source with a new associated stop state. +pub fn make_stop_source() -> cxx::SharedPtr { + ffi::make_stop_source() +} + +unsafe impl Send for StopToken {} +unsafe impl Sync for StopToken {} +unsafe impl Send for StopSource {} +unsafe impl Sync for StopSource {} + +impl StopToken { + /// Returns whether a stop request has been made on this token's stop state. + pub fn stop_requested(&self) -> bool { + ffi::stop_token_stop_requested(self) + } + + /// Returns whether this token has associated stop state that can be stopped. + pub fn stop_possible(&self) -> bool { + ffi::stop_token_stop_possible(self) + } +} + +impl PartialEq for StopToken { + fn eq(&self, other: &Self) -> bool { + ffi::stop_token_equal(self, other) + } +} + +impl Eq for StopToken {} + +impl StopSource { + /// Returns whether a stop request has been made on this source's stop state. + pub fn stop_requested(&self) -> bool { + ffi::stop_source_stop_requested(self) + } + + /// Returns whether this source has associated stop state. + pub fn stop_possible(&self) -> bool { + ffi::stop_source_stop_possible(self) + } + + /// Requests stop on the associated stop state. + pub fn request_stop(&self) -> bool { + ffi::stop_source_request_stop(self) + } + + /// Returns an owned token associated with this source's stop state. + pub fn get_token(&self) -> cxx::SharedPtr { + ffi::stop_source_get_token(self) + } +} + +impl PartialEq for StopSource { + fn eq(&self, other: &Self) -> bool { + ffi::stop_source_equal(self, other) + } +} + +impl Eq for StopSource {} + +#[cfg(test)] +mod tests { + use super::{make_default_stop_token, make_stop_source}; + + #[test] + fn default_token_has_no_stop_state() { + let token = make_default_stop_token(); + let token = token.as_ref().expect("default token must be allocated"); + + assert!(!token.stop_requested()); + assert!(!token.stop_possible()); + } + + #[test] + fn source_requests_stop_visible_to_its_token() { + let source = make_stop_source(); + let token = source.as_ref().expect("stop source must be allocated").get_token(); + let token = token.as_ref().expect("source token must be allocated"); + + assert!(source.as_ref().expect("stop source must be allocated").stop_possible()); + assert!(!token.stop_requested()); + assert!(source.as_ref().expect("stop source must be allocated").request_stop()); + assert!(token.stop_requested()); + assert!(!source.as_ref().expect("stop source must be allocated").request_stop()); + } +}