如何制作安装包打包软件

实现原理

本质就是将exe所需的所有资源制作为一个自解压文件(SFX)。

打包软件

本体

tauri+rust做配置界面

  • 打包文件夹
  • 界面方式(本地文件-单页面应用/网址)
  • 起始界面(资源路径)
  • pip(可新增)
  • install(进度回调)
  • complete(选项设置-快捷方式)

打包自解压

使用rust打包

toml 复制代码
[
dependencies
]
flate2 = { version = "1.0", features = ["zlib"] }
walkdir = "2.3"
tempfile = "3.3"
std = { version = "1.0", features = ["fs", "path", "process"] }
  • flate2:用于文件的压缩和解压缩操作,它提供了对 zlib 等压缩算法的支持。
  • walkdir:方便遍历目录及其子目录中的所有文件。
  • tempfile:用于创建临时目录和解压文件。
rust 复制代码
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::path::Path;
use tempfile::tempdir;
use walkdir::WalkDir;

// 压缩文件或目录
fn compress_files(source_paths: &[&str], output_path: &str) -> io::Result<()> {
    let output_file = File::create(output_path)?;
    let mut encoder = ZlibEncoder::new(BufWriter::new(output_file), Compression::default());

    for source_path in source_paths {
        for entry in WalkDir::new(source_path) {
            let entry = entry?;
            if entry.file_type().is_file() {
                let mut file = File::open(entry.path())?;
                let mut buffer = Vec::new();
                file.read_to_end(&mut buffer)?;

                // 写入文件名长度和文件名
                let file_name = entry.path().to_str().unwrap();
                encoder.write_all(&(file_name.len() as u32).to_be_bytes())?;
                encoder.write_all(file_name.as_bytes())?;

                // 写入文件内容长度和文件内容
                encoder.write_all(&(buffer.len() as u32).to_be_bytes())?;
                encoder.write_all(&buffer)?;
            }
        }
    }

    encoder.finish()?;
    Ok(())
}

// 解压文件
fn decompress_files(compressed_path: &str, destination_dir: &Path) -> io::Result<()> {
    let compressed_file = File::open(compressed_path)?;
    let mut decoder = flate2::read::ZlibDecoder::new(BufReader::new(compressed_file));

    loop {
        let mut name_length_bytes = [0; 4];
        if decoder.read_exact(&mut name_length_bytes).is_err() {
            break;
        }
        let name_length = u32::from_be_bytes(name_length_bytes) as usize;

        let mut name_bytes = vec![0; name_length];
        decoder.read_exact(&mut name_bytes)?;
        let file_name = String::from_utf8_lossy(&name_bytes);

        let mut content_length_bytes = [0; 4];
        decoder.read_exact(&mut content_length_bytes)?;
        let content_length = u32::from_be_bytes(content_length_bytes) as usize;

        let mut content_bytes = vec![0; content_length];
        decoder.read_exact(&mut content_bytes)?;

        let output_path = destination_dir.join(&file_name);
        fs::create_dir_all(output_path.parent().unwrap())?;
        let mut output_file = File::create(output_path)?;
        output_file.write_all(&content_bytes)?;
    }

    Ok(())
}

// 执行程序
fn execute_program(program_path: &Path) -> io::Result<()> {
    std::process::Command::new(program_path).spawn()?.wait()?;
    Ok(())
}

