跳转至

一次混合开发交叉编译mongo-c-driver和rust的笔记

20240425更新

如果不需要静态编译, 直接拉 ubuntu:22.04 的docker镜像, 在里面安装几个软件就行了, 哈哈

apt update
apt install pkg-config
apt install gcc
apt install g++
apt install zlib1g-dev
apt install libicu-dev
apt install cmake

apt install openssl libssl-dev  # 如果想静态编译, openssl 使用下面 前置安装 里面写的方式安装吧, 用apt安装的路径太乱了

然后 mongo 就可以在rust中使用了

Cargo.toml:

[dependencies]
once_cell = "1.19.0"
mongo_driver = "0.14.2"
bson = "1.2.0"

main.rs:

// use std::cell::RefCell;
// use std::collections::HashMap;
// use std::sync::{Arc, RwLock};
// use once_cell::sync::Lazy;
// use pyo3::prelude::*;
// use pyo3::types::{IntoPyDict, PyDict};
//
//
// #[derive(Clone, Debug)]
// struct DBClient {
//     client: PyObject
// }
//
// struct DBPool {
//     inner: HashMap<String, PyResult<DBClient>>,
// }
//
// static DB_POOL:Lazy<RwLock<DBPool>> = Lazy::new(||{
//     RwLock::new(DBPool {
//         inner: HashMap::new(),
//     })
// });
//
// pub fn get_db(uri: String) -> Result<DBClient, String> {
//     {
//         let mut db_pool = DB_POOL.read()
//             .unwrap();
//
//         if let Some(_db_client_ret) = db_pool.inner.get(&uri) {
//             return match _db_client_ret {
//                 Ok(db_client) => Ok(db_client.clone()),
//                 Err(py_err) => Err(py_err.to_string()),
//             };
//         }
//     }
//
//     let mut db_pool = DB_POOL.write()
//         .unwrap();
//     let db_client_ret = db_pool.inner
//         .entry(uri)
//         .or_insert_with_key(|uri_ref| {
//             Python::with_gil(|py| {
//                 let pymongo = py.import_bound("pymongo")?;
//                 let locals = [("pymongo", pymongo)].into_py_dict_bound(py);
//                 let exec_str = format!("pymongo.MongoClient({uri_ref})");
//                 let db_client = py.eval_bound(&exec_str, None, Some(&locals))?.into_py(py);
//                 Ok(DBClient {
//                     client: db_client
//                 })
//             })
//         });
//     match db_client_ret {
//         Ok(db_client) => Ok(db_client.clone()),
//         Err(py_err) => Err(py_err.to_string()),
//     }
// }

use mongo_driver::collection::UpdateOptions;
use mongo_driver::flags::UpdateFlag;

fn t1() {
    use mongo_driver::client::{ClientPool, Uri};
    use bson::{Bson, doc};

    let mongo_uri = Uri::new("mongodb://mongo_pkRxPF:mongo_8r5mbt@192.168.230.131:27017").expect("mongodb uri 格式错误");
    let mongo_pool = ClientPool::new(mongo_uri, None);
    let client = mongo_pool.pop();
    let db = client.get_database("god");
    let coll = db.get_collection("quant");

    for i in 0..100{
        let filter = doc! {
            "stock_code": i,
        };

        let mongo_data = doc! {
            "stock_code": i,
        };
        let mut up_opt = UpdateOptions::default();
        up_opt.update_flags.add(UpdateFlag::Upsert);
        coll.update(
            &filter,
            &mongo_data,
            Some(&up_opt),
        );
    }

    let query = doc! {
        "$query": {
        }
    };
    for cur in coll.find(&query, None).unwrap() {
        println!("{:?}", cur);
    }
}

fn main() {
    t1();

}

1. 前置安装

openssl

在混合开发时, 尽量安装编译位置无关的静态库, 防止rust在链接c库时找不到符号(血淋淋的教训)

# 查看路径
which openssl

# 查看版本
openssl version

# 查看centos版本
cat /etc/redhat-release

# 尽量重新安装

# 下载
wget http://www.openssl.org/source/openssl-1.0.2j.tar.gz

# 解压
tar -xzf openssl-1.0.2j.tar.gz && cd openssl-1.0.2j

注意

# 设置安装路径以及安装方式, fPIC方式, -prefix参数为欲安装之目录, 也就是安装后的档案会出现在该目录下
sudo ./config  -fPIC --prefix=/usr/local/openssl
sudo ./config -t  # !!! 配置生效
sudo make && sudo make install

cmake

根据文档要求, cmake需要是3.2或之后的版本, camke --version发现系统的cmake还停留在2.x版本, 先升级一下cmake

