From 8c10675d8a2638a6ea2d497ab7392733b02dd255 Mon Sep 17 00:00:00 2001 From: Kingtous <kingtous@qq.com> Date: Wed, 21 Sep 2022 11:28:28 +0800 Subject: [PATCH] feat: windows portable build script --- Cargo.toml | 2 +- build.py | 27 ++- libs/portable/.gitignore | 3 + libs/portable/Cargo.lock | 285 ++++++++++++++++++++++++++++++++ libs/portable/Cargo.toml | 16 ++ libs/portable/build.rs | 5 + libs/portable/generate.py | 88 ++++++++++ libs/portable/icon.rc | 1 + libs/portable/requirements.txt | 1 + libs/portable/src/bin_reader.rs | 134 +++++++++++++++ libs/portable/src/main.rs | 51 ++++++ 11 files changed, 611 insertions(+), 2 deletions(-) create mode 100644 libs/portable/.gitignore create mode 100644 libs/portable/Cargo.lock create mode 100644 libs/portable/Cargo.toml create mode 100644 libs/portable/build.rs create mode 100644 libs/portable/generate.py create mode 100644 libs/portable/icon.rc create mode 100644 libs/portable/requirements.txt create mode 100644 libs/portable/src/bin_reader.rs create mode 100644 libs/portable/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 8670ade40..062e32abb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,7 @@ jni = "0.19" flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge" } [workspace] -members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/simple_rc"] +members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/simple_rc", "libs/portable"] [package.metadata.winres] LegalCopyright = "Copyright © 2022 Purslane, Inc." diff --git a/build.py b/build.py index 52ccbe41e..2c08e5af0 100755 --- a/build.py +++ b/build.py @@ -73,6 +73,11 @@ def make_parser(): action='store_true', help='Enable feature hwcodec' ) + parser.add_argument( + '--portable', + action='store_true', + help='Build windows portable' + ) return parser @@ -187,6 +192,20 @@ def build_flutter_arch_manjaro(): os.chdir('..') os.system('HBB=`pwd` FLUTTER=1 makepkg -f') +def build_flutter_windows_portable(): + os.system("cargo build --lib --features flutter --release") + os.chdir('flutter') + os.system("flutter build windows --release") + os.chdir('..') + os.chdir("libs/portable") + os.system("pip3 install -r requirements.txt") + os.system("python3 .\\generate.py -f ..\\..\\flutter\\build\\windows\\runner\Release\ -o . -e ..\\..\\flutter\\build\\windows\\runner\\Release\\rustdesk.exe") + os.chdir("../..") + if os.path.exists("./rustdesk_portable.exe"): + os.replace("./target/release/rustdesk-portable-packer.exe", "./rustdesk_portable.exe") + else: + os.rename("./target/release/rustdesk-portable-packer.exe", "./rustdesk_portable.exe") + print(f"output location: {os.path.abspath(os.curdir)}/rustdesk_portable.exe") def main(): parser = make_parser() @@ -201,13 +220,19 @@ def main(): '//#![windows_subsystem', '#![windows_subsystem')) if os.path.exists(exe_path): os.unlink(exe_path) - os.system('python3 res/inline-sciter.py') if os.path.isfile('/usr/bin/pacman'): os.system('git checkout src/ui/common.tis') version = get_version() features = ",".join(get_features(args)) flutter = args.flutter + if not flutter: + # not flutter, is sciter + os.system('python3 res/inline-sciter.py') + portable = args.portable if windows: + if portable: + build_flutter_windows_portable() + return os.system('cargo build --release --features ' + features) # os.system('upx.exe target/release/rustdesk.exe') os.system('mv target/release/rustdesk.exe target/release/RustDesk.exe') diff --git a/libs/portable/.gitignore b/libs/portable/.gitignore new file mode 100644 index 000000000..8dfaeb73d --- /dev/null +++ b/libs/portable/.gitignore @@ -0,0 +1,3 @@ +/target +*.exe +*.bin \ No newline at end of file diff --git a/libs/portable/Cargo.lock b/libs/portable/Cargo.lock new file mode 100644 index 000000000..623d64918 --- /dev/null +++ b/libs/portable/Cargo.lock @@ -0,0 +1,285 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "embed-resource" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936c1354206a875581696369aef920e12396e93bbd251c43a7a3f3fa85023a7d" +dependencies = [ + "cc", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustdesk-portable-packer" +version = "0.1.0" +dependencies = [ + "brotli", + "dirs", + "embed-resource", + "md5", +] + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" + +[[package]] +name = "syn" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/libs/portable/Cargo.toml b/libs/portable/Cargo.toml new file mode 100644 index 000000000..bdd7b23ca --- /dev/null +++ b/libs/portable/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rustdesk-portable-packer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +build = "build.rs" + +[build-dependencies] +embed-resource = "1.7" + +[dependencies] +brotli = "3.3.4" +dirs = "4.0.0" +md5 = "0.7.0" diff --git a/libs/portable/build.rs b/libs/portable/build.rs new file mode 100644 index 000000000..2fedd1d9c --- /dev/null +++ b/libs/portable/build.rs @@ -0,0 +1,5 @@ +extern crate embed_resource; + +fn main() { + embed_resource::compile("icon.rc"); +} diff --git a/libs/portable/generate.py b/libs/portable/generate.py new file mode 100644 index 000000000..640f2ae6a --- /dev/null +++ b/libs/portable/generate.py @@ -0,0 +1,88 @@ +from ast import parse +import os +import optparse +from hashlib import md5 +import brotli + +# file compress level(0-11) +compress_level = 11 +# 4GB maximum +length_count = 4 +# encoding +encoding = 'utf-8' + +# output: {path: (compressed_data, file_md5)} + + +def generate_md5_table(folder: str) -> dict: + res: dict = dict() + curdir = os.curdir + os.chdir(folder) + for root, _, files in os.walk('.'): + # remove ./ + for f in files: + md5_generator = md5() + full_path = os.path.join(root, f) + print(f"processing {full_path}...") + f = open(full_path, "rb") + content = f.read() + content_compressed = brotli.compress( + content, quality=compress_level) + md5_generator.update(content) + md5_code = md5_generator.hexdigest().encode(encoding=encoding) + res[full_path] = (content_compressed, md5_code) + os.chdir(curdir) + return res + + +def write_metadata(md5_table: dict, output_folder: str, exe: str): + output_path = os.path.join(output_folder, "data.bin") + with open(output_path, "wb") as f: + f.write("rustdesk".encode(encoding=encoding)) + for path in md5_table.keys(): + (compressed_data, md5_code) = md5_table[path] + data_length = len(compressed_data) + path = path.encode(encoding=encoding) + # path length & path + f.write((len(path)).to_bytes(length=length_count, byteorder='big')) + f.write(path) + # data length & compressed data + f.write((data_length).to_bytes( + length=length_count, byteorder='big')) + f.write(compressed_data) + # md5 code + f.write(md5_code) + # end + f.write("rustdesk".encode(encoding=encoding)) + # executable + f.write(exe.encode(encoding='utf-8')) + print(f"metadata had written to {output_path}") + + +def build_portable(output_folder: str): + os.chdir(output_folder) + os.system("cargo build --release") + +# Linux: python3 generate.py -f ../rustdesk-portable-packer/test -o . -e ./test/main.py +# Windows: python3 .\generate.py -f ..\rustdesk\flutter\build\windows\runner\Debug\ -o . -e ..\rustdesk\flutter\build\windows\runner\Debug\rustdesk.exe +if __name__ == '__main__': + parser = optparse.OptionParser() + parser.add_option("-f", "--folder", dest="folder", + help="folder to compress") + parser.add_option("-o", "--output", dest="output_folder", + help="the root of portable packer project") + parser.add_option("-e", "--executable", dest="executable", + help="specify startup file") + (options, args) = parser.parse_args() + folder = options.folder + output_folder = os.path.abspath(options.output_folder) + + exe: str = os.path.abspath(options.executable) + if not exe.startswith(os.path.abspath(folder)): + print("the executable must locate in source folder") + exit(-1) + exe = '.' + exe[len(os.path.abspath(folder)):] + print("executable path: " + exe) + md5_table = generate_md5_table(folder) + write_metadata(md5_table, output_folder, exe) + build_portable(output_folder) diff --git a/libs/portable/icon.rc b/libs/portable/icon.rc new file mode 100644 index 000000000..2f41e79d8 --- /dev/null +++ b/libs/portable/icon.rc @@ -0,0 +1 @@ +rustdesk_icon ICON "../../res/icon.ico" \ No newline at end of file diff --git a/libs/portable/requirements.txt b/libs/portable/requirements.txt new file mode 100644 index 000000000..ac6cebc82 --- /dev/null +++ b/libs/portable/requirements.txt @@ -0,0 +1 @@ +brotli \ No newline at end of file diff --git a/libs/portable/src/bin_reader.rs b/libs/portable/src/bin_reader.rs new file mode 100644 index 000000000..499c18e2c --- /dev/null +++ b/libs/portable/src/bin_reader.rs @@ -0,0 +1,134 @@ +use std::{ + fs::{self}, + io::{Cursor, Read}, + path::PathBuf, +}; + +const BIN_DATA: &[u8] = include_bytes!("../data.bin"); +// 4bytes +const LENGTH: usize = 4; +const IDENTIFIER_LENGTH: usize = 8; +const MD5_LENGTH: usize = 32; +const BUF_SIZE: usize = 4096; + +pub(crate) struct BinaryData { + pub md5_code: &'static [u8], + // compressed gzip data + pub raw: &'static [u8], + pub path: String, +} + +pub(crate) struct BinaryReader { + pub files: Vec<BinaryData>, + pub exe: String, +} + +impl Default for BinaryReader { + fn default() -> Self { + let (files, exe) = BinaryReader::read(); + Self { files, exe } + } +} + +impl BinaryData { + fn decompress(&self) -> Vec<u8> { + let cursor = Cursor::new(self.raw); + let mut decoder = brotli::Decompressor::new(cursor, BUF_SIZE); + let mut buf = Vec::new(); + decoder.read_to_end(&mut buf).unwrap(); + buf + } + + pub fn write_to_file(&self, prefix: &PathBuf) { + let p = prefix.join(&self.path); + if let Some(parent) = p.parent() { + if !parent.exists() { + let _ = fs::create_dir_all(parent); + } + } + if p.exists() { + // check md5 + let f = fs::read(p.clone()).unwrap(); + let digest = format!("{:x}", md5::compute(&f)); + let md5_record = String::from_utf8_lossy(self.md5_code); + if digest == md5_record { + // same, skip this file + println!("skip {}", &self.path); + return; + } else { + println!("writing {}", p.display()); + println!("{} -> {}", md5_record, digest) + } + } + let _ = fs::write(p, self.decompress()); + } +} + +impl BinaryReader { + fn read() -> (Vec<BinaryData>, String) { + let mut base: usize = 0; + let mut parsed = vec![]; + assert!(BIN_DATA.len() > IDENTIFIER_LENGTH, "bin data invalid!"); + let mut iden = String::from_utf8_lossy(&BIN_DATA[base..base + IDENTIFIER_LENGTH]); + if iden != "rustdesk" { + panic!("bin file is not vaild!"); + } + base += IDENTIFIER_LENGTH; + loop { + iden = String::from_utf8_lossy(&BIN_DATA[base..base + IDENTIFIER_LENGTH]); + if iden == "rustdesk" { + base += IDENTIFIER_LENGTH; + break; + } + // start reading + let mut offset = 0; + let path_length = u32::from_be_bytes([ + BIN_DATA[base + offset], + BIN_DATA[base + offset + 1], + BIN_DATA[base + offset + 2], + BIN_DATA[base + offset + 3], + ]) as usize; + offset += LENGTH; + let path = + String::from_utf8_lossy(&BIN_DATA[base + offset..base + offset + path_length]) + .to_string(); + offset += path_length; + // file sz + let file_length = u32::from_be_bytes([ + BIN_DATA[base + offset], + BIN_DATA[base + offset + 1], + BIN_DATA[base + offset + 2], + BIN_DATA[base + offset + 3], + ]) as usize; + offset += LENGTH; + let raw = &BIN_DATA[base + offset..base + offset + file_length]; + offset += file_length; + // md5 + let md5 = &BIN_DATA[base + offset..base + offset + MD5_LENGTH]; + offset += MD5_LENGTH; + parsed.push(BinaryData { + md5_code: md5, + raw: raw, + path: path, + }); + base += offset; + } + // executable + let executable = String::from_utf8_lossy(&BIN_DATA[base..]).to_string(); + (parsed, executable) + } + + #[cfg(unix)] + pub fn configure_permission(&self, prefix: &PathBuf) { + use std::os::unix::prelude::PermissionsExt; + + let exe_path = prefix.join(&self.exe); + if exe_path.exists() { + let f = File::open(exe_path).unwrap(); + let meta = f.metadata().unwrap(); + let mut permissions = meta.permissions(); + permissions.set_mode(0o755); + f.set_permissions(permissions).unwrap(); + } + } +} diff --git a/libs/portable/src/main.rs b/libs/portable/src/main.rs new file mode 100644 index 000000000..614c4c17c --- /dev/null +++ b/libs/portable/src/main.rs @@ -0,0 +1,51 @@ +#![windows_subsystem = "windows"] + +use std::{ + path::PathBuf, + process::{Command, Stdio}, +}; + +use bin_reader::BinaryReader; + +pub mod bin_reader; + +const APP_PREFIX: &str = "rustdesk"; +const APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME"; + +fn setup(reader: BinaryReader) -> Option<PathBuf> { + // home dir + if let Some(dir) = dirs::data_local_dir() { + let dir = dir.join(APP_PREFIX); + for file in reader.files.iter() { + file.write_to_file(&dir); + } + #[cfg(unix)] + reader.configure_permission(&dir); + Some(dir.join(&reader.exe)) + } else { + eprintln!("not found data local dir"); + None + } +} + +fn execute(path: PathBuf) { + println!("executing {}", path.display()); + // setup env + let exe = std::env::current_exe().unwrap(); + let exe_name = exe.file_name().unwrap(); + // run executable + Command::new(path) + .env(APPNAME_RUNTIME_ENV_KEY, exe_name) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .expect(&format!("failed to execute {:?}", exe_name)); +} + +fn main() { + let reader = BinaryReader::default(); + if let Some(exe) = setup(reader) { + execute(exe); + } +}