fn main() -> io::Result<()> {
    // 要压缩的文件或目录
    let source_paths = ["main.exe", "example.dll", "config.ini"];
    let compressed_file_path = "compressed_data.zlib";

    // 压缩文件
    compress_files(&source_paths, compressed_file_path)?;

    // 创建临时目录
    let temp_dir = tempdir()?;
    let temp_dir_path = temp_dir.path();

    // 解压文件
    decompress_files(compressed_file_path, temp_dir_path)?;

    // 执行主程序
    let main_program_path = temp_dir_path.join("main.exe");
    execute_program(&main_program_path)?;

    // 删除压缩文件
    fs::remove_file(compressed_file_path)?;

    Ok(())
}
  • compress_files 函数:
    • 遍历指定的文件和目录,使用 flate2 库的 ZlibEncoder 进行压缩。
    • 对于每个文件,先写入文件名的长度和文件名,再写入文件内容的长度和文件内容。
  • decompress_files 函数:
    • 读取压缩文件,使用 flate2 库的 ZlibDecoder 进行解压。
    • 根据之前写入的文件名长度和内容长度信息,将文件解压到指定的临时目录。
  • execute_program 函数 :使用 std::process::Command 执行指定路径的程序。
  • main 函数:
    • 调用 compress_files 函数将文件压缩到 compressed_data.zlib
    • 创建临时目录,调用 decompress_files 函数将压缩文件解压到临时目录。
    • 执行主程序 main.exe
    • 最后删除压缩文件。

编译并运行,生成自解压文件(SFX)

bash 复制代码
cargo build --release
./target/release/your_project_name

本体打包

在开发环境中使用 cargo 把 Rust 项目编译成可执行文件,并且使用 --release 选项来进行优化,生成高性能的二进制文件。在项目根目录下执行如下命令:

bash 复制代码
cargo build --release

需要将以上可以生成自解压文件的项目打包,去除cargo流程

安装过程

首先移除 src-tauri/tauri.conf.json 中的 window 这个属性配置

其次修改 src-tauri/main.rs , 在初始化app时,使用其 setup 这个 api 进行动态化初始一个 window:

rust 复制代码
tauri::Builder::default()
.setup(|app| {
    WindowBuilder::new(app, "main", WindowUrl::App("https://weread.qq.com/".into()))
    .title("")
    .build();
    Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");

tauri通过rust流式下载文件,并计算进度给前端

  1. 在 Rust 端,使用合适的网络库(如 reqwest)来发起文件下载请求。
  2. 在下载过程中,通过读取已接收的数据量来计算下载进度。
  3. 使用 Tauri 的消息传递机制(例如 invoke 方法)将进度信息传递给前端。
rust 复制代码
use reqwest::blocking::Response;
use tauri::api::http::send_message;

fn download_file(url: &str) {
    let client = reqwest::blocking::Client::new();
    let mut response = client.get(url).unwrap();

    let total_length = response
      .content_length()
      .unwrap_or(0);
    let mut downloaded_bytes = 0;

    let mut buffer = Vec::new();
    while let Some(chunk) = response.chunk().unwrap() {
        buffer.extend_from_slice(&chunk);
        downloaded_bytes += chunk.len();

        let progress = (downloaded_bytes as f64 / total_length as f64) * 100.0;
        // 将进度发送给前端
        send_message("download_progress", progress).unwrap();
    }

    // 保存文件等后续操作
}

进度

目前已完成前端ui部分

源码在github

相关推荐
渡之10 小时前
GRiM-Net 深度解析 | 无人机 GNSS 拒止场景下两阶段跨视角视觉定位框架
深度学习·算法·动态规划·无人机
测试仪器廖生1359025638511 小时前
罗德与施瓦茨 FSP13频谱分析仪FSP30
网络·人工智能·算法
happymaker062611 小时前
LeetCodeHot100——560.和为K的子数组
算法
dtq042411 小时前
C语言刷题数组5,6(求平均值,求最大值)
c语言·数据结构·算法
郭梧悠11 小时前
Hash算法入门Hash冲突解决方案
算法·哈希算法
洛水水12 小时前
【力扣100题】81.寻找两个正序数组的中位数
数据结构·算法·leetcode
happymaker062612 小时前
LeetCodeHot100——155.最小栈
算法
guyoung12 小时前
BoxAgnts 工具系统(6)——多 Provider 适配与 Agent 查询循环
rust·agent·ai编程
洛水水12 小时前
【力扣100题】85.每日温度
算法·leetcode·职场和发展
Coder-magician12 小时前
《代码随想录》刷题打卡day15:二叉树part05
数据结构·c++·算法