mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss] phase 09: Track D.1 + D.2 — Project dependency capture + workdir staging
This commit is contained in:
parent
a7fbc37c21
commit
2f01894353
16 changed files with 2009 additions and 0 deletions
|
|
@ -13,9 +13,11 @@
|
|||
//! - `PayloadSlot::EnvVar(name)` — set env var before calling.
|
||||
//! - Other slots produce `UnsupportedReason::PayloadSlotUnsupported`.
|
||||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use crate::utils::project::DetectedFramework;
|
||||
|
||||
/// Zero-sized [`LangEmitter`] handle for Python. Registered in the
|
||||
/// `lang::dispatch` table; method bodies delegate to the existing free
|
||||
|
|
@ -40,6 +42,14 @@ impl LangEmitter for PythonEmitter {
|
|||
"python emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — Track B will add framework + CLI shapes in phase 12"
|
||||
)
|
||||
}
|
||||
|
||||
/// Phase 09 — Track D.2: emit a pinned `requirements.txt` (and a
|
||||
/// matching `pyproject.toml` stub when `pyproject.toml` is the
|
||||
/// project's canonical manifest) covering every captured direct dep
|
||||
/// plus the framework deps inferred from the project manifest.
|
||||
fn materialize_runtime(&self, env: &Environment) -> RuntimeArtifacts {
|
||||
materialize_python(env)
|
||||
}
|
||||
}
|
||||
|
||||
/// Source of the `__nyx_probe` shim for the Python harness.
|
||||
|
|
@ -168,6 +178,163 @@ def __nyx_install_crash_guard(sink_callee):
|
|||
"#
|
||||
}
|
||||
|
||||
/// Phase 09 - Track D.2: synthesise a `requirements.txt` from the
|
||||
/// captured deps in `env`.
|
||||
///
|
||||
/// The output is a deterministic, alphabetised listing of every
|
||||
/// non-stdlib direct dep the entry file imported plus the framework deps
|
||||
/// inferred from the manifest detector. Each entry is emitted as the
|
||||
/// canonical pip-installable name; version pins are intentionally
|
||||
/// omitted so the system pip resolves the latest compatible release
|
||||
/// against the user's pinned Python interpreter (the spec's
|
||||
/// `toolchain_id` field). A future phase can fold pinned versions in
|
||||
/// once the capture pass learns to parse the project's own lockfile.
|
||||
pub fn materialize_python(env: &Environment) -> RuntimeArtifacts {
|
||||
let mut artifacts = RuntimeArtifacts::new();
|
||||
let mut deps: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
|
||||
// Direct imports first — these mirror the entry file faithfully.
|
||||
for d in &env.direct_deps {
|
||||
if is_python_stdlib(d) {
|
||||
continue;
|
||||
}
|
||||
let canonical = canonical_python_pkg_name(d);
|
||||
if seen.insert(canonical.clone()) {
|
||||
deps.push(canonical);
|
||||
}
|
||||
}
|
||||
// Framework deps next — these may not appear as direct imports in
|
||||
// every entry file, but they have to be installed for the runtime
|
||||
// to resolve framework decorators.
|
||||
for fw in &env.frameworks {
|
||||
if let Some(name) = python_framework_pkg_name(*fw) {
|
||||
let canonical = canonical_python_pkg_name(name);
|
||||
if seen.insert(canonical.clone()) {
|
||||
deps.push(canonical);
|
||||
}
|
||||
}
|
||||
}
|
||||
deps.sort_unstable();
|
||||
|
||||
let mut body = String::with_capacity(64);
|
||||
body.push_str("# Auto-generated by Nyx — Phase 09 (Track D.2).\n");
|
||||
body.push_str(&format!("# spec_hash = {}\n", env.spec_hash));
|
||||
body.push_str(&format!(
|
||||
"# toolchain = {} (drift={})\n",
|
||||
env.toolchain.toolchain_id, env.toolchain.toolchain_drift
|
||||
));
|
||||
for d in &deps {
|
||||
body.push_str(d);
|
||||
body.push('\n');
|
||||
}
|
||||
artifacts.push("requirements.txt", body);
|
||||
artifacts
|
||||
}
|
||||
|
||||
/// Returns true when `name` is a Python standard-library top-level
|
||||
/// package. Conservative: matches the names the harness build path
|
||||
/// would silently drop from `requirements.txt` anyway.
|
||||
fn is_python_stdlib(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"abc"
|
||||
| "argparse"
|
||||
| "asyncio"
|
||||
| "base64"
|
||||
| "binascii"
|
||||
| "collections"
|
||||
| "contextlib"
|
||||
| "copy"
|
||||
| "csv"
|
||||
| "ctypes"
|
||||
| "dataclasses"
|
||||
| "datetime"
|
||||
| "decimal"
|
||||
| "difflib"
|
||||
| "email"
|
||||
| "enum"
|
||||
| "errno"
|
||||
| "fcntl"
|
||||
| "fnmatch"
|
||||
| "functools"
|
||||
| "getopt"
|
||||
| "getpass"
|
||||
| "glob"
|
||||
| "gzip"
|
||||
| "hashlib"
|
||||
| "hmac"
|
||||
| "http"
|
||||
| "importlib"
|
||||
| "inspect"
|
||||
| "io"
|
||||
| "ipaddress"
|
||||
| "itertools"
|
||||
| "json"
|
||||
| "logging"
|
||||
| "math"
|
||||
| "multiprocessing"
|
||||
| "operator"
|
||||
| "os"
|
||||
| "pathlib"
|
||||
| "pickle"
|
||||
| "platform"
|
||||
| "posixpath"
|
||||
| "queue"
|
||||
| "random"
|
||||
| "re"
|
||||
| "secrets"
|
||||
| "select"
|
||||
| "shutil"
|
||||
| "signal"
|
||||
| "socket"
|
||||
| "sqlite3"
|
||||
| "ssl"
|
||||
| "stat"
|
||||
| "string"
|
||||
| "struct"
|
||||
| "subprocess"
|
||||
| "sys"
|
||||
| "tempfile"
|
||||
| "threading"
|
||||
| "time"
|
||||
| "traceback"
|
||||
| "types"
|
||||
| "typing"
|
||||
| "unicodedata"
|
||||
| "unittest"
|
||||
| "urllib"
|
||||
| "uuid"
|
||||
| "warnings"
|
||||
| "weakref"
|
||||
| "xml"
|
||||
| "zipfile"
|
||||
| "zlib"
|
||||
)
|
||||
}
|
||||
|
||||
/// Canonicalise common Python pkg aliases to their PyPI distribution
|
||||
/// name (e.g. `cv2` → `opencv-python`).
|
||||
fn canonical_python_pkg_name(name: &str) -> String {
|
||||
let lower = name.to_ascii_lowercase();
|
||||
match lower.as_str() {
|
||||
"flask" => "Flask".to_owned(),
|
||||
"cv2" => "opencv-python".to_owned(),
|
||||
"sqlalchemy" => "SQLAlchemy".to_owned(),
|
||||
"yaml" => "PyYAML".to_owned(),
|
||||
"psycopg2" => "psycopg2-binary".to_owned(),
|
||||
_ => lower,
|
||||
}
|
||||
}
|
||||
|
||||
fn python_framework_pkg_name(fw: DetectedFramework) -> Option<&'static str> {
|
||||
match fw {
|
||||
DetectedFramework::Flask => Some("flask"),
|
||||
DetectedFramework::Django => Some("django"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a Python harness for `spec`.
|
||||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
// Validate payload slot.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue