#cyphal #serialization #uavcan #canadensis #type #dsdl #generate

bin+lib canadensis_codegen_rust

根据 Cyphal DSDL 文件生成数据类型的 Rust 代码

8 个版本

0.4.2 2023 年 9 月 5 日
0.4.1 2023 年 3 月 30 日
0.3.2 2022 年 10 月 18 日
0.3.1 2022 年 7 月 15 日
0.2.0 2021 年 10 月 31 日

#524 in 编码

41 个月下载量
用于 canadensis_macro

MIT/Apache

310KB
7K SLoC

canadensis_codegen_rust: Cyphal 数据类型的 Rust 代码生成器

此应用程序读取 Cyphal 数据结构描述语言 (DSDL) 文件。它生成 Rust 代码来表示 Cyphal 数据类型,序列化它们,并反序列化它们。

使用方法

编译 DSDL 包

canadensis_codegen_rust compile-输出-文件输入-目录..

指定一个代码将写入的输出文件,以及包含 DSDL 文件的一个或多个输入目录。编译器将读取输入目录中的所有 DSDL 文件,并将所有数据类型的代码放入输出文件。

示例

克隆 Cyphal 公共规范数据类型存储库 并运行 canadensis_codegen_rust compile -o lib.rs public_regulated_data_types

为了便于查看,您可能希望使用 rustfmt 重新格式化生成的代码。

使用生成的代码

编译器只生成一个 .rs 文件。要编译它,您需要将其放入某些 Cargo 包中。该文件可以与其它代码一起放在子模块中,或者在库的根目录下。

生成的代码与 no_std 兼容,因此如果需要,您可以在 lib.rs 文件中包含 #![no_std]

生成的代码依赖于几个外部库进行数据类型和序列化。运行 canadensis_codegen_rust print-dependencies 以显示依赖规范。您应将输出包含在包的 Cargo.toml 文件中。

格式化

默认情况下,生成的代码没有一致的格式。要格式化它,在运行 canadensis_codegen_rust 时添加 --rustfmt 选项。此选项需要默认路径下预安装的 rustfmt 二进制文件。

外部模块

为了说明,假设你有一个这个文件:depends_on_prdt/canadensis/test/ContainsHealth.1.0.uavcan

uavcan.node.Health.1.0 health0
uavcan.node.Health.1.0 health1

@sealed

此数据类型依赖于Cyphal公共规范数据类型中的Health类型。通常,你需要将这两个包一起编译:canadensis_codegen_rust compile -o lib.rs public_regulated_data_types depends_on_prdt。这将生成包含所有公共规范数据类型和ContainsHealth类型的文件。

然而,如果你的代码已经依赖于canadensis_data_types,你将需要编译所有公共规范数据类型两次,并且生成的ContainsHealth代码将与canadensis_data_types不兼容。

相反,你可以将uavcanreg包标记为外部,并使用以下命令引用canadensis_data_types中的预生成代码:canadensis_codegen_rust compile public_regulated_data_types depends_on_prdt --external-package uavcan,canadensis_data_types::uavcan --external-package reg,canadensis_data_types::reg -o lib.rs

该命令将生成此代码

#[cfg(not(target_endian = "little"))]
compile_error!("Zero-copy serialization requires a little-endian target");
#[allow(unused_variables, unused_braces, unused_parens)]
#[deny(unaligned_references)]
pub mod canadensis {
    pub mod test {
        pub mod contains_health_0_1 {
            /// `canadensis.test.ContainsHealth.0.1`
            ///
            /// Fixed size 2 bytes
            pub struct ContainsHealth {
                /// `uavcan.node.Health.1.0`
                ///
                /// Always aligned
                /// Size 8 bits
                pub health0: ::canadensis_data_types::uavcan::node::health_1_0::Health,
                /// `uavcan.node.Health.1.0`
                ///
                /// Always aligned
                /// Size 8 bits
                pub health1: ::canadensis_data_types::uavcan::node::health_1_0::Health,
            }
            impl ::canadensis_encoding::DataType for ContainsHealth {
                const EXTENT_BYTES: Option<u32> = None;
            }
            impl ::canadensis_encoding::Message for ContainsHealth {}
            impl ContainsHealth {}
            impl ::canadensis_encoding::Serialize for ContainsHealth {
                fn size_bits(&self) -> usize {
                    16
                }
                fn serialize(&self, cursor: &mut ::canadensis_encoding::WriteCursor<'_>) {
                    cursor.write_composite(&self.health0);
                    cursor.write_composite(&self.health1);
                }
            }
            impl ::canadensis_encoding::Deserialize for ContainsHealth {
                fn deserialize(
                    cursor: &mut ::canadensis_encoding::ReadCursor<'_>,
                ) -> ::core::result::Result<Self, ::canadensis_encoding::DeserializeError>
                where
                    Self: Sized,
                {
                    Ok(ContainsHealth {
                        health0: { cursor.read_composite()? },
                        health1: { cursor.read_composite()? },
                    })
                }
            }
        }
    }
}

请注意,此文件仅包含自定义的ContainsHealth类型,并引用现有的Health类型:canadensis_data_types::uavcan::node::health_1_0::Health.

生成枚举

所有非联合的DSDL类型通常会被转换成Rust结构体。一些DSDL类型作为Rust枚举表示更好,因为它们有一个单整型字段,允许有某些预定义的值。两个例子是uavcan.node.Health.1.0uavcan.diagnostic.Severity.1.0

此软件目前不能自动为非联合的DSDL类型生成枚举。相反,可以通过添加一个带有注释的DSDL类型(消息、请求或响应)来启用枚举生成:#[canadensis(enum)]。注释中的#必须位于行的开头,以便被识别。该注释应在第一个字段之前。

为了生成Rust枚举,DSDL类型必须遵循以下所有规则

  • 类型不是联合类型
  • 类型恰好有一个字段,且该字段为无符号整型
  • 如果类型有任何常量,则每个常量都与字段具有相同的类型
  • 类型中没有任何两个常量的值相同

任何被标记为 #[canadensis(enum)] 而又不遵循规则的类型将导致代码生成错误。

在生成的代码中,每个常量都成为一个枚举变体。正常的 Rust 枚举限制适用。在反序列化时,任何与 DSDL 常量不相等的值将导致错误。

示例

DSDL

#[canadensis(enum)]
uint1 value

uint1 BUTTERCREAM = 0
uint1 PASTRY_CREAM = 1

@sealed

生成的代码

/// `canadensis.Uint1Exhaustive.1.0`
///
/// Fixed size 1 bytes
///
#[cfg_attr(not(doctest), doc = "[canadensis(enum)]")]
pub enum Uint1Exhaustive {
    Buttercream,
    PastryCream,
}

限制

  • 支持零拷贝序列化/反序列化的类型始终被标记为 #[repr(C, packed),但有时它们不需要打包,#[repr(C)] 就足够了。打包的结构体不易处理,因为不允许引用其字段,并且不能在它们上使用 derives。
  • 某些生成的序列化/反序列化代码没有充分利用始终对齐的字段

依赖关系

~7–17MB
~239K SLoC