1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
mod kvstore;
mod package_db;
mod prelude;
mod resolve;
mod util;
mod vocab;
mod env;
pub mod error;
mod output;
mod platform_tags;
mod seek_slice;
#[cfg(test)]
mod test_util;
mod trampolines;
mod tree;
use std::path::Path;
use crate::{env::EnvForest, prelude::*, resolve::Brief};
use clap::Parser;
use kvstore::KVDirStore;
use resolve::AllowPre;
#[derive(Parser)]
#[command(author, version, about)]
struct Cli {
#[command(flatten)]
output_args: output::OutputArgs,
}
fn main() -> Result<()> {
let cli = Cli::parse();
output::init(&cli.output_args);
let env_forest = EnvForest::new(Path::new("posy-test-forest"))?;
let build_tmp = tempfile::TempDir::new()?;
let build_store = KVDirStore::new(build_tmp.path())?;
let db = package_db::PackageDB::new(
&vec![
Url::parse("https://pybi.vorpus.org")?,
Url::parse("https://pypi.org/simple/")?,
],
PROJECT_DIRS.cache_dir(),
// PackageDB needs a place to install packages, in case it has to build some
// sdists. Using a shared env_forest is efficient, because it means different
// builds can share the same package installs.
&env_forest,
// This is the temporary directory we use for sdist builds. It's also a
// content-addressed store, so if we want to build the same package twice (e.g.
// first to get metadata, and then to get a wheel), we can re-use the same build
// directory.
&build_store,
)?;
// We can resolve and install for arbitrary platforms. But for this demo we'll just
// use the platform of the machine we're running on. Or platforms, in case it
// supports several (e.g. macOS arm64+x86_64, Windows 32bit+64bit, Linux
// manylinux+musllinux, etc.).
let platforms = PybiPlatform::native_platforms()?;
// A "brief" is a user-level description of a desired environment.
// https://en.wikipedia.org/wiki/Brief_(architecture)
let brief = Brief {
// "cpython_unofficial" is the package name I used for my test pybis at
// pybi.vorpus.org. We restrict to 3.10 or earlier because peewee upstream is
// broken on 3.11 (it attempts to use the now-private longintrepr.h)
python: "cpython_unofficial >= 3, < 3.11".try_into().unwrap(),
requirements: vec![
// Simple pure-Python package with some dependencies
"trio".try_into().unwrap(),
// Package with binary wheels
"numpy".try_into().unwrap(),
// Package with entrypoint scripts
"black".try_into().unwrap(),
// Package with no wheels, only sdist
"peewee".try_into().unwrap(),
],
allow_pre: AllowPre::Some(HashSet::new()),
};
// A "blueprint" is a set of fully-resolved package pins describing an environment,
// like a lock-file.
let blueprint = brief.resolve(&db, &platforms, None, &[])?;
// And an "env" of course is an installed environment.
let env = env_forest.get_env(&db, &blueprint, &platforms, &[])?;
let mut cmd = std::process::Command::new("python");
// env.env_vars() gives us the magic environment variables needed to run a command
// in our new environment.
cmd.envs(env.env_vars()?);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
Err(cmd.exec())?;
unreachable!();
}
#[cfg(windows)]
{
// XX FIXME: factor out the windows trampoline code and reuse it here.
//
// unwrap() is safe b/c this branch only runs on windows, and Windows doesn't
// have special exit statuses; that's a special thing for Unix signals.
std::process::exit(cmd.status()?.code().unwrap());
}
#[cfg(not(any(unix, windows)))]
{
not_supported
}
}