# 安装新版本
cd ~ && mkdir cmake-source && cd cmake-source && wget https://cmake.org/files/v3.23/cmake-3.23.0-rc1.tar.gz && tar xvf cmake-3.23.0-rc1.tar.gz && cd cmake-3.23.0-rc1/ && ./bootstrap && sudo gmake && sudo gmake install

# 查看版本
cmake --version  

2. 源码安装mongo-c-driver

使用cmake安装

可能会出错

# 下载
wget https://github.com/mongodb/mongo-c-driver/releases/download/1.17.4/mongo-c-driver-1.17.4.tar.gz && tar -xzf mongo-c-driver-1.17.4.tar.gz && cd mongo-c-driver-1.17.4

# 安装准备
mkdir cmake_build && cd cmake_build

# 安装(配置有点多, 这里是按照rust箱中build.rs中抄下来的, 具体配置的参数, 需要在mongo-c-driver参考查询:  http://mongocxx.org/mongocxx-v3/installation/)

# 使用release安装
cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF -DCMAKE_BUILD_TYPE=Release -DENABLE_SSL=OPENSSL -DENABLE_SASL=OFF -DENABLE_STATIC=ON -DENABLE_BSON=ON -DENABLE_ENABLE_EXAMPLES=OFF -DENABLE_TESTS=OFF -DENABLE_SHM_COUNTERS=OFF -DWITH_PIC=ON ..

# 如果使用release安装出现问题, 那就去掉release
cmake -DENABLE_AUTOMATIC_INIT_AND_CLEANUP=OFF -DENABLE_SSL=OPENSSL -DENABLE_SASL=OFF -DENABLE_STATIC=ON -DENABLE_BSON=ON -DENABLE_ENABLE_EXAMPLES=OFF -DENABLE_TESTS=OFF -DENABLE_SHM_COUNTERS=OFF -DWITH_PIC=ON ..

安装出错

这里要特别注意cmake的输出, 会提示你系统还缺少什么库, 不要无视

例如, openssl和snappy没有安装正确, 虽然最终cmake和make不会报错, 但是最后面用mongocxx的时候会提示ssl错误, 不能连接mongodb

根据具体情况选择处理, 具体处理方式如下:

设置 OPENSSL_ROOT_DIR

-- Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR) CMake Error at src/libmongoc/CMakeLists.txt:178 (message): No SSL library found

# 根据提示信息设置 OPENSSL_ROOT_DIR 即可(即我们安装openssl的根目录: /usr/local/openssl)
export OPENSSL_ROOT_DIR=/usr/local/openssl

未找到各种库: not found xxx

安装snappy(暂时不用安装)

好像不用安装了~

# 下载
wget https://src.fedoraproject.org/repo/pkgs/snappy/snappy-1.1.8.tar.gz/sha512/efe18ff1b3edda1b4b6cefcbc6da8119c05d63afdbf7a784f3490353c74dced76baed7b5f1aa34b99899729192b9d657c33c76de4b507a51553fa8001ae75c1c/snappy-1.1.8.tar.gz && tar xvfz snappy-1.1.8.tar.gz && cd snappy-1.1.8/ && mkdir cmake_build && cd cmake_build

