use std::env;
use std::fmt::Display;
use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::io::Read;
use std::path::PathBuf;
use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
use highway::PortableHash;
use rand::distributions::{Alphanumeric, DistString};
use regex::Regex;
const DEFAULT_PYTHON_VERSION: &str = "3.12";
const KNOWN_DISTRIBUTION_FORMATS: &[&str] = &["tar|bzip2", "tar|gzip", "tar|zstd", "zip"];
const DEFAULT_CPYTHON_SOURCE: &str =
"https://github.com/indygreg/python-build-standalone/releases/download/";
const DEFAULT_PYPY_SOURCE: &str = "https://downloads.python.org/pypy/";
#[rustfmt::skip]
const DEFAULT_CPYTHON_DISTRIBUTIONS: &[(&str, &str, &str, &str, &str, &str)] = &[
("3.12", "linux", "aarch64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "powerpc64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "s390x", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "x86_64", "gnu", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "x86_64", "gnu", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64_v2-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "x86_64", "gnu", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64_v3-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "x86_64", "gnu", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64_v4-unknown-linux-gnu-install_only.tar.gz"),
("3.12", "linux", "x86_64", "musl", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz"),
("3.12", "linux", "x86_64", "musl", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64_v2-unknown-linux-musl-install_only.tar.gz"),
("3.12", "linux", "x86_64", "musl", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64_v3-unknown-linux-musl-install_only.tar.gz"),
("3.12", "linux", "x86_64", "musl", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64_v4-unknown-linux-musl-install_only.tar.gz"),
("3.12", "windows", "x86", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz"),
("3.12", "windows", "x86", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-i686-pc-windows-msvc-static-install_only.tar.gz"),
("3.12", "windows", "x86_64", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz"),
("3.12", "windows", "x86_64", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz"),
("3.12", "macos", "aarch64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-aarch64-apple-darwin-install_only.tar.gz"),
("3.12", "macos", "x86_64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.12.2%2B20240224-x86_64-apple-darwin-install_only.tar.gz"),
("3.11", "linux", "aarch64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "powerpc64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "s390x", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "x86", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.11.5%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "x86_64", "gnu", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "x86_64", "gnu", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64_v2-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "x86_64", "gnu", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64_v3-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "x86_64", "gnu", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64_v4-unknown-linux-gnu-install_only.tar.gz"),
("3.11", "linux", "x86_64", "musl", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz"),
("3.11", "linux", "x86_64", "musl", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64_v2-unknown-linux-musl-install_only.tar.gz"),
("3.11", "linux", "x86_64", "musl", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64_v3-unknown-linux-musl-install_only.tar.gz"),
("3.11", "linux", "x86_64", "musl", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64_v4-unknown-linux-musl-install_only.tar.gz"),
("3.11", "windows", "x86", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz"),
("3.11", "windows", "x86", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-i686-pc-windows-msvc-static-install_only.tar.gz"),
("3.11", "windows", "x86_64", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz"),
("3.11", "windows", "x86_64", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz"),
("3.11", "macos", "aarch64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-aarch64-apple-darwin-install_only.tar.gz"),
("3.11", "macos", "x86_64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.11.8%2B20240224-x86_64-apple-darwin-install_only.tar.gz"),
("3.10", "linux", "aarch64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "powerpc64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "s390x", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "x86", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.10.13%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "x86_64", "gnu", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "x86_64", "gnu", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64_v2-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "x86_64", "gnu", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64_v3-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "x86_64", "gnu", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64_v4-unknown-linux-gnu-install_only.tar.gz"),
("3.10", "linux", "x86_64", "musl", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz"),
("3.10", "linux", "x86_64", "musl", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64_v2-unknown-linux-musl-install_only.tar.gz"),
("3.10", "linux", "x86_64", "musl", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64_v3-unknown-linux-musl-install_only.tar.gz"),
("3.10", "linux", "x86_64", "musl", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64_v4-unknown-linux-musl-install_only.tar.gz"),
("3.10", "windows", "x86", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz"),
("3.10", "windows", "x86", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-i686-pc-windows-msvc-static-install_only.tar.gz"),
("3.10", "windows", "x86_64", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz"),
("3.10", "windows", "x86_64", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz"),
("3.10", "macos", "aarch64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-apple-darwin-install_only.tar.gz"),
("3.10", "macos", "x86_64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-x86_64-apple-darwin-install_only.tar.gz"),
("3.9", "linux", "aarch64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "powerpc64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-ppc64le-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "s390x", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-s390x-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "x86", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.9.18%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "x86_64", "gnu", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "x86_64", "gnu", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64_v2-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "x86_64", "gnu", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64_v3-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "x86_64", "gnu", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64_v4-unknown-linux-gnu-install_only.tar.gz"),
("3.9", "linux", "x86_64", "musl", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz"),
("3.9", "linux", "x86_64", "musl", "v2",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64_v2-unknown-linux-musl-install_only.tar.gz"),
("3.9", "linux", "x86_64", "musl", "v3",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64_v3-unknown-linux-musl-install_only.tar.gz"),
("3.9", "linux", "x86_64", "musl", "v4",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64_v4-unknown-linux-musl-install_only.tar.gz"),
("3.9", "windows", "x86", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz"),
("3.9", "windows", "x86", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-i686-pc-windows-msvc-static-install_only.tar.gz"),
("3.9", "windows", "x86_64", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz"),
("3.9", "windows", "x86_64", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz"),
("3.9", "macos", "aarch64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz"),
("3.9", "macos", "x86_64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.9.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz"),
("3.8", "linux", "aarch64", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz"),
("3.8", "linux", "x86", "gnu", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20230826/cpython-3.8.17%2B20230826-i686-unknown-linux-gnu-install_only.tar.gz"),
("3.8", "linux", "x86_64", "gnu", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz"),
("3.8", "linux", "x86_64", "musl", "v1",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-unknown-linux-musl-install_only.tar.gz"),
("3.8", "windows", "x86", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-i686-pc-windows-msvc-shared-install_only.tar.gz"),
("3.8", "windows", "x86", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-i686-pc-windows-msvc-static-install_only.tar.gz"),
("3.8", "windows", "x86_64", "msvc", "shared",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz"),
("3.8", "windows", "x86_64", "msvc", "static",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz"),
("3.8", "macos", "aarch64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz"),
("3.8", "macos", "x86_64", "", "",
"https://github.com/indygreg/python-build-standalone/releases/download/20240224/cpython-3.8.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz"),
("3.7", "linux", "x86_64", "gnu", "", "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-gnu-pgo-20200823T0036.tar.zst"),
("3.7", "linux", "x86_64", "musl", "", "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-unknown-linux-musl-noopt-20200823T0036.tar.zst"),
("3.7", "windows", "x86", "msvc", "shared", "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-i686-pc-windows-msvc-shared-pgo-20200823T0159.tar.zst"),
("3.7", "windows", "x86", "msvc", "static", "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-i686-pc-windows-msvc-static-noopt-20200823T0221.tar.zst"),
("3.7", "windows", "x86_64", "msvc", "shared", "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-pc-windows-msvc-shared-pgo-20200823T0118.tar.zst"),
("3.7", "windows", "x86_64", "msvc", "static", "https://github.com/indygreg/python-build-standalone/releases/download/20200822/cpython-3.7.9-x86_64-pc-windows-msvc-static-noopt-20200823T0153.tar.zst"),
("3.7", "macos", "x86_64", "", "", "https://github.com/indygreg/python-build-standalone/releases/download/20200823/cpython-3.7.9-x86_64-apple-darwin-pgo-20200823T2228.tar.zst"),
];
#[rustfmt::skip]
const DEFAULT_PYPY_DISTRIBUTIONS: &[(&str, &str, &str, &str, &str)] = &[
("pypy3.10", "linux", "aarch64", "gnu",
"https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2"),
("pypy3.10", "linux", "x86_64", "gnu",
"https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2"),
("pypy3.10", "windows", "x86_64", "msvc",
"https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip"),
("pypy3.10", "macos", "aarch64", "",
"https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2"),
("pypy3.10", "macos", "x86_64", "",
"https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2"),
("pypy3.9", "linux", "aarch64", "gnu",
"https://downloads.python.org/pypy/pypy3.9-v7.3.12-aarch64.tar.bz2"),
("pypy3.9", "linux", "x86_64", "gnu",
"https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux64.tar.bz2"),
("pypy3.9", "windows", "x86_64", "msvc",
"https://downloads.python.org/pypy/pypy3.9-v7.3.12-win64.zip"),
("pypy3.9", "macos", "aarch64", "",
"https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_arm64.tar.bz2"),
("pypy3.9", "macos", "x86_64", "",
"https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_x86_64.tar.bz2"),
("pypy2.7", "linux", "aarch64", "gnu",
"https://downloads.python.org/pypy/pypy2.7-v7.3.12-aarch64.tar.bz2"),
("pypy2.7", "linux", "x86_64", "gnu",
"https://downloads.python.org/pypy/pypy2.7-v7.3.12-linux64.tar.bz2"),
("pypy2.7", "windows", "x86_64", "msvc",
"https://downloads.python.org/pypy/pypy2.7-v7.3.12-win64.zip"),
("pypy2.7", "macos", "aarch64", "",
"https://downloads.python.org/pypy/pypy2.7-v7.3.12-macos_arm64.tar.bz2"),
("pypy2.7", "macos", "x86_64", "",
"https://downloads.python.org/pypy/pypy2.7-v7.3.12-macos_x86_64.tar.bz2"),
];
fn set_runtime_variable(name: &str, value: impl Display) {
println!("cargo:rustc-env={}={}", name, value)
}
fn check_environment_variable(name: &str) -> String {
let value = env::var(name).unwrap_or_default();
if value.is_empty() && env::var("DEBUG").unwrap() == "false" {
panic!("\n\n{name} environment variable is not set\n\n");
}
value
}
fn is_enabled(name: &str) -> bool {
["true", "1"].contains(&env::var(name).unwrap_or_default().as_str())
}
fn normalize_project_name(name: &String) -> String {
if !Regex::new(r"^([[:alnum:]]|[[:alnum:]][[:alnum:]._-]*[[:alnum:]])$")
.unwrap()
.is_match(name)
&& env::var("DEBUG").unwrap() == "false"
{
panic!(
"\n\nInvalid project name `{name}`; must only contain ASCII letters/digits, underscores, \
hyphens, and periods, and must begin and end with ASCII letters/digits.\n\n"
);
}
Regex::new(r"[-_.]+")
.unwrap()
.replace_all(name, "-")
.to_lowercase()
}
fn embed_file(name: &str) -> PathBuf {
[
env::var("CARGO_MANIFEST_DIR").unwrap().as_str(),
"src",
"embed",
name,
]
.iter()
.collect()
}
fn truncate_embed_file(path: &PathBuf) {
fs::File::create(path).unwrap().set_len(0).unwrap();
}
fn get_python_version() -> String {
let python_version = env::var("PYAPP_PYTHON_VERSION").unwrap_or_default();
if !python_version.is_empty() {
return python_version;
};
DEFAULT_PYTHON_VERSION.to_string()
}
fn get_distribution_source() -> String {
let distribution_source = env::var("PYAPP_DISTRIBUTION_SOURCE").unwrap_or_default();
if !distribution_source.is_empty() {
return distribution_source;
};
let selected_python_version = get_python_version();
let selected_platform = match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
"windows" => "windows",
"macos" | "ios" => "macos",
_ => "linux",
}
.to_string();
let selected_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let selected_variant = {
let mut variant = env::var("PYAPP_DISTRIBUTION_VARIANT").unwrap_or_default();
if variant.is_empty() {
if selected_platform == "windows" {
variant = "shared".to_string();
} else if selected_platform == "linux"
&& selected_arch == "x86_64"
&& selected_python_version != "3.7"
{
variant = "v3".to_string();
}
};
variant
};
let selected_abi = {
let mut abi = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
if abi.is_empty() {
if selected_platform == "windows" {
abi = "msvc".to_string();
} else if selected_platform == "linux" {
abi = "gnu".to_string();
}
} else if &abi == "gnu" && selected_platform == "windows" {
abi = "msvc".to_string();
};
abi
};
for (python_version, platform, arch, abi, variant, url) in DEFAULT_CPYTHON_DISTRIBUTIONS {
if python_version == &selected_python_version
&& platform == &selected_platform
&& arch == &selected_arch
&& abi == &selected_abi
&& variant == &selected_variant
{
return url.to_string();
}
}
for (python_version, platform, arch, abi, url) in DEFAULT_PYPY_DISTRIBUTIONS {
if python_version == &selected_python_version
&& platform == &selected_platform
&& arch == &selected_arch
&& abi == &selected_abi
{
return url.to_string();
}
}
panic!(
"\n\nNo default distribution source found\nPython version: {}\nPlatform: {}\nArchitecture: {}\nABI: {}\nVariant: {}\n\n",
selected_python_version, selected_platform, selected_arch, selected_abi, selected_variant
);
}
fn set_project_from_metadata(metadata: &str, file_name: &str) {
for item in ["Name", "Version"] {
match Regex::new(&format!(r"(?m)^{item}: (\S+)"))
.unwrap()
.captures(metadata)
{
Some(captures) => {
let value = if item == "Name" {
normalize_project_name(&captures[1].to_string())
} else {
captures[1].to_string()
};
set_runtime_variable(&format!("PYAPP_PROJECT_{}", item.to_uppercase()), value);
}
None => {
panic!("\n\nFailed to parse metadata {item} in {file_name}\n\n");
}
}
}
}
fn set_project_dependency_file(dependency_file: &str) {
if dependency_file.is_empty() {
set_runtime_variable("PYAPP_PROJECT_DEPENDENCY_FILE", "");
set_runtime_variable("PYAPP__PROJECT_DEPENDENCY_FILE_NAME", "");
return;
}
let path = PathBuf::from(dependency_file);
if !path.is_file() {
panic!("\n\nDependency file is not a file: {dependency_file}\n\n");
}
let file_name = path.file_name().unwrap().to_str().unwrap();
let contents = fs::read_to_string(dependency_file)
.unwrap_or_else(|_| panic!("\n\nFailed to read dependency file {dependency_file}\n\n"));
set_runtime_variable(
"PYAPP_PROJECT_DEPENDENCY_FILE",
STANDARD_NO_PAD.encode(contents),
);
set_runtime_variable("PYAPP__PROJECT_DEPENDENCY_FILE_NAME", file_name);
}
fn set_project() {
let embed_path = embed_file("project");
let local_path = env::var("PYAPP_PROJECT_PATH").unwrap_or_default();
if !local_path.is_empty() {
set_project_dependency_file("");
let path = PathBuf::from(&local_path);
if !path.is_file() {
panic!("\n\nProject path is not a file: {local_path}\n\n");
}
fs::copy(&local_path, &embed_path).unwrap_or_else(|_| {
panic!(
"\n\nFailed to copy project's archive from {local_path} to {embed_path}\n\n",
embed_path = embed_path.display()
)
});
let file_name = path.file_name().unwrap().to_str().unwrap();
if file_name.ends_with(".whl") {
let mut archive = zip::ZipArchive::new(File::open(embed_path).unwrap()).unwrap();
for i in (0..archive.len()).rev() {
let mut file = archive.by_index(i).unwrap();
let entry_path = file.enclosed_name().unwrap().to_string_lossy().to_string();
if entry_path.ends_with(".dist-info/METADATA") {
let mut metadata = String::new();
file.read_to_string(&mut metadata).unwrap();
set_project_from_metadata(&metadata, &entry_path);
set_runtime_variable("PYAPP__PROJECT_EMBED_FILE_NAME", file_name);
return;
}
}
} else if file_name.ends_with(".tar.gz") {
let gz = flate2::read::GzDecoder::new(File::open(embed_path).unwrap());
let mut archive = tar::Archive::new(gz);
for file in archive.entries().unwrap() {
let mut file = file.unwrap();
let entry_path = file.path().unwrap().to_string_lossy().to_string();
if entry_path.ends_with("/PKG-INFO") && entry_path.matches('/').count() == 1 {
let mut metadata = String::new();
file.read_to_string(&mut metadata).unwrap();
set_project_from_metadata(&metadata, &entry_path);
set_runtime_variable("PYAPP__PROJECT_EMBED_FILE_NAME", file_name);
return;
}
}
} else {
panic!("\n\nUnsupported project archive format: {file_name}\n\n");
}
panic!("\n\nUnable to find project metadata in {file_name}\n\n");
} else {
let project_name = check_environment_variable("PYAPP_PROJECT_NAME");
set_runtime_variable("PYAPP_PROJECT_NAME", normalize_project_name(&project_name));
let project_version = check_environment_variable("PYAPP_PROJECT_VERSION");
set_runtime_variable("PYAPP_PROJECT_VERSION", project_version);
let dependency_file = env::var("PYAPP_PROJECT_DEPENDENCY_FILE").unwrap_or_default();
if dependency_file.is_empty() {
set_project_dependency_file("");
} else {
set_project_dependency_file(&dependency_file);
}
set_runtime_variable("PYAPP__PROJECT_EMBED_FILE_NAME", "");
truncate_embed_file(&embed_path);
}
}
fn set_distribution() {
let embed_path = embed_file("distribution");
let mut hasher = PortableHash::default();
let local_path = env::var("PYAPP_DISTRIBUTION_PATH").unwrap_or_default();
if !local_path.is_empty()
&& !env::var("PYAPP_DISTRIBUTION_SOURCE")
.unwrap_or_default()
.is_empty()
{
panic!("\n\nBoth distribution path and source are set\n\n");
}
let distribution_source = if !local_path.is_empty() {
let path = PathBuf::from(&local_path);
if !path.is_file() {
panic!("\n\nDistribution path is not a file: {local_path}\n\n");
}
fs::copy(&local_path, &embed_path).unwrap_or_else(|_| {
panic!(
"\n\nFailed to copy distribution's archive from {local_path} to {embed_path}\n\n",
embed_path = embed_path.display()
)
});
let mut file = File::open(&embed_path).unwrap();
std::io::copy(&mut file, &mut hasher).unwrap();
local_path
} else if is_enabled("PYAPP_DISTRIBUTION_EMBED") {
let distribution_source = get_distribution_source();
let bytes = reqwest::blocking::get(&distribution_source)
.unwrap()
.bytes()
.unwrap();
fs::write(&embed_path, bytes).unwrap();
let mut file = File::open(&embed_path).unwrap();
std::io::copy(&mut file, &mut hasher).unwrap();
distribution_source
} else {
truncate_embed_file(&embed_path);
let distribution_source = get_distribution_source();
distribution_source.hash(&mut hasher);
distribution_source
};
set_runtime_variable("PYAPP_DISTRIBUTION_SOURCE", &distribution_source);
set_runtime_variable("PYAPP__DISTRIBUTION_ID", hasher.finish());
set_distribution_format(&distribution_source);
set_python_path(&distribution_source);
set_site_packages_path(&distribution_source);
set_distribution_pip_available(&distribution_source);
let python_isolation_flag = if get_python_version() == "pypy2.7" {
"-sE"
} else {
"-I"
};
set_runtime_variable("PYAPP__PYTHON_ISOLATION_FLAG", python_isolation_flag);
}
fn set_distribution_format(distribution_source: &String) {
let variable = "PYAPP_DISTRIBUTION_FORMAT";
let distribution_format = env::var(variable).unwrap_or_default();
if !distribution_format.is_empty() {
if KNOWN_DISTRIBUTION_FORMATS.contains(&distribution_format.as_str()) {
set_runtime_variable(variable, &distribution_format);
} else {
panic!("\n\nUnknown distribution format: {distribution_format}\n\n");
}
} else if distribution_source.ends_with(".tar.bz2") || distribution_source.ends_with(".bz2") {
set_runtime_variable(variable, "tar|bzip2");
} else if distribution_source.ends_with(".tar.gz") || distribution_source.ends_with(".tgz") {
set_runtime_variable(variable, "tar|gzip");
} else if distribution_source.ends_with(".tar.zst")
|| distribution_source.ends_with(".tar.zstd")
{
set_runtime_variable(variable, "tar|zstd");
} else if distribution_source.ends_with(".zip") {
set_runtime_variable(variable, "zip");
} else {
panic!("\n\nUnable to determine format for distribution source: {distribution_source}\n\n");
}
}
fn set_python_path(distribution_source: &str) {
let distribution_variable = "PYAPP_DISTRIBUTION_PYTHON_PATH";
let on_windows = env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows";
let python_path = env::var(distribution_variable).unwrap_or_default();
let relative_path = if !python_path.is_empty() {
python_path
} else if !env::var("PYAPP_DISTRIBUTION_PATH")
.unwrap_or_default()
.is_empty()
{
panic!("\n\nThe following option must be set when embedding a custom distribution: {distribution_variable}\n\n");
} else if distribution_source.starts_with(DEFAULT_CPYTHON_SOURCE) {
if get_python_version() == "3.7" {
if on_windows {
r"python\install\python.exe".to_string()
} else {
"python/install/bin/python3".to_string()
}
} else if on_windows {
r"python\python.exe".to_string()
} else {
"python/bin/python3".to_string()
}
} else if distribution_source.starts_with(DEFAULT_PYPY_SOURCE) {
let directory = distribution_source
.split('/')
.last()
.unwrap()
.trim_end_matches(".tar.bz2")
.trim_end_matches(".zip");
if on_windows {
format!(r"{}\pypy.exe", directory)
} else {
format!("{}/bin/pypy", directory)
}
} else if on_windows {
"python.exe".to_string()
} else {
"bin/python3".to_string()
};
set_runtime_variable(distribution_variable, &relative_path);
let installation_variable = "PYAPP__INSTALLATION_PYTHON_PATH";
if is_enabled("PYAPP_FULL_ISOLATION") {
set_runtime_variable(installation_variable, &relative_path);
} else if on_windows {
set_runtime_variable(installation_variable, r"Scripts\python.exe");
} else {
set_runtime_variable(installation_variable, "bin/python3");
};
}
fn set_site_packages_path(distribution_source: &str) {
let distribution_variable = "PYAPP_DISTRIBUTION_SITE_PACKAGES_PATH";
let on_windows = env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows";
let python_version = get_python_version();
let site_packages_path = env::var(distribution_variable).unwrap_or_default();
let relative_path = if !site_packages_path.is_empty() {
site_packages_path
} else if distribution_source.starts_with(DEFAULT_CPYTHON_SOURCE) {
if python_version == "3.7" {
if on_windows {
r"python\install\Lib\site-packages".to_string()
} else {
format!("python/install/lib/python{}/site-packages", python_version)
}
} else if on_windows {
r"python\Lib\site-packages".to_string()
} else {
format!("python/lib/python{}/site-packages", python_version)
}
} else if distribution_source.starts_with(DEFAULT_PYPY_SOURCE) {
let directory = distribution_source
.split('/')
.last()
.unwrap()
.trim_end_matches(".tar.bz2")
.trim_end_matches(".zip");
if python_version == "pypy2.7" {
if on_windows {
format!(r"{}\site-packages", directory)
} else {
format!("{}/site-packages", directory)
}
} else if on_windows {
format!(r"{}\Lib\site-packages", directory)
} else {
format!("{}/lib/{}/site-packages", directory, python_version)
}
} else if on_windows {
r"Lib\site-packages".to_string()
} else {
format!("lib/python{}/site-packages", python_version)
};
set_runtime_variable(distribution_variable, &relative_path);
let installation_variable = "PYAPP__INSTALLATION_SITE_PACKAGES_PATH";
if is_enabled("PYAPP_FULL_ISOLATION") {
set_runtime_variable(installation_variable, &relative_path);
} else if get_python_version() == "pypy2.7" {
set_runtime_variable(installation_variable, "site-packages");
} else if on_windows {
set_runtime_variable(installation_variable, r"Lib\site-packages");
} else {
set_runtime_variable(
installation_variable,
format!("lib/python{}/site-packages", python_version),
);
};
}
fn set_distribution_pip_available(distribution_source: &str) {
let variable = "PYAPP_DISTRIBUTION_PIP_AVAILABLE";
if is_enabled(variable)
|| (!distribution_source.is_empty()
&& !distribution_source.starts_with(DEFAULT_PYPY_SOURCE)
&& env::var("PYAPP_DISTRIBUTION_PATH")
.unwrap_or_default()
.is_empty()
&& env::var("PYAPP_DISTRIBUTION_SOURCE")
.unwrap_or_default()
.is_empty())
{
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_execution_mode() {
let module_variable = "PYAPP_EXEC_MODULE";
let module = env::var(module_variable).unwrap_or_default();
let spec_variable = "PYAPP_EXEC_SPEC";
let spec = env::var(spec_variable).unwrap_or_default();
let code_variable = "PYAPP_EXEC_CODE";
let code = env::var(code_variable).unwrap_or_default();
let script_variable = "PYAPP_EXEC_SCRIPT";
let script = env::var(script_variable).unwrap_or_default();
let notebook_variable = "PYAPP_EXEC_NOTEBOOK";
let notebook = env::var(notebook_variable).unwrap_or_default();
set_runtime_variable(module_variable, "");
set_runtime_variable(code_variable, "");
set_runtime_variable("PYAPP__EXEC_CODE_ENCODED", "0");
set_runtime_variable(script_variable, "");
set_runtime_variable("PYAPP__EXEC_SCRIPT_NAME", "");
set_runtime_variable("PYAPP__EXEC_SCRIPT_ID", "");
set_runtime_variable(notebook_variable, "");
set_runtime_variable("PYAPP__EXEC_NOTEBOOK_NAME", "");
set_runtime_variable("PYAPP__EXEC_NOTEBOOK_ID", "");
if [
module.is_empty(),
spec.is_empty(),
code.is_empty(),
script.is_empty(),
notebook.is_empty(),
]
.iter()
.filter(|x| !(**x))
.count()
> 1
{
panic!("\n\nThe {module_variable}, {spec_variable}, {code_variable}, {script_variable}, and {notebook_variable} options are mutually exclusive\n\n");
} else if !module.is_empty() {
set_runtime_variable(module_variable, &module);
} else if !spec.is_empty() {
let (module, object) = spec.split_once(':').unwrap();
set_runtime_variable(
code_variable,
format!("import {module};{module}.{object}()"),
);
} else if !code.is_empty() {
set_runtime_variable(code_variable, STANDARD_NO_PAD.encode(code));
set_runtime_variable("PYAPP__EXEC_CODE_ENCODED", "1");
} else if !script.is_empty() {
let path = PathBuf::from(&script);
if !path.is_file() {
panic!("\n\nScript is not a file: {script}\n\n");
}
let file_name = path.file_name().unwrap().to_str().unwrap();
let contents = fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("\n\nFailed to read script: {script}\n\n"));
let mut hasher = PortableHash::default();
hasher.write(contents.as_bytes());
set_runtime_variable(script_variable, STANDARD_NO_PAD.encode(contents));
set_runtime_variable("PYAPP__EXEC_SCRIPT_NAME", file_name);
set_runtime_variable("PYAPP__EXEC_SCRIPT_ID", hasher.finish());
} else if !notebook.is_empty() {
let path = PathBuf::from(¬ebook);
if !path.is_file() {
panic!("\n\nNotebook is not a file: {notebook}\n\n");
}
let file_name = path.file_name().unwrap().to_str().unwrap();
let contents = fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("\n\nFailed to read notebook: {notebook}\n\n"));
let mut hasher = PortableHash::default();
hasher.write(contents.as_bytes());
set_runtime_variable(notebook_variable, STANDARD_NO_PAD.encode(contents));
set_runtime_variable("PYAPP__EXEC_NOTEBOOK_NAME", file_name);
set_runtime_variable("PYAPP__EXEC_NOTEBOOK_ID", hasher.finish());
} else {
set_runtime_variable(
module_variable,
normalize_project_name(&env::var("PYAPP_PROJECT_NAME").unwrap_or_default())
.replace('-', "_"),
);
}
}
fn set_is_gui() {
let variable = "PYAPP_IS_GUI";
if is_enabled(variable) {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_isolation_mode() {
let variable = "PYAPP_FULL_ISOLATION";
if is_enabled(variable) {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_upgrade_virtualenv() {
let variable = "PYAPP_UPGRADE_VIRTUALENV";
if is_enabled(variable) || get_python_version() == "pypy2.7" {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_pip_external() {
let variable = "PYAPP_PIP_EXTERNAL";
if is_enabled(variable) {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_pip_version() {
let variable = "PYAPP_PIP_VERSION";
set_runtime_variable(variable, env::var(variable).unwrap_or("latest".to_string()));
}
fn set_pip_project_features() {
let variable = "PYAPP_PROJECT_FEATURES";
set_runtime_variable(variable, env::var(variable).unwrap_or_default());
}
fn set_pip_extra_args() {
let variable = "PYAPP_PIP_EXTRA_ARGS";
set_runtime_variable(variable, env::var(variable).unwrap_or_default());
}
fn set_pip_allow_config() {
let variable = "PYAPP_PIP_ALLOW_CONFIG";
if is_enabled(variable) {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_skip_install() {
let variable = "PYAPP_SKIP_INSTALL";
if is_enabled(variable) {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_indicator() {
let variable = "PYAPP_PASS_LOCATION";
if is_enabled(variable) {
set_runtime_variable(variable, "1");
} else {
set_runtime_variable(variable, "0");
}
}
fn set_self_command() {
let variable = "PYAPP_SELF_COMMAND";
let command_name = env::var(variable).unwrap_or_default();
if command_name == "none" {
set_runtime_variable(
variable,
Alphanumeric.sample_string(&mut rand::thread_rng(), 16),
);
set_runtime_variable("PYAPP__EXPOSED_COMMAND", "");
} else if !command_name.is_empty() {
set_runtime_variable(variable, &command_name);
set_runtime_variable("PYAPP__EXPOSED_COMMAND", &command_name);
} else {
set_runtime_variable(variable, "self");
set_runtime_variable("PYAPP__EXPOSED_COMMAND", "self");
}
}
fn set_exposed_commands() {
let indicator = Regex::new(r"(?m)^#\[command\(hide = env!").unwrap();
let commands_dir: PathBuf = [
env::var("CARGO_MANIFEST_DIR").unwrap().as_str(),
"src",
"commands",
"self_cmd",
]
.iter()
.collect();
for entry in fs::read_dir(commands_dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if !path.is_file() {
continue;
}
let command_name = path.file_stem().unwrap().to_str().unwrap();
let command_path = path.to_str().unwrap();
let command_source = fs::read_to_string(command_path).unwrap();
if indicator.is_match(&command_source) {
let variable = format!("PYAPP_EXPOSE_{}", command_name.to_uppercase());
if is_enabled(&variable) {
set_runtime_variable(&variable, "1");
} else {
set_runtime_variable(&variable, "0");
}
}
}
}
fn set_metadata_template() {
let variable = "PYAPP_METADATA_TEMPLATE";
let metadata_template = env::var(variable).unwrap_or_default();
if !metadata_template.is_empty() {
set_runtime_variable(variable, &metadata_template);
} else {
set_runtime_variable(variable, "{project} v{version}");
}
}
fn main() {
set_project();
set_distribution();
set_execution_mode();
set_is_gui();
set_isolation_mode();
set_upgrade_virtualenv();
set_pip_external();
set_pip_version();
set_pip_project_features();
set_pip_extra_args();
set_pip_allow_config();
set_skip_install();
set_indicator();
set_self_command();
set_exposed_commands();
set_metadata_template();
}