diff --git a/readme.md b/readme.md index 25ffc9b..214c02c 100644 --- a/readme.md +++ b/readme.md @@ -15,9 +15,19 @@ https://user-images.githubusercontent.com/38364983/205297243-6a1b698b-4b8b-40c8- * Enables `import site` * Downloads `get-pip.py` and install `pip` * (Optional) installs other packages in `requirements.txt` -* (Optional) compresses the python into single zip file. +* (Optional) compresses the python into a single zip file. -## Usage +## Download embeddable python with pip installed +The python executables with pip installed available (Only amd64). +|version|link +|--|--| +|3.11.0|https://github.com/europeanplaice/distribute-embeddable-python/releases/download/v3.11.0/python-3.11.0-embed-amd64.zip +|3.10.8|https://github.com/europeanplaice/distribute-embeddable-python/releases/download/v3.10.8/python-3.10.8-embed-amd64.zip +|3.9.13|https://github.com/europeanplaice/distribute-embeddable-python/releases/download/v3.9.13/python-3.9.13-embed-amd64.zip +|3.8.10|https://github.com/europeanplaice/distribute-embeddable-python/releases/download/v3.8.10/python-3.8.10-embed-amd64.zip +|3.7.9|https://github.com/europeanplaice/distribute-embeddable-python/releases/download/v3.7.9/python-3.7.9-embed-amd64.zip + +## Make your own python with pip and libraries 1. Install Rust from https://www.rust-lang.org/ 2. Clone this repository and change your current directory to this repo 3. Build Rust and make an executable @@ -26,19 +36,27 @@ https://user-images.githubusercontent.com/38364983/205297243-6a1b698b-4b8b-40c8- 4. Run the executable ``` -distribute_embeddable_python.exe [OPTIONS] --pyversion +Usage: distribute_embeddable_python.exe [OPTIONS] --pyversion Options: - -p, --pyversion: python version e.g. 3.11.0 - -s, --savepath: (optional) where to save the configured python - -r, --requirements: (optional) requirements.txt path to install libraries from - -c, --compress: (optional) compresses the python into single zip file. + -p, --pyversion Python version e.g. 3.11.0 + --cpu Cpu type to install. it must be 'win32' or 'amd64' or 'arm64' (arm64 only if pyversion >= 3.11). Defaults to 'amd64' + -s, --savepath where to save the python. Defaults to './python-{pyversion}-embed-{cpu}' + -r, --requirements requirements.txt path to install libraries from. if not specified, no libraries will be installed + -i, --install libraries to install. If you install multiple libraries, the command must be surrounded by "". This is ignored if requirements is set. + -c, --compress if true it compresses the python into a single zip file. + -h, --help Print help information + -V, --version Print version information ``` -without `--savepath`, the folder is created in your current directory as `python-{pyversion}-embed-amd64`. ### example -`distribute_embeddable_python.exe --pyversion 3.10.8 --compress` +`distribute_embeddable_python.exe --pyversion 3.10.8 --compress --install "pandas numpy tqdm" ` +`distribute_embeddable_python.exe --pyversion 3.10.8 --requirements ./requirements.txt ` + +## How to install libraries manually +Because PATH of the embeddable python or pip is not added, you can't call pip as you usually do like `pip install numpy`. +Instead, you can call pip from the path of python such as `python-3.11.0-embed-amd64\python.exe -m pip install numpy` ## Test diff --git a/src/main.rs b/src/main.rs index f6aa582..f596b45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,15 +11,28 @@ use std::process::Command; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Args { + /// Python version e.g. 3.11.0 #[arg(short, long)] pyversion: String, + /// Cpu type to install. it must be 'win32' or 'amd64' or 'arm64' (arm64 only if pyversion >= 3.11). Defaults to 'amd64' + #[arg(long)] + cpu: Option, + + /// where to save the python. Defaults to './python-{pyversion}-embed-{cpu}' #[arg(short, long)] savepath: Option, + /// requirements.txt path to install libraries from. if not specified, no libraries will be installed. #[arg(short, long)] requirements: Option, + /// libraries to install. If you install multiple libraries, the command must be surrounded by "". + /// This is ignored if requirements is set. + #[arg(short, long)] + install: Option, + + /// if true it compresses the python into a single zip file #[arg(short, long)] compress: bool, } @@ -45,16 +58,18 @@ fn make_semantic_versioning(ver: &String) -> SemanticVersioning { fn distribute( pyversion: &String, + cpu: &String, savepath: &String, requirements: Option, + install: Option, compress: bool, ) -> Result<(), io::Error> { let sv = make_semantic_versioning(pyversion); - let zipfilepath = format!("python-{}-embed-amd64.zip", pyversion); + let zipfilepath = format!("python-{}-embed-{}.zip", pyversion, cpu); download( format!( - "https://www.python.org/ftp/python/{}/python-{}-embed-amd64.zip", - pyversion, pyversion + "https://www.python.org/ftp/python/{}/python-{}-embed-{}.zip", + pyversion, pyversion, cpu ) .to_string(), zipfilepath.to_string(), @@ -92,8 +107,10 @@ fn distribute( .status() .expect("failed to execute process"); + println!("requirements: {:?}", requirements); + match requirements { - Some(path) => { + Some(_path) => { Command::new("cmd") .arg("/C") .arg(format!("{}\\python.exe", savepath.replace("/", "\\"))) @@ -101,11 +118,30 @@ fn distribute( .arg("pip") .arg("install") .arg("-r") - .arg(path.replace("/", "\\")) + .arg(_path.replace("/", "\\")) .output() .expect("failed to execute process"); - } - None => (), + }, + None => { + println!("install: {:?}", install); + match install { + Some(libraries) => { + println!("begin install"); + let splitted_lib = libraries.split(" "); + let mut com = Command::new("cmd"); + com.arg("/C") + .arg(format!("{}\\python.exe", savepath.replace("/", "\\"))) + .arg("-m") + .arg("pip") + .arg("install"); + for lib in splitted_lib { + com.arg(lib); + } + com.output() + .expect("failed to execute process"); + }, + None => (), + }}, } if compress == true { @@ -128,11 +164,23 @@ fn distribute( fn main() -> Result<(), Box> { let args = Args::parse(); + let cpu = match args.cpu { + Some(cpu) => cpu, + None => "amd64".to_string(), + }; + let savepath = match args.savepath { Some(path) => path, - None => format!("./python-{}-embed-amd64", args.pyversion), + None => format!("./python-{}-embed-{}", args.pyversion, cpu), }; - distribute(&args.pyversion, &savepath, args.requirements, args.compress)?; + distribute( + &args.pyversion, + &cpu, + &savepath, + args.requirements, + args.install, + args.compress, + )?; Ok(()) } @@ -149,51 +197,75 @@ mod tests { use std::fs::{remove_file, write}; use std::{fs::remove_dir_all, process::Command}; - fn run_test(pyversion: &String) { - let body = format!("numpy",); - - write(format!("{}_requirements.txt", pyversion), body).unwrap(); - distribute( - &pyversion, - &format!("test_{}/python-{}-embed-amd64", pyversion, pyversion).to_string(), - Some(format!("{}_requirements.txt", pyversion)), - false, - ) - .unwrap(); + fn run_test(pyversion: &String, install: Option) { + let body = format!("numpy\npandas",); + + match install.clone() { + Some(_install) => { + distribute( + &pyversion, + &"amd64".to_string(), + &format!("test_{}/python-{}-embed-amd64", pyversion, pyversion).to_string(), + None, + Some(_install), + false, + ) + .unwrap(); + }, + None => { + write(format!("{}_requirements.txt", pyversion), body).unwrap(); + distribute( + &pyversion, + &"amd64".to_string(), + &format!("test_{}/python-{}-embed-amd64", pyversion, pyversion).to_string(), + Some(format!("{}_requirements.txt", pyversion)), + None, + false, + ) + .unwrap(); + } + } let status = Command::new(format!( "test_{}\\python-{}-embed-amd64\\python.exe", pyversion, pyversion )) .arg("-c") - .arg("try:\n\timport numpy\nexcept:\n\traise") + .arg("try:\n\timport numpy\n\timport pandas\nexcept:\n\traise") .status() .expect("failed to execute process"); assert!(status.success()); - remove_file(format!("{}_requirements.txt", pyversion)).unwrap(); + match install { + Some(_) => (), + None => remove_file(format!("{}_requirements.txt", pyversion)).unwrap() + } remove_dir_all(format!("test_{}", pyversion)).unwrap(); } + #[test] + fn test_3_11_2_no_requirements() { + run_test(&"3.11.2".to_string(), Some("numpy pandas".to_string())); + } #[test] fn test_3_11_0() { - run_test(&"3.11.0".to_string()); + run_test(&"3.11.0".to_string(), None); } #[test] fn test_3_10_8() { - run_test(&"3.10.8".to_string()); + run_test(&"3.10.8".to_string(), None); } #[test] fn test_3_9_13() { - run_test(&"3.9.13".to_string()); + run_test(&"3.9.13".to_string(), None); } #[test] fn test_3_8_10() { - run_test(&"3.8.10".to_string()); + run_test(&"3.8.10".to_string(), None); } #[test] fn test_3_7_9() { - run_test(&"3.7.9".to_string()); + run_test(&"3.7.9".to_string(), None); } }