vi ../CMakeLists.txt
# 添加
add_compile_options(-fPIC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -fPIC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -fPIC")


cmake ..
# 暂停一下
make
sudo make install

在make前, 我们需要先编辑一下CMakeCache.txt, 再次修改CMAKE_CXX_FLAGS:STRING=-fPIC, 为什么要加 -fPIC 呢? 因为不这样的话, 待会mongo-c-driver执行make时,应该会报错

安装icu

安装完毕, 他的.pc需要后面使用, 所在位置/usr/local/lib/pkgconfig

cd ~ && mkdir icu-source && cd icu-source && wget https://github.com/unicode-org/icu/releases/download/release-50-2/icu4c-50_2-src.tgz && tar -xzf icu4c-50_2-src.tgz && cd icu/source && ./configure && sudo make && sudo make install
安装musl版本的
cd ~ && mkdir icu-source && cd icu-source
wget https://github.com/unicode-org/icu/releases/download/release-75-1/icu4c-75_1-src.tgz
tar -xzf icu4c-75_1-src.tgz
cd icu/source
# 中间提示缺啥文件给他创建啥文件
./configure --prefix=/root/musl-cross-make/output/x86_64-unknown-linux-musl --host=x86_64-unknown-linux-musl --with-cross-build=/root/icu_build
安装sasl
yum install cyrus-sasl-lib.x86_64
yum install cyrus-sasl-devel.x86_64
yum install libgsasl-devel.x86_64
yum install saslwrapper-devel.x86_64
安装zlib
# 方式1(不推荐)
sudo yum install -y zlib zlib-devel

# 方式2 (推荐)
官网地址:http://www.zlib.net/ 我下载的是: zlib-1.2.11.tar.gz, 然后上传到centos服务器
wget http://www.zlib.net/zlib-1.2.11.tar.gz
tar -zxvf zlib-1.2.11.tar.gz
cd zlib-1.2.11/


 ./configure
sudo make && sudo make install


# ~~ 备用
./configure --prefix=/usr/local/zlib

make

make check

make install

echo "/usr/local/zlib/lib" >> /etc/ld.so.conf 

ldconfig -v
注意

安装完毕注意要把pkg的pc文件拷贝到 和icu.pc的同文件夹(/usr/local/lib/pkgconfig/)

sudo find / -name "*zlib.pc*"  # 找到zlib.pc

cp your_zlib.pc_path your_icu.pc_path

最后make安装mongo-c-driver

# 错误排查完毕, 我们继续
sudo make
sudo make install

卸载

有两种方法卸载已安装的组件, 第一种方法是直接调用卸载程序, 在 Linux/Unix 下命令如下

 sudo /usr/local/share/mongo-c-driver/uninstall.sh

第二种方法是在构建目录中执行以下命令, 假设卸载时的状态和使用安装时的状态相同

sudo make uninstall
rm -rf ./*

2. 混合开发

1. 前置准备

cd ~ && cargo new my_code
cd my_code
cargo run

2. 代码引入 mongo-c-driver 箱

这里我们手动git下载这个箱, 方便我们更改源码修改build过程(不修改的话, 太慢了而且也没走代理)

# 下载
git clone https://github.com/appsignal/mongo-rust-driver.git

# 引入, 修改my_code的Cargo.toml
mongo_driver= {path = "./mongo-rust-driver"}

# 修改build过程
vi mongo-rust-driver/mongoc-sys/build.rs 

# build.rs main 下第一行开始添加


    let pkg = pkg_config::Config::new();
    pkg.probe("zlib").expect("Cannot find zlib");
    #[cfg(target_os = "linux")] 
    pkg.probe("icu-i18n").expect("Cannot find icu");

    println!("cargo:rustc-link-search=native=/usr/local/lib64");  // TODO 这里替换成你的mongolib的安装路径
    println!("cargo:rustc-link-lib=static=bson-static-1.0");
    println!("cargo:rustc-link-lib=static=mongoc-static-1.0");
    println!("cargo:rustc-link-lib=resolv");
    return;
# src/main.rs 修改
use mongo_driver;
use mongo_driver::client::{Uri, ClientPool};
use mongo_driver:: {CommandAndFindOptions, flags};
fn main() {
    let A_uri:Uri = Uri::new("mongodb://127.0.0.1:27017").unwrap();
    let A_pool:ClientPool = ClientPool::new(A_uri.clone(), None);
    println!("Hello, world!");
}

3. 开始build

传递环境变量给Cargo

好了, 我们在 mongo-rust-driver/mongoc-sys/build.rs, 指定了mongoc-sys链接的一些库, 但是, 在build my_code 这个bin的时候, 还需要我们手动传递环境变量我们的一些库文件所在的文件夹路径给 Cargo, 所以build的命令就如下

cargo build

出错了

找不到openssl根目录出错了!

Could not find directory of OpenSSL installation, and this -sys crate cannot proceed without this knowledge. If OpenSSL is installed and this crate had trouble finding it, you can set the OPENSSL_DIR environment variable for the compilation process.

Make sure you also have the development packages of openssl installed. For example, libssl-dev on Ubuntu or openssl-devel on Fedora.

If you're in a situation where you think the directory should be found automatically, please open a bug at https://github.com/sfackler/rust-openssl and include information about your system as well as this message

# 提示说的很明白我们设置 openssl的根目录即可(注意这里和上方是不同的过程~发出的错误), 然后继续build
export OPENSSL_DIR=/usr/local/openssl
找不到icu的pkgconfig了!

我们添加pkg配置即可

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/

3. 静态链接交叉编译

芜湖, 开始痛苦之旅, 不过笔者已经踩通了如下一条路~

本地centos 编译为musl单文件, 放到服务器上运行

安装rustc工具链

rustup target add x86_64-unknown-linux-musl
rustup toolchain install stable-x86_64-unknown-linux-musl

纯rust代码编译

如果你的代码是纯rust的(你的程序和依赖包都是rust写的), 通过cargo添加target参数直接使用即可

cargo build --target=x86_64-unknown-linux-musl

混合开发编译

如果你的代码中使用了c库, 就需要单独按照 rust musl 的 toolchain进行编译, 使用 https://github.com/richfelker/musl-cross-make这个仓库的代码即可编译出 x86_64-unknown-linux-musl 的 toolchain

我们手动构建工具链

# 下载
git clone https://github.com/richfelker/musl-cross-make.git

# 构建我们需要的工具包 (这个过程会很慢大概30分钟)
sudo make TARGET=x86_64-unknown-linux-musl install

# 构建完毕 我们查看构建后的目录
output/bin: 目录包含了链接器以及编译器
output/x86_64-unknown-linux-musl: 该目录包含了交叉编译所需要的所有文件

我们修改CC环境变量进行编译

# 我们使用cargo check 先试试
CC=/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc RUSTFLAGS="-L/usr/local/lib64" cargo check --target=x86_64-unknown-linux-musl

! 出错了

Install a sysroot for the target platform and configure it via PKG_CONFIG_SYSROOT_DIR and PKG_CONFIG_PATH, or install a cross-compiling wrapper for pkg-config and set it via PKG_CONFIG environment variable.', mongo-rust-driver/mongoc-sys/build.rs:10:23 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

# 根据提示我们设置 PKG_CONFIG_SYSROOT_DIR
PKG_CONFIG_SYSROOT_DIR=/home/zhangy233/musl/musl-cross-make/output/x86_64-unknown-linux-musl CC=/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc RUSTFLAGS="-L/usr/local/lib64" cargo check --target=x86_64-unknown-linux-musl
PKG_CONFIG_SYSROOT_DIR=/usr/lib/x86_64-linux-gnu/pkgconfig/ CC=/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc RUSTFLAGS="-L/usr/local/lib64" cargo check --target=x86_64-unknown-linux-musl
也可以使用的下面的设置
export PKG_CONFIG_PATH=/home/zhangy233/musl/musl-cross-make/output/x86_64-unknown-linux-musl/pkgconfig:$PKG_CONFIG_PATH

~~ 我们现在已经能成功编译了, 但是能够编译还不够, 链接仍然会报错, 我们如果使用 cargo build 会报如下错误

note: /usr/bin/ld: /home/zhangy233/my_code/target/x86_64-unknown-linux-musl/debug/deps/libmongoc_sys-a79b80fa87011230.rlib(kms_request_str.c.o): undefined reference to symbol '__strdup@@GLIBC_2.2.5' //usr/lib64/libc.so.6: error adding symbols: DSO missing from command line collect2: error: ld returned 1 exit status

= help: some extern functions couldn't be found; some native libraries may need to be installed or have their path specified = note: use the -l flag to specify native libraries to link = note: use the cargo:rustc-link-lib directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

# 糟糕, 好像是libc 出现问题了 我们再手动传给rustc 参数表示我们链接 libc
PKG_CONFIG_SYSROOT_DIR=/home/zhangy233/musl/musl-cross-make/output/x86_64-unknown-linux-musl CC=/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc RUSTFLAGS="-lc -L/usr/local/lib64" cargo build --target=x86_64-unknown-linux-musl

能够编译还不行, 我们需要链接修改我们的链接器, 我们手动指定链接器试试?

# 修改 ~/.cargo/config, 添加

[target."x86_64-unknown-linux-musl"]
linker = "/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc"
ar = "/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-ar"
# 再次编译?
PKG_CONFIG_SYSROOT_DIR=/home/zhangy233/musl/musl-cross-make/output/x86_64-unknown-linux-musl CC=/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc RUSTFLAGS="-lc -L/usr/local/lib64" cargo build --target=x86_64-unknown-linux-musl

好了大功告成, 让我们看一下我们的可执行文件

 PKG_CONFIG_SYSROOT_DIR=/home/zhangy233/musl/musl-cross-make/output/x86_64-unknown-linux-musl CC=/home/zhangy233/musl/musl-cross-make/output/bin/x86_64-unknown-linux-musl-gcc RUSTFLAGS="-lc -L/usr/local/lib64" cargo build --bin KlineData --target=x86_64-unknown-linux-musl

其他错误

Failed to find OpenSSL development headers.

You can try fixing this setting the OPENSSL_DIR environment variable pointing to your OpenSSL installation or installing OpenSSL headers package specific to your distribution:

# 我们根据错误提示设置 OPENSSL_DIR即可, 再次编译
 OPENSSL_ROOT_DIR=/usr/local/openssl RUSTFLAGS="-L/usr/local/lib64" cargo build --target=x86_64-unknown-linux-musl

参考链接/鸣谢:

  1. https://zhuanlan.zhihu.com/p/106621926
  2. https://zhuanlan.zhihu.com/p/376471249
  3. https://stackoverflow.com/questions/31492799/cross-compile-a-rust-application-from-linux-to-windows
  4. https://stackoverflow.com/questions/68871193/pkg-config-error-during-rust-cross-compilation
  5. https://stackoverflow.com/questions/19901934/libpthread-so-0-error-adding-symbols-dso-missing-from-command-line
  6. http://mongocxx.org/mongocxx-v3/installation/