Le blog du jour
OpenWrt facile

26 janvier 2025

Rust facile dans OpenWrt

Création d’un package Rust

Un package Rust nécessite le paquet rust-lang fourni par openwrt depuis  la version 23.05. La création du package est similaire à celles des autres paquets, elle se base sur la présence d’un Makefile faisant appel à l’environnement de compilation d’OpenWrt. Ce paquet dépend du paquet hôte rust et inclut les règles rust-package. Le répertoire de génération doit contenir les fichiers du projet Rust, à minima Cargo.toml et mains.rs.

Le paquet basique testrust permet de créer un exécutable affichant un texte :

Paquet testrust

testrust

├── Cargo.toml

├── Makefile

└── src

    └── main.rs

Makefile

include $(TOPDIR)/rules.mk

 

PKG_NAME:=testrust

PKG_VERSION:=0.0.1

PKG_RELEASE:=1

 

PKG_BUILD_DEPENDS:=rust/host

PKG_BUILD_PARALLEL:=1

 

include $(INCLUDE_DIR)/package.mk

include $(TOPDIR)/package/feeds/packages/rust/rust-package.mk

 

define Package/testrust

  SECTION:=Perso

  CATEGORY:=Utilities

  TITLE:=Test rust

  DEPENDS:=

endef

 

define Build/Prepare

        mkdir -p $(PKG_BUILD_DIR)

        mkdir -p $(PKG_BUILD_DIR)/src

        $(CP) ./src/* $(PKG_BUILD_DIR)/src/

        $(CP) Cargo.toml $(PKG_BUILD_DIR)/

endef

 

$(eval $(call RustBinPackage,testrust))

$(eval $(call BuildPackage,testrust))

src/main.rs code source

fn main() {

  println!("[rust] start");

}

Cargo.toml fichier manifeste

[package]

name = "testrust"

version = "0.0.1"

La compilation du paquet nécessite la compilation de rust-lang qui se base sur les cibles rust pour générer l’environnement de compilation croisé.

Le paquet testrust génère un exécutable testrust que l‘on peut lancer sur la console :

root@OpenWrt:~# testrust

[rust] start

Utilisation d’une librairie C

Le paquet testrust est ainsi intégré à l’environnement de compilation d’OpenWrt, l’utilisation des autres paquets s’en trouve facilitée.

L’utilisation d’une librairie personnelle développée en C est simple.

La librairie basique libcool permet de renseigner une structure avec des paramètres en entrée :

cool.h

typedef struct CoolStruct {

    int x;

    int y;

} CoolStruct;

 

void cool_function(int i, char c, CoolStruct* cs);

cool.c

#include <stdio.h>

#include "cool.h"

 

void cool_function(int i, char c, CoolStruct* cs) {

        cs->x = i+c;

        cs->y = i;

}

Pour l’utiliser dans le paquet testrust il suffit de la déclarer dans un nouveau fichier build.rs qui va l’indiquer au système de génération de l’exécutable :

build.rs

fn main() {

    println!("cargo::rustc-link-lib=dylib=cool");

}

Ce fichier doit être incorporé au répertoire de génération, il suffit pour cela d’ajouter sa copie par le Makefile outre la dépendance :

define Build/Prepare

        mkdir -p $(PKG_BUILD_DIR)

        mkdir -p $(PKG_BUILD_DIR)/src

        $(CP) ./src/* $(PKG_BUILD_DIR)/src/

        $(CP) Cargo.toml $(PKG_BUILD_DIR)/

$(CP) build.rs $(PKG_BUILD_DIR)/

endef

Il est ensuite nécessaire de définir l’interface avec la librairie dans le fichier source rust. Pour cela il est nécessaire d’utiliser les types explicitement C compatibles, d’indiquer explicitement la représentation C compatible des structures et d’indiquer l’utilisation de C ABI par les fonctions lors de la définition de leur signature. Enfin l’appel aux fonctions étrangères est forcément unsafe :

main.rs

use std::os::raw::c_int;

use std::os::raw::c_char;

 

#[repr(C)]

pub struct CoolStruct {

    pub x: c_int,

    pub y: c_int,

}

 

extern "C" {

    pub fn cool_function(i: c_int, c: c_char, cs: *mut CoolStruct);

}

 

fn main() {

  println!("[rust] start\n");

 

  let mut cs = CoolStruct { x : 0, y : 0 };

  unsafe {

        cool_function(50,36,&mut cs);

          println!("cool structure : {} {} gives x {} and y {}",50,36,cs.x,cs.y);

  }

}

Utilisation de la libc

L’utilisation des fonctions de la libc, ici musl, est simple, il suffit de les définir en tant que C, ici les fonctions abs, sqrt et pow :

use std::os::raw::c_int;

use std::os::raw::c_double;

use std::os::raw::c_char;

 

extern "C" {

    fn abs(num: c_int) -> c_int;

    fn sqrt(num: c_double) -> c_double;

    fn pow(num: c_double, power: c_double) -> c_double;

}

 

fn main() {

  println!("[rust] start\n");

 

  let x: i32 = -123;

  println!("\nAbsolute value of {x}: {}.",

             unsafe { abs(x) });

 

  let n: f64 = 9.0;

  let p: f64 = 3.0;

  println!("\n{n} raised to {p}: {}.",

             unsafe { pow(n, p) });

 

  let mut y: f64 = 64.0;

  println!("\nSquare root of {y}: {}.",

             unsafe { sqrt(y) });

   y = -3.14;

   println!("\nSquare root of {y}: {}.",

             unsafe { sqrt(y) });

   

}

Utilisation des paquets rust

Enfin l’utilisation des autres paquets rust est aussi très simple. Par exemple l’utilisation de rand nécessite juste sa déclaration en tant que dépendance dans le fichier manifeste.

Cargo.toml

[package]

name = "testrust"

version = "0.0.1"

 

[dependencies]

rand = "0.8.4"

Et la fonction random, par exemple est maintenant disponible :

fn main() {

  println!("[rust] start\n");

 

  let x: u8 = rand::prelude::random();

  println!("\nrandom number {}", x);

}

En conclusion

Le paquet testrust génère maintenant un exécutable utilisant une librairie dynamique C, la libc, et le paquet rand.

main.rs

use std::os::raw::c_int;

use std::os::raw::c_double;

use std::os::raw::c_char;

 

#[repr(C)]

pub struct CoolStruct {

    pub x: c_int,

    pub y: c_int,

}

 

extern "C" {

    pub fn cool_function(i: c_int, c: c_char, cs: *mut CoolStruct);

    fn abs(num: c_int) -> c_int;

    fn sqrt(num: c_double) -> c_double;

    fn pow(num: c_double, power: c_double) -> c_double;

}

 

fn main() {

  println!("[rust] start\n");

 

  let mut cs = CoolStruct { x : 0, y : 0 };

  unsafe {

        cool_function(50,36,&mut cs);

          println!("cool structure : {} {} gives x {} and y {}",50,36,cs.x,cs.y);

  }

 

  let x: i32 = -123;

  println!("\nAbsolute value of {x}: {}.",

             unsafe { abs(x) });

 

  let n: f64 = 9.0;

  let p: f64 = 3.0;

  println!("\n{n} raised to {p}: {}.",

             unsafe { pow(n, p) });

 

  let mut y: f64 = 64.0;

  println!("\nSquare root of {y}: {}.",

             unsafe { sqrt(y) });

  y = -3.14;

  println!("\nSquare root of {y}: {}.",

             unsafe { sqrt(y) });

 

  let x: u8 = rand::prelude::random();

  println!("\nrandom number {}", x);

}

Après génération l’éxécutable testrust est utilisable via la console :

root@OpenWrt:~# testrust

[rust] start

 

cool structure : 50 36 gives x 86 and y 50

 

Absolute value of -123: 123.

 

9 raised to 3: 729.

 

Square root of 64: 8.

 

Square root of -3.14: NaN.

 

random number 212