@osfactory/har 0.61.3 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +30 -19
- package/dist/templates/har-boilerplate/provision-toolchain.sh +258 -19
- package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +258 -19
- package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +258 -19
- package/dist/templates/runtime-bundles/shared-kernel/provision-toolchain.sh +258 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
mod
|
|
34
34
|
));
|
|
35
35
|
|
|
36
|
+
// src/core/package-version.ts
|
|
37
|
+
function getHarPackageVersion() {
|
|
38
|
+
if (process.env.HAR_PACKAGE_VERSION) {
|
|
39
|
+
return process.env.HAR_PACKAGE_VERSION;
|
|
40
|
+
}
|
|
41
|
+
const candidates = [
|
|
42
|
+
path.resolve(__dirname, "..", "package.json"),
|
|
43
|
+
path.resolve(__dirname, "..", "..", "package.json")
|
|
44
|
+
];
|
|
45
|
+
for (const packageJsonPath of candidates) {
|
|
46
|
+
if (!fs.existsSync(packageJsonPath)) continue;
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
48
|
+
return pkg.version;
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
51
|
+
}
|
|
52
|
+
var fs, path;
|
|
53
|
+
var init_package_version = __esm({
|
|
54
|
+
"src/core/package-version.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
fs = __toESM(require("fs"));
|
|
57
|
+
path = __toESM(require("path"));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
36
61
|
// src/core/telemetry-config.ts
|
|
37
62
|
function getPreferencePath() {
|
|
38
63
|
if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
|
|
@@ -32881,6 +32906,7 @@ async function buildPortalPayload(repoPath, controlApiUrl, since, full = false)
|
|
|
32881
32906
|
}
|
|
32882
32907
|
const syncBody = {
|
|
32883
32908
|
path: repoPath,
|
|
32909
|
+
cliVersion: getHarPackageVersion(),
|
|
32884
32910
|
...status.gitRemote ? { gitRemote: status.gitRemote } : {},
|
|
32885
32911
|
...manifest ? { manifest } : {},
|
|
32886
32912
|
...stagesRegistry ? { stagesRegistry } : {},
|
|
@@ -33303,6 +33329,7 @@ var init_control_sync = __esm({
|
|
|
33303
33329
|
init_work_units();
|
|
33304
33330
|
init_cloud_executor();
|
|
33305
33331
|
init_telemetry_config();
|
|
33332
|
+
init_package_version();
|
|
33306
33333
|
init_logging();
|
|
33307
33334
|
init_usage_harvest();
|
|
33308
33335
|
init_telemetry_env();
|
|
@@ -66492,26 +66519,8 @@ function isYargsInstance(y) {
|
|
|
66492
66519
|
var Yargs = YargsFactory(esm_default);
|
|
66493
66520
|
var yargs_default = Yargs;
|
|
66494
66521
|
|
|
66495
|
-
// src/core/package-version.ts
|
|
66496
|
-
var fs = __toESM(require("fs"));
|
|
66497
|
-
var path = __toESM(require("path"));
|
|
66498
|
-
function getHarPackageVersion() {
|
|
66499
|
-
if (process.env.HAR_PACKAGE_VERSION) {
|
|
66500
|
-
return process.env.HAR_PACKAGE_VERSION;
|
|
66501
|
-
}
|
|
66502
|
-
const candidates = [
|
|
66503
|
-
path.resolve(__dirname, "..", "package.json"),
|
|
66504
|
-
path.resolve(__dirname, "..", "..", "package.json")
|
|
66505
|
-
];
|
|
66506
|
-
for (const packageJsonPath of candidates) {
|
|
66507
|
-
if (!fs.existsSync(packageJsonPath)) continue;
|
|
66508
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
66509
|
-
return pkg.version;
|
|
66510
|
-
}
|
|
66511
|
-
throw new Error("Could not resolve @osfactory/har package.json for version lookup");
|
|
66512
|
-
}
|
|
66513
|
-
|
|
66514
66522
|
// src/cli/index.ts
|
|
66523
|
+
init_package_version();
|
|
66515
66524
|
init_telemetry_config();
|
|
66516
66525
|
init_sync_context();
|
|
66517
66526
|
|
|
@@ -72206,6 +72215,7 @@ var import_child_process13 = require("child_process");
|
|
|
72206
72215
|
var path52 = __toESM(require("path"));
|
|
72207
72216
|
|
|
72208
72217
|
// src/core/control-image.ts
|
|
72218
|
+
init_package_version();
|
|
72209
72219
|
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
72210
72220
|
function getControlImageName() {
|
|
72211
72221
|
return process.env.HAR_CONTROL_IMAGE ?? DEFAULT_CONTROL_IMAGE;
|
|
@@ -82504,6 +82514,7 @@ var StdioServerTransport = class {
|
|
|
82504
82514
|
};
|
|
82505
82515
|
|
|
82506
82516
|
// src/mcp/server.ts
|
|
82517
|
+
init_package_version();
|
|
82507
82518
|
init_telemetry_config();
|
|
82508
82519
|
init_runs();
|
|
82509
82520
|
init_work_units();
|
|
@@ -228,52 +228,291 @@ provision_node() {
|
|
|
228
228
|
append_path_prefix "$dir/node_modules/.bin"
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
# ── Python interpreter helpers ────────────────────────────────────────────────
|
|
232
|
+
# Resolve the project Python (requires-python, .python-version, uv) instead of
|
|
233
|
+
# blindly using PATH python3.
|
|
234
|
+
|
|
235
|
+
har_python_version_from_bin() {
|
|
236
|
+
local bin="$1"
|
|
237
|
+
"$bin" -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null \
|
|
238
|
+
| tr -d '[:space:]'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
har_python_version_to_sortable() {
|
|
242
|
+
local v="${1#python}"
|
|
243
|
+
v="${v#v}"
|
|
244
|
+
local major=0 minor=0 patch=0
|
|
245
|
+
IFS='.' read -r major minor patch _ <<< "$v"
|
|
246
|
+
major=${major:-0}
|
|
247
|
+
minor=${minor:-0}
|
|
248
|
+
patch=${patch:-0}
|
|
249
|
+
printf '%03d%03d%03d' "$major" "$minor" "$patch"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
har_python_version_ge() {
|
|
253
|
+
local a b
|
|
254
|
+
a="$(har_python_version_to_sortable "$1")"
|
|
255
|
+
b="$(har_python_version_to_sortable "$2")"
|
|
256
|
+
[ "$a" -ge "$b" ]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
har_python_requires_minimum() {
|
|
260
|
+
local spec="$1"
|
|
261
|
+
local ver=""
|
|
262
|
+
|
|
263
|
+
[ -n "$spec" ] || return 1
|
|
264
|
+
|
|
265
|
+
ver="$(printf '%s' "$spec" | sed -n \
|
|
266
|
+
-e 's/.*>=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
267
|
+
-e 's/.*~=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
268
|
+
-e 's/.*==\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
269
|
+
| head -1)"
|
|
270
|
+
|
|
271
|
+
if [ -z "$ver" ]; then
|
|
272
|
+
ver="$(printf '%s' "$spec" | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
[ -n "$ver" ] && echo "$ver"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
har_python_read_dot_version() {
|
|
279
|
+
local dir="$1"
|
|
280
|
+
[ -f "$dir/.python-version" ] || return 1
|
|
281
|
+
head -1 "$dir/.python-version" | tr -d '[:space:]'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
har_python_read_requires_spec() {
|
|
285
|
+
local dir="$1"
|
|
286
|
+
local spec=""
|
|
287
|
+
|
|
288
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
289
|
+
spec="$(sed -n 's/^[[:space:]]*requires-python[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
290
|
+
"$dir/pyproject.toml" | head -1)"
|
|
291
|
+
fi
|
|
292
|
+
if [ -z "$spec" ] && [ -f "$dir/setup.cfg" ]; then
|
|
293
|
+
spec="$(sed -n '/^\[options\]/,/^\[/ s/^python_requires[[:space:]]*=[[:space:]]*\(.*\)/\1/p' \
|
|
294
|
+
"$dir/setup.cfg" | head -1 | tr -d ' "')"
|
|
295
|
+
fi
|
|
296
|
+
[ -n "$spec" ] && echo "$spec"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
har_python_project_minimum() {
|
|
300
|
+
local dir="$1"
|
|
301
|
+
local req_min="" dot_ver="" higher=""
|
|
302
|
+
|
|
303
|
+
req_min="$(har_python_requires_minimum "$(har_python_read_requires_spec "$dir" || true)" || true)"
|
|
304
|
+
dot_ver="$(har_python_read_dot_version "$dir" || true)"
|
|
305
|
+
if [ -n "$dot_ver" ]; then
|
|
306
|
+
dot_ver="$(printf '%s' "$dot_ver" | sed 's/^v//; s/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if [ -n "$req_min" ] && [ -n "$dot_ver" ]; then
|
|
310
|
+
if har_python_version_ge "$dot_ver" "$req_min"; then
|
|
311
|
+
higher="$dot_ver"
|
|
312
|
+
else
|
|
313
|
+
higher="$req_min"
|
|
314
|
+
fi
|
|
315
|
+
elif [ -n "$req_min" ]; then
|
|
316
|
+
higher="$req_min"
|
|
317
|
+
elif [ -n "$dot_ver" ]; then
|
|
318
|
+
higher="$dot_ver"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
[ -n "$higher" ] && echo "$higher"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
har_python_is_uv_project() {
|
|
325
|
+
local dir="$1"
|
|
326
|
+
[ -f "$dir/uv.lock" ] && return 0
|
|
327
|
+
[ -f "$dir/pyproject.toml" ] && grep -q '^\[tool\.uv\]' "$dir/pyproject.toml" 2>/dev/null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
har_python_uv_find() {
|
|
331
|
+
local dir="$1"
|
|
332
|
+
local request="${2:-}"
|
|
333
|
+
|
|
334
|
+
command -v uv >/dev/null 2>&1 || return 1
|
|
335
|
+
if [ -n "$request" ]; then
|
|
336
|
+
(cd "$dir" && uv python find "$request" 2>/dev/null)
|
|
337
|
+
else
|
|
338
|
+
(cd "$dir" && uv python find 2>/dev/null)
|
|
339
|
+
fi
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
har_python_resolve_interpreter() {
|
|
343
|
+
local dir="$1"
|
|
344
|
+
local minimum="${2:-}"
|
|
345
|
+
local found=""
|
|
346
|
+
|
|
347
|
+
if har_python_is_uv_project "$dir"; then
|
|
348
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
349
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
350
|
+
echo "$found"
|
|
351
|
+
return 0
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if [ -f "$dir/.python-version" ]; then
|
|
356
|
+
found="$(har_python_uv_find "$dir" "$(har_python_read_dot_version "$dir")" || true)"
|
|
357
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
358
|
+
echo "$found"
|
|
359
|
+
return 0
|
|
360
|
+
fi
|
|
361
|
+
if command -v pyenv >/dev/null 2>&1; then
|
|
362
|
+
found="$(cd "$dir" && pyenv which python 2>/dev/null || true)"
|
|
363
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
364
|
+
echo "$found"
|
|
365
|
+
return 0
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [ -n "$minimum" ]; then
|
|
371
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
372
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
373
|
+
echo "$found"
|
|
374
|
+
return 0
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
379
|
+
local sys_py sys_ver=""
|
|
380
|
+
sys_py="$(command -v python3)"
|
|
381
|
+
if [ -z "$minimum" ]; then
|
|
382
|
+
echo "$sys_py"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
sys_ver="$(har_python_version_from_bin "$sys_py")"
|
|
386
|
+
if [ -n "$sys_ver" ] && har_python_version_ge "$sys_ver" "$minimum"; then
|
|
387
|
+
echo "$sys_py"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
echo "$sys_py"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
return 1
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
har_python_warn_if_below_minimum() {
|
|
398
|
+
local bin="$1"
|
|
399
|
+
local minimum="$2"
|
|
400
|
+
local actual=""
|
|
401
|
+
|
|
402
|
+
[ -n "$minimum" ] || return 0
|
|
403
|
+
actual="$(har_python_version_from_bin "$bin")"
|
|
404
|
+
[ -n "$actual" ] || return 0
|
|
405
|
+
if ! har_python_version_ge "$actual" "$minimum"; then
|
|
406
|
+
pt_log "WARNING: resolved Python ${actual} is older than required ${minimum} (requires-python / .python-version)"
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
har_python_create_venv() {
|
|
411
|
+
local dir="$1"
|
|
412
|
+
local venv_dir="$2"
|
|
413
|
+
local interpreter="$3"
|
|
414
|
+
local venv_rel="${venv_dir#"$dir"/}"
|
|
415
|
+
|
|
416
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
417
|
+
pt_log "Creating Python venv with uv at ${venv_rel}..."
|
|
418
|
+
if (cd "$dir" && uv venv --python "$interpreter" "$venv_dir" 2>/dev/null) \
|
|
419
|
+
|| (cd "$dir" && uv venv "$venv_dir" 2>/dev/null); then
|
|
420
|
+
return 0
|
|
421
|
+
fi
|
|
422
|
+
pt_log "uv venv failed — falling back to ${interpreter} -m venv"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
pt_log "Creating Python venv at ${venv_rel}..."
|
|
426
|
+
"$interpreter" -m venv "$venv_dir"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
har_python_install_deps() {
|
|
430
|
+
local dir="$1"
|
|
431
|
+
local venv_dir="$2"
|
|
432
|
+
|
|
433
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
434
|
+
pt_log "Syncing Python dependencies with uv..."
|
|
435
|
+
if (cd "$dir" && UV_PROJECT_ENVIRONMENT="$venv_dir" uv sync --quiet 2>/dev/null); then
|
|
436
|
+
return 0
|
|
437
|
+
fi
|
|
438
|
+
if (cd "$dir" && uv sync --quiet 2>/dev/null); then
|
|
439
|
+
return 0
|
|
440
|
+
fi
|
|
441
|
+
pt_log "uv sync failed — falling back to pip"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
231
447
|
provision_python() {
|
|
232
448
|
local dir="$1"
|
|
233
449
|
local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
|
|
234
450
|
local venv_dir="$dir/$venv_rel"
|
|
235
|
-
local python_bin="
|
|
451
|
+
local python_bin=""
|
|
452
|
+
local interpreter=""
|
|
453
|
+
local project_min=""
|
|
454
|
+
|
|
455
|
+
project_min="$(har_python_project_minimum "$dir" || true)"
|
|
456
|
+
interpreter="$(har_python_resolve_interpreter "$dir" "$project_min" || true)"
|
|
236
457
|
|
|
237
|
-
if ! command -v
|
|
238
|
-
pt_log "
|
|
458
|
+
if [ -z "$interpreter" ] || ! command -v "$interpreter" >/dev/null 2>&1; then
|
|
459
|
+
pt_log "No suitable Python interpreter found — skipping venv provisioning"
|
|
239
460
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
240
461
|
append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
|
|
241
462
|
return
|
|
242
463
|
fi
|
|
243
464
|
|
|
465
|
+
if [ -n "$project_min" ]; then
|
|
466
|
+
har_python_warn_if_below_minimum "$interpreter" "$project_min"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if [ -d "$venv_dir" ] && [ -x "$venv_dir/bin/python" ] && [ -n "$project_min" ]; then
|
|
470
|
+
local venv_ver=""
|
|
471
|
+
venv_ver="$(har_python_version_from_bin "$venv_dir/bin/python")"
|
|
472
|
+
if [ -n "$venv_ver" ] && ! har_python_version_ge "$venv_ver" "$project_min"; then
|
|
473
|
+
pt_log "Existing venv Python ${venv_ver} is older than required ${project_min} — recreating..."
|
|
474
|
+
rm -rf "$venv_dir"
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
|
|
244
478
|
if [ ! -d "$venv_dir" ]; then
|
|
245
|
-
|
|
246
|
-
if ! python3 -m venv "$venv_dir"; then
|
|
479
|
+
if ! har_python_create_venv "$dir" "$venv_dir" "$interpreter"; then
|
|
247
480
|
rm -rf "$venv_dir"
|
|
248
|
-
pt_log "
|
|
481
|
+
pt_log "venv creation failed (on Debian/Ubuntu install python3-venv) — using resolved interpreter"
|
|
249
482
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
250
|
-
append_env "PYTHON_BIN" "$
|
|
483
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
251
484
|
return
|
|
252
485
|
fi
|
|
253
486
|
fi
|
|
254
487
|
|
|
255
488
|
if [ ! -x "$venv_dir/bin/python" ]; then
|
|
256
|
-
pt_log "Python venv at $venv_rel is missing or broken — using
|
|
489
|
+
pt_log "Python venv at $venv_rel is missing or broken — using resolved interpreter"
|
|
257
490
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
258
|
-
append_env "PYTHON_BIN" "$
|
|
491
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
259
492
|
return
|
|
260
493
|
fi
|
|
261
494
|
|
|
262
495
|
python_bin="$venv_dir/bin/python"
|
|
496
|
+
if [ -n "$project_min" ]; then
|
|
497
|
+
har_python_warn_if_below_minimum "$python_bin" "$project_min"
|
|
498
|
+
fi
|
|
499
|
+
|
|
263
500
|
# shellcheck disable=SC1091
|
|
264
501
|
source "$venv_dir/bin/activate"
|
|
265
502
|
|
|
266
503
|
if ! run_install_cmd "$dir"; then
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
504
|
+
if ! har_python_install_deps "$dir" "$venv_dir"; then
|
|
505
|
+
pt_log "Installing Python dependencies..."
|
|
506
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
507
|
+
(cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
|
|
508
|
+
elif [ -f "$dir/requirements.txt" ]; then
|
|
509
|
+
(cd "$dir" && pip install -q -r requirements.txt)
|
|
510
|
+
elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
|
|
511
|
+
(cd "$dir" && pip install -q -e .)
|
|
512
|
+
elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
|
|
513
|
+
(cd "$dir" && pipenv install --dev)
|
|
514
|
+
python_bin="$(cd "$dir" && pipenv --py)"
|
|
515
|
+
fi
|
|
277
516
|
fi
|
|
278
517
|
fi
|
|
279
518
|
|
|
@@ -228,52 +228,291 @@ provision_node() {
|
|
|
228
228
|
append_path_prefix "$dir/node_modules/.bin"
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
# ── Python interpreter helpers ────────────────────────────────────────────────
|
|
232
|
+
# Resolve the project Python (requires-python, .python-version, uv) instead of
|
|
233
|
+
# blindly using PATH python3.
|
|
234
|
+
|
|
235
|
+
har_python_version_from_bin() {
|
|
236
|
+
local bin="$1"
|
|
237
|
+
"$bin" -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null \
|
|
238
|
+
| tr -d '[:space:]'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
har_python_version_to_sortable() {
|
|
242
|
+
local v="${1#python}"
|
|
243
|
+
v="${v#v}"
|
|
244
|
+
local major=0 minor=0 patch=0
|
|
245
|
+
IFS='.' read -r major minor patch _ <<< "$v"
|
|
246
|
+
major=${major:-0}
|
|
247
|
+
minor=${minor:-0}
|
|
248
|
+
patch=${patch:-0}
|
|
249
|
+
printf '%03d%03d%03d' "$major" "$minor" "$patch"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
har_python_version_ge() {
|
|
253
|
+
local a b
|
|
254
|
+
a="$(har_python_version_to_sortable "$1")"
|
|
255
|
+
b="$(har_python_version_to_sortable "$2")"
|
|
256
|
+
[ "$a" -ge "$b" ]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
har_python_requires_minimum() {
|
|
260
|
+
local spec="$1"
|
|
261
|
+
local ver=""
|
|
262
|
+
|
|
263
|
+
[ -n "$spec" ] || return 1
|
|
264
|
+
|
|
265
|
+
ver="$(printf '%s' "$spec" | sed -n \
|
|
266
|
+
-e 's/.*>=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
267
|
+
-e 's/.*~=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
268
|
+
-e 's/.*==\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
269
|
+
| head -1)"
|
|
270
|
+
|
|
271
|
+
if [ -z "$ver" ]; then
|
|
272
|
+
ver="$(printf '%s' "$spec" | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
[ -n "$ver" ] && echo "$ver"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
har_python_read_dot_version() {
|
|
279
|
+
local dir="$1"
|
|
280
|
+
[ -f "$dir/.python-version" ] || return 1
|
|
281
|
+
head -1 "$dir/.python-version" | tr -d '[:space:]'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
har_python_read_requires_spec() {
|
|
285
|
+
local dir="$1"
|
|
286
|
+
local spec=""
|
|
287
|
+
|
|
288
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
289
|
+
spec="$(sed -n 's/^[[:space:]]*requires-python[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
290
|
+
"$dir/pyproject.toml" | head -1)"
|
|
291
|
+
fi
|
|
292
|
+
if [ -z "$spec" ] && [ -f "$dir/setup.cfg" ]; then
|
|
293
|
+
spec="$(sed -n '/^\[options\]/,/^\[/ s/^python_requires[[:space:]]*=[[:space:]]*\(.*\)/\1/p' \
|
|
294
|
+
"$dir/setup.cfg" | head -1 | tr -d ' "')"
|
|
295
|
+
fi
|
|
296
|
+
[ -n "$spec" ] && echo "$spec"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
har_python_project_minimum() {
|
|
300
|
+
local dir="$1"
|
|
301
|
+
local req_min="" dot_ver="" higher=""
|
|
302
|
+
|
|
303
|
+
req_min="$(har_python_requires_minimum "$(har_python_read_requires_spec "$dir" || true)" || true)"
|
|
304
|
+
dot_ver="$(har_python_read_dot_version "$dir" || true)"
|
|
305
|
+
if [ -n "$dot_ver" ]; then
|
|
306
|
+
dot_ver="$(printf '%s' "$dot_ver" | sed 's/^v//; s/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if [ -n "$req_min" ] && [ -n "$dot_ver" ]; then
|
|
310
|
+
if har_python_version_ge "$dot_ver" "$req_min"; then
|
|
311
|
+
higher="$dot_ver"
|
|
312
|
+
else
|
|
313
|
+
higher="$req_min"
|
|
314
|
+
fi
|
|
315
|
+
elif [ -n "$req_min" ]; then
|
|
316
|
+
higher="$req_min"
|
|
317
|
+
elif [ -n "$dot_ver" ]; then
|
|
318
|
+
higher="$dot_ver"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
[ -n "$higher" ] && echo "$higher"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
har_python_is_uv_project() {
|
|
325
|
+
local dir="$1"
|
|
326
|
+
[ -f "$dir/uv.lock" ] && return 0
|
|
327
|
+
[ -f "$dir/pyproject.toml" ] && grep -q '^\[tool\.uv\]' "$dir/pyproject.toml" 2>/dev/null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
har_python_uv_find() {
|
|
331
|
+
local dir="$1"
|
|
332
|
+
local request="${2:-}"
|
|
333
|
+
|
|
334
|
+
command -v uv >/dev/null 2>&1 || return 1
|
|
335
|
+
if [ -n "$request" ]; then
|
|
336
|
+
(cd "$dir" && uv python find "$request" 2>/dev/null)
|
|
337
|
+
else
|
|
338
|
+
(cd "$dir" && uv python find 2>/dev/null)
|
|
339
|
+
fi
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
har_python_resolve_interpreter() {
|
|
343
|
+
local dir="$1"
|
|
344
|
+
local minimum="${2:-}"
|
|
345
|
+
local found=""
|
|
346
|
+
|
|
347
|
+
if har_python_is_uv_project "$dir"; then
|
|
348
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
349
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
350
|
+
echo "$found"
|
|
351
|
+
return 0
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if [ -f "$dir/.python-version" ]; then
|
|
356
|
+
found="$(har_python_uv_find "$dir" "$(har_python_read_dot_version "$dir")" || true)"
|
|
357
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
358
|
+
echo "$found"
|
|
359
|
+
return 0
|
|
360
|
+
fi
|
|
361
|
+
if command -v pyenv >/dev/null 2>&1; then
|
|
362
|
+
found="$(cd "$dir" && pyenv which python 2>/dev/null || true)"
|
|
363
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
364
|
+
echo "$found"
|
|
365
|
+
return 0
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [ -n "$minimum" ]; then
|
|
371
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
372
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
373
|
+
echo "$found"
|
|
374
|
+
return 0
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
379
|
+
local sys_py sys_ver=""
|
|
380
|
+
sys_py="$(command -v python3)"
|
|
381
|
+
if [ -z "$minimum" ]; then
|
|
382
|
+
echo "$sys_py"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
sys_ver="$(har_python_version_from_bin "$sys_py")"
|
|
386
|
+
if [ -n "$sys_ver" ] && har_python_version_ge "$sys_ver" "$minimum"; then
|
|
387
|
+
echo "$sys_py"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
echo "$sys_py"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
return 1
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
har_python_warn_if_below_minimum() {
|
|
398
|
+
local bin="$1"
|
|
399
|
+
local minimum="$2"
|
|
400
|
+
local actual=""
|
|
401
|
+
|
|
402
|
+
[ -n "$minimum" ] || return 0
|
|
403
|
+
actual="$(har_python_version_from_bin "$bin")"
|
|
404
|
+
[ -n "$actual" ] || return 0
|
|
405
|
+
if ! har_python_version_ge "$actual" "$minimum"; then
|
|
406
|
+
pt_log "WARNING: resolved Python ${actual} is older than required ${minimum} (requires-python / .python-version)"
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
har_python_create_venv() {
|
|
411
|
+
local dir="$1"
|
|
412
|
+
local venv_dir="$2"
|
|
413
|
+
local interpreter="$3"
|
|
414
|
+
local venv_rel="${venv_dir#"$dir"/}"
|
|
415
|
+
|
|
416
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
417
|
+
pt_log "Creating Python venv with uv at ${venv_rel}..."
|
|
418
|
+
if (cd "$dir" && uv venv --python "$interpreter" "$venv_dir" 2>/dev/null) \
|
|
419
|
+
|| (cd "$dir" && uv venv "$venv_dir" 2>/dev/null); then
|
|
420
|
+
return 0
|
|
421
|
+
fi
|
|
422
|
+
pt_log "uv venv failed — falling back to ${interpreter} -m venv"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
pt_log "Creating Python venv at ${venv_rel}..."
|
|
426
|
+
"$interpreter" -m venv "$venv_dir"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
har_python_install_deps() {
|
|
430
|
+
local dir="$1"
|
|
431
|
+
local venv_dir="$2"
|
|
432
|
+
|
|
433
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
434
|
+
pt_log "Syncing Python dependencies with uv..."
|
|
435
|
+
if (cd "$dir" && UV_PROJECT_ENVIRONMENT="$venv_dir" uv sync --quiet 2>/dev/null); then
|
|
436
|
+
return 0
|
|
437
|
+
fi
|
|
438
|
+
if (cd "$dir" && uv sync --quiet 2>/dev/null); then
|
|
439
|
+
return 0
|
|
440
|
+
fi
|
|
441
|
+
pt_log "uv sync failed — falling back to pip"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
231
447
|
provision_python() {
|
|
232
448
|
local dir="$1"
|
|
233
449
|
local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
|
|
234
450
|
local venv_dir="$dir/$venv_rel"
|
|
235
|
-
local python_bin="
|
|
451
|
+
local python_bin=""
|
|
452
|
+
local interpreter=""
|
|
453
|
+
local project_min=""
|
|
454
|
+
|
|
455
|
+
project_min="$(har_python_project_minimum "$dir" || true)"
|
|
456
|
+
interpreter="$(har_python_resolve_interpreter "$dir" "$project_min" || true)"
|
|
236
457
|
|
|
237
|
-
if ! command -v
|
|
238
|
-
pt_log "
|
|
458
|
+
if [ -z "$interpreter" ] || ! command -v "$interpreter" >/dev/null 2>&1; then
|
|
459
|
+
pt_log "No suitable Python interpreter found — skipping venv provisioning"
|
|
239
460
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
240
461
|
append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
|
|
241
462
|
return
|
|
242
463
|
fi
|
|
243
464
|
|
|
465
|
+
if [ -n "$project_min" ]; then
|
|
466
|
+
har_python_warn_if_below_minimum "$interpreter" "$project_min"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if [ -d "$venv_dir" ] && [ -x "$venv_dir/bin/python" ] && [ -n "$project_min" ]; then
|
|
470
|
+
local venv_ver=""
|
|
471
|
+
venv_ver="$(har_python_version_from_bin "$venv_dir/bin/python")"
|
|
472
|
+
if [ -n "$venv_ver" ] && ! har_python_version_ge "$venv_ver" "$project_min"; then
|
|
473
|
+
pt_log "Existing venv Python ${venv_ver} is older than required ${project_min} — recreating..."
|
|
474
|
+
rm -rf "$venv_dir"
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
|
|
244
478
|
if [ ! -d "$venv_dir" ]; then
|
|
245
|
-
|
|
246
|
-
if ! python3 -m venv "$venv_dir"; then
|
|
479
|
+
if ! har_python_create_venv "$dir" "$venv_dir" "$interpreter"; then
|
|
247
480
|
rm -rf "$venv_dir"
|
|
248
|
-
pt_log "
|
|
481
|
+
pt_log "venv creation failed (on Debian/Ubuntu install python3-venv) — using resolved interpreter"
|
|
249
482
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
250
|
-
append_env "PYTHON_BIN" "$
|
|
483
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
251
484
|
return
|
|
252
485
|
fi
|
|
253
486
|
fi
|
|
254
487
|
|
|
255
488
|
if [ ! -x "$venv_dir/bin/python" ]; then
|
|
256
|
-
pt_log "Python venv at $venv_rel is missing or broken — using
|
|
489
|
+
pt_log "Python venv at $venv_rel is missing or broken — using resolved interpreter"
|
|
257
490
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
258
|
-
append_env "PYTHON_BIN" "$
|
|
491
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
259
492
|
return
|
|
260
493
|
fi
|
|
261
494
|
|
|
262
495
|
python_bin="$venv_dir/bin/python"
|
|
496
|
+
if [ -n "$project_min" ]; then
|
|
497
|
+
har_python_warn_if_below_minimum "$python_bin" "$project_min"
|
|
498
|
+
fi
|
|
499
|
+
|
|
263
500
|
# shellcheck disable=SC1091
|
|
264
501
|
source "$venv_dir/bin/activate"
|
|
265
502
|
|
|
266
503
|
if ! run_install_cmd "$dir"; then
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
504
|
+
if ! har_python_install_deps "$dir" "$venv_dir"; then
|
|
505
|
+
pt_log "Installing Python dependencies..."
|
|
506
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
507
|
+
(cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
|
|
508
|
+
elif [ -f "$dir/requirements.txt" ]; then
|
|
509
|
+
(cd "$dir" && pip install -q -r requirements.txt)
|
|
510
|
+
elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
|
|
511
|
+
(cd "$dir" && pip install -q -e .)
|
|
512
|
+
elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
|
|
513
|
+
(cd "$dir" && pipenv install --dev)
|
|
514
|
+
python_bin="$(cd "$dir" && pipenv --py)"
|
|
515
|
+
fi
|
|
277
516
|
fi
|
|
278
517
|
fi
|
|
279
518
|
|
|
@@ -228,52 +228,291 @@ provision_node() {
|
|
|
228
228
|
append_path_prefix "$dir/node_modules/.bin"
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
# ── Python interpreter helpers ────────────────────────────────────────────────
|
|
232
|
+
# Resolve the project Python (requires-python, .python-version, uv) instead of
|
|
233
|
+
# blindly using PATH python3.
|
|
234
|
+
|
|
235
|
+
har_python_version_from_bin() {
|
|
236
|
+
local bin="$1"
|
|
237
|
+
"$bin" -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null \
|
|
238
|
+
| tr -d '[:space:]'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
har_python_version_to_sortable() {
|
|
242
|
+
local v="${1#python}"
|
|
243
|
+
v="${v#v}"
|
|
244
|
+
local major=0 minor=0 patch=0
|
|
245
|
+
IFS='.' read -r major minor patch _ <<< "$v"
|
|
246
|
+
major=${major:-0}
|
|
247
|
+
minor=${minor:-0}
|
|
248
|
+
patch=${patch:-0}
|
|
249
|
+
printf '%03d%03d%03d' "$major" "$minor" "$patch"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
har_python_version_ge() {
|
|
253
|
+
local a b
|
|
254
|
+
a="$(har_python_version_to_sortable "$1")"
|
|
255
|
+
b="$(har_python_version_to_sortable "$2")"
|
|
256
|
+
[ "$a" -ge "$b" ]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
har_python_requires_minimum() {
|
|
260
|
+
local spec="$1"
|
|
261
|
+
local ver=""
|
|
262
|
+
|
|
263
|
+
[ -n "$spec" ] || return 1
|
|
264
|
+
|
|
265
|
+
ver="$(printf '%s' "$spec" | sed -n \
|
|
266
|
+
-e 's/.*>=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
267
|
+
-e 's/.*~=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
268
|
+
-e 's/.*==\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
269
|
+
| head -1)"
|
|
270
|
+
|
|
271
|
+
if [ -z "$ver" ]; then
|
|
272
|
+
ver="$(printf '%s' "$spec" | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
[ -n "$ver" ] && echo "$ver"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
har_python_read_dot_version() {
|
|
279
|
+
local dir="$1"
|
|
280
|
+
[ -f "$dir/.python-version" ] || return 1
|
|
281
|
+
head -1 "$dir/.python-version" | tr -d '[:space:]'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
har_python_read_requires_spec() {
|
|
285
|
+
local dir="$1"
|
|
286
|
+
local spec=""
|
|
287
|
+
|
|
288
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
289
|
+
spec="$(sed -n 's/^[[:space:]]*requires-python[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
290
|
+
"$dir/pyproject.toml" | head -1)"
|
|
291
|
+
fi
|
|
292
|
+
if [ -z "$spec" ] && [ -f "$dir/setup.cfg" ]; then
|
|
293
|
+
spec="$(sed -n '/^\[options\]/,/^\[/ s/^python_requires[[:space:]]*=[[:space:]]*\(.*\)/\1/p' \
|
|
294
|
+
"$dir/setup.cfg" | head -1 | tr -d ' "')"
|
|
295
|
+
fi
|
|
296
|
+
[ -n "$spec" ] && echo "$spec"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
har_python_project_minimum() {
|
|
300
|
+
local dir="$1"
|
|
301
|
+
local req_min="" dot_ver="" higher=""
|
|
302
|
+
|
|
303
|
+
req_min="$(har_python_requires_minimum "$(har_python_read_requires_spec "$dir" || true)" || true)"
|
|
304
|
+
dot_ver="$(har_python_read_dot_version "$dir" || true)"
|
|
305
|
+
if [ -n "$dot_ver" ]; then
|
|
306
|
+
dot_ver="$(printf '%s' "$dot_ver" | sed 's/^v//; s/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if [ -n "$req_min" ] && [ -n "$dot_ver" ]; then
|
|
310
|
+
if har_python_version_ge "$dot_ver" "$req_min"; then
|
|
311
|
+
higher="$dot_ver"
|
|
312
|
+
else
|
|
313
|
+
higher="$req_min"
|
|
314
|
+
fi
|
|
315
|
+
elif [ -n "$req_min" ]; then
|
|
316
|
+
higher="$req_min"
|
|
317
|
+
elif [ -n "$dot_ver" ]; then
|
|
318
|
+
higher="$dot_ver"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
[ -n "$higher" ] && echo "$higher"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
har_python_is_uv_project() {
|
|
325
|
+
local dir="$1"
|
|
326
|
+
[ -f "$dir/uv.lock" ] && return 0
|
|
327
|
+
[ -f "$dir/pyproject.toml" ] && grep -q '^\[tool\.uv\]' "$dir/pyproject.toml" 2>/dev/null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
har_python_uv_find() {
|
|
331
|
+
local dir="$1"
|
|
332
|
+
local request="${2:-}"
|
|
333
|
+
|
|
334
|
+
command -v uv >/dev/null 2>&1 || return 1
|
|
335
|
+
if [ -n "$request" ]; then
|
|
336
|
+
(cd "$dir" && uv python find "$request" 2>/dev/null)
|
|
337
|
+
else
|
|
338
|
+
(cd "$dir" && uv python find 2>/dev/null)
|
|
339
|
+
fi
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
har_python_resolve_interpreter() {
|
|
343
|
+
local dir="$1"
|
|
344
|
+
local minimum="${2:-}"
|
|
345
|
+
local found=""
|
|
346
|
+
|
|
347
|
+
if har_python_is_uv_project "$dir"; then
|
|
348
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
349
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
350
|
+
echo "$found"
|
|
351
|
+
return 0
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if [ -f "$dir/.python-version" ]; then
|
|
356
|
+
found="$(har_python_uv_find "$dir" "$(har_python_read_dot_version "$dir")" || true)"
|
|
357
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
358
|
+
echo "$found"
|
|
359
|
+
return 0
|
|
360
|
+
fi
|
|
361
|
+
if command -v pyenv >/dev/null 2>&1; then
|
|
362
|
+
found="$(cd "$dir" && pyenv which python 2>/dev/null || true)"
|
|
363
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
364
|
+
echo "$found"
|
|
365
|
+
return 0
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [ -n "$minimum" ]; then
|
|
371
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
372
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
373
|
+
echo "$found"
|
|
374
|
+
return 0
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
379
|
+
local sys_py sys_ver=""
|
|
380
|
+
sys_py="$(command -v python3)"
|
|
381
|
+
if [ -z "$minimum" ]; then
|
|
382
|
+
echo "$sys_py"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
sys_ver="$(har_python_version_from_bin "$sys_py")"
|
|
386
|
+
if [ -n "$sys_ver" ] && har_python_version_ge "$sys_ver" "$minimum"; then
|
|
387
|
+
echo "$sys_py"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
echo "$sys_py"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
return 1
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
har_python_warn_if_below_minimum() {
|
|
398
|
+
local bin="$1"
|
|
399
|
+
local minimum="$2"
|
|
400
|
+
local actual=""
|
|
401
|
+
|
|
402
|
+
[ -n "$minimum" ] || return 0
|
|
403
|
+
actual="$(har_python_version_from_bin "$bin")"
|
|
404
|
+
[ -n "$actual" ] || return 0
|
|
405
|
+
if ! har_python_version_ge "$actual" "$minimum"; then
|
|
406
|
+
pt_log "WARNING: resolved Python ${actual} is older than required ${minimum} (requires-python / .python-version)"
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
har_python_create_venv() {
|
|
411
|
+
local dir="$1"
|
|
412
|
+
local venv_dir="$2"
|
|
413
|
+
local interpreter="$3"
|
|
414
|
+
local venv_rel="${venv_dir#"$dir"/}"
|
|
415
|
+
|
|
416
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
417
|
+
pt_log "Creating Python venv with uv at ${venv_rel}..."
|
|
418
|
+
if (cd "$dir" && uv venv --python "$interpreter" "$venv_dir" 2>/dev/null) \
|
|
419
|
+
|| (cd "$dir" && uv venv "$venv_dir" 2>/dev/null); then
|
|
420
|
+
return 0
|
|
421
|
+
fi
|
|
422
|
+
pt_log "uv venv failed — falling back to ${interpreter} -m venv"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
pt_log "Creating Python venv at ${venv_rel}..."
|
|
426
|
+
"$interpreter" -m venv "$venv_dir"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
har_python_install_deps() {
|
|
430
|
+
local dir="$1"
|
|
431
|
+
local venv_dir="$2"
|
|
432
|
+
|
|
433
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
434
|
+
pt_log "Syncing Python dependencies with uv..."
|
|
435
|
+
if (cd "$dir" && UV_PROJECT_ENVIRONMENT="$venv_dir" uv sync --quiet 2>/dev/null); then
|
|
436
|
+
return 0
|
|
437
|
+
fi
|
|
438
|
+
if (cd "$dir" && uv sync --quiet 2>/dev/null); then
|
|
439
|
+
return 0
|
|
440
|
+
fi
|
|
441
|
+
pt_log "uv sync failed — falling back to pip"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
231
447
|
provision_python() {
|
|
232
448
|
local dir="$1"
|
|
233
449
|
local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
|
|
234
450
|
local venv_dir="$dir/$venv_rel"
|
|
235
|
-
local python_bin="
|
|
451
|
+
local python_bin=""
|
|
452
|
+
local interpreter=""
|
|
453
|
+
local project_min=""
|
|
454
|
+
|
|
455
|
+
project_min="$(har_python_project_minimum "$dir" || true)"
|
|
456
|
+
interpreter="$(har_python_resolve_interpreter "$dir" "$project_min" || true)"
|
|
236
457
|
|
|
237
|
-
if ! command -v
|
|
238
|
-
pt_log "
|
|
458
|
+
if [ -z "$interpreter" ] || ! command -v "$interpreter" >/dev/null 2>&1; then
|
|
459
|
+
pt_log "No suitable Python interpreter found — skipping venv provisioning"
|
|
239
460
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
240
461
|
append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
|
|
241
462
|
return
|
|
242
463
|
fi
|
|
243
464
|
|
|
465
|
+
if [ -n "$project_min" ]; then
|
|
466
|
+
har_python_warn_if_below_minimum "$interpreter" "$project_min"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if [ -d "$venv_dir" ] && [ -x "$venv_dir/bin/python" ] && [ -n "$project_min" ]; then
|
|
470
|
+
local venv_ver=""
|
|
471
|
+
venv_ver="$(har_python_version_from_bin "$venv_dir/bin/python")"
|
|
472
|
+
if [ -n "$venv_ver" ] && ! har_python_version_ge "$venv_ver" "$project_min"; then
|
|
473
|
+
pt_log "Existing venv Python ${venv_ver} is older than required ${project_min} — recreating..."
|
|
474
|
+
rm -rf "$venv_dir"
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
|
|
244
478
|
if [ ! -d "$venv_dir" ]; then
|
|
245
|
-
|
|
246
|
-
if ! python3 -m venv "$venv_dir"; then
|
|
479
|
+
if ! har_python_create_venv "$dir" "$venv_dir" "$interpreter"; then
|
|
247
480
|
rm -rf "$venv_dir"
|
|
248
|
-
pt_log "
|
|
481
|
+
pt_log "venv creation failed (on Debian/Ubuntu install python3-venv) — using resolved interpreter"
|
|
249
482
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
250
|
-
append_env "PYTHON_BIN" "$
|
|
483
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
251
484
|
return
|
|
252
485
|
fi
|
|
253
486
|
fi
|
|
254
487
|
|
|
255
488
|
if [ ! -x "$venv_dir/bin/python" ]; then
|
|
256
|
-
pt_log "Python venv at $venv_rel is missing or broken — using
|
|
489
|
+
pt_log "Python venv at $venv_rel is missing or broken — using resolved interpreter"
|
|
257
490
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
258
|
-
append_env "PYTHON_BIN" "$
|
|
491
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
259
492
|
return
|
|
260
493
|
fi
|
|
261
494
|
|
|
262
495
|
python_bin="$venv_dir/bin/python"
|
|
496
|
+
if [ -n "$project_min" ]; then
|
|
497
|
+
har_python_warn_if_below_minimum "$python_bin" "$project_min"
|
|
498
|
+
fi
|
|
499
|
+
|
|
263
500
|
# shellcheck disable=SC1091
|
|
264
501
|
source "$venv_dir/bin/activate"
|
|
265
502
|
|
|
266
503
|
if ! run_install_cmd "$dir"; then
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
504
|
+
if ! har_python_install_deps "$dir" "$venv_dir"; then
|
|
505
|
+
pt_log "Installing Python dependencies..."
|
|
506
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
507
|
+
(cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
|
|
508
|
+
elif [ -f "$dir/requirements.txt" ]; then
|
|
509
|
+
(cd "$dir" && pip install -q -r requirements.txt)
|
|
510
|
+
elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
|
|
511
|
+
(cd "$dir" && pip install -q -e .)
|
|
512
|
+
elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
|
|
513
|
+
(cd "$dir" && pipenv install --dev)
|
|
514
|
+
python_bin="$(cd "$dir" && pipenv --py)"
|
|
515
|
+
fi
|
|
277
516
|
fi
|
|
278
517
|
fi
|
|
279
518
|
|
|
@@ -228,52 +228,291 @@ provision_node() {
|
|
|
228
228
|
append_path_prefix "$dir/node_modules/.bin"
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
# ── Python interpreter helpers ────────────────────────────────────────────────
|
|
232
|
+
# Resolve the project Python (requires-python, .python-version, uv) instead of
|
|
233
|
+
# blindly using PATH python3.
|
|
234
|
+
|
|
235
|
+
har_python_version_from_bin() {
|
|
236
|
+
local bin="$1"
|
|
237
|
+
"$bin" -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null \
|
|
238
|
+
| tr -d '[:space:]'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
har_python_version_to_sortable() {
|
|
242
|
+
local v="${1#python}"
|
|
243
|
+
v="${v#v}"
|
|
244
|
+
local major=0 minor=0 patch=0
|
|
245
|
+
IFS='.' read -r major minor patch _ <<< "$v"
|
|
246
|
+
major=${major:-0}
|
|
247
|
+
minor=${minor:-0}
|
|
248
|
+
patch=${patch:-0}
|
|
249
|
+
printf '%03d%03d%03d' "$major" "$minor" "$patch"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
har_python_version_ge() {
|
|
253
|
+
local a b
|
|
254
|
+
a="$(har_python_version_to_sortable "$1")"
|
|
255
|
+
b="$(har_python_version_to_sortable "$2")"
|
|
256
|
+
[ "$a" -ge "$b" ]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
har_python_requires_minimum() {
|
|
260
|
+
local spec="$1"
|
|
261
|
+
local ver=""
|
|
262
|
+
|
|
263
|
+
[ -n "$spec" ] || return 1
|
|
264
|
+
|
|
265
|
+
ver="$(printf '%s' "$spec" | sed -n \
|
|
266
|
+
-e 's/.*>=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
267
|
+
-e 's/.*~=\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
268
|
+
-e 's/.*==\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p' \
|
|
269
|
+
| head -1)"
|
|
270
|
+
|
|
271
|
+
if [ -z "$ver" ]; then
|
|
272
|
+
ver="$(printf '%s' "$spec" | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
[ -n "$ver" ] && echo "$ver"
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
har_python_read_dot_version() {
|
|
279
|
+
local dir="$1"
|
|
280
|
+
[ -f "$dir/.python-version" ] || return 1
|
|
281
|
+
head -1 "$dir/.python-version" | tr -d '[:space:]'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
har_python_read_requires_spec() {
|
|
285
|
+
local dir="$1"
|
|
286
|
+
local spec=""
|
|
287
|
+
|
|
288
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
289
|
+
spec="$(sed -n 's/^[[:space:]]*requires-python[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
290
|
+
"$dir/pyproject.toml" | head -1)"
|
|
291
|
+
fi
|
|
292
|
+
if [ -z "$spec" ] && [ -f "$dir/setup.cfg" ]; then
|
|
293
|
+
spec="$(sed -n '/^\[options\]/,/^\[/ s/^python_requires[[:space:]]*=[[:space:]]*\(.*\)/\1/p' \
|
|
294
|
+
"$dir/setup.cfg" | head -1 | tr -d ' "')"
|
|
295
|
+
fi
|
|
296
|
+
[ -n "$spec" ] && echo "$spec"
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
har_python_project_minimum() {
|
|
300
|
+
local dir="$1"
|
|
301
|
+
local req_min="" dot_ver="" higher=""
|
|
302
|
+
|
|
303
|
+
req_min="$(har_python_requires_minimum "$(har_python_read_requires_spec "$dir" || true)" || true)"
|
|
304
|
+
dot_ver="$(har_python_read_dot_version "$dir" || true)"
|
|
305
|
+
if [ -n "$dot_ver" ]; then
|
|
306
|
+
dot_ver="$(printf '%s' "$dot_ver" | sed 's/^v//; s/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if [ -n "$req_min" ] && [ -n "$dot_ver" ]; then
|
|
310
|
+
if har_python_version_ge "$dot_ver" "$req_min"; then
|
|
311
|
+
higher="$dot_ver"
|
|
312
|
+
else
|
|
313
|
+
higher="$req_min"
|
|
314
|
+
fi
|
|
315
|
+
elif [ -n "$req_min" ]; then
|
|
316
|
+
higher="$req_min"
|
|
317
|
+
elif [ -n "$dot_ver" ]; then
|
|
318
|
+
higher="$dot_ver"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
[ -n "$higher" ] && echo "$higher"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
har_python_is_uv_project() {
|
|
325
|
+
local dir="$1"
|
|
326
|
+
[ -f "$dir/uv.lock" ] && return 0
|
|
327
|
+
[ -f "$dir/pyproject.toml" ] && grep -q '^\[tool\.uv\]' "$dir/pyproject.toml" 2>/dev/null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
har_python_uv_find() {
|
|
331
|
+
local dir="$1"
|
|
332
|
+
local request="${2:-}"
|
|
333
|
+
|
|
334
|
+
command -v uv >/dev/null 2>&1 || return 1
|
|
335
|
+
if [ -n "$request" ]; then
|
|
336
|
+
(cd "$dir" && uv python find "$request" 2>/dev/null)
|
|
337
|
+
else
|
|
338
|
+
(cd "$dir" && uv python find 2>/dev/null)
|
|
339
|
+
fi
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
har_python_resolve_interpreter() {
|
|
343
|
+
local dir="$1"
|
|
344
|
+
local minimum="${2:-}"
|
|
345
|
+
local found=""
|
|
346
|
+
|
|
347
|
+
if har_python_is_uv_project "$dir"; then
|
|
348
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
349
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
350
|
+
echo "$found"
|
|
351
|
+
return 0
|
|
352
|
+
fi
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if [ -f "$dir/.python-version" ]; then
|
|
356
|
+
found="$(har_python_uv_find "$dir" "$(har_python_read_dot_version "$dir")" || true)"
|
|
357
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
358
|
+
echo "$found"
|
|
359
|
+
return 0
|
|
360
|
+
fi
|
|
361
|
+
if command -v pyenv >/dev/null 2>&1; then
|
|
362
|
+
found="$(cd "$dir" && pyenv which python 2>/dev/null || true)"
|
|
363
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
364
|
+
echo "$found"
|
|
365
|
+
return 0
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
if [ -n "$minimum" ]; then
|
|
371
|
+
found="$(har_python_uv_find "$dir" "$minimum" || true)"
|
|
372
|
+
if [ -n "$found" ] && [ -x "$found" ]; then
|
|
373
|
+
echo "$found"
|
|
374
|
+
return 0
|
|
375
|
+
fi
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
379
|
+
local sys_py sys_ver=""
|
|
380
|
+
sys_py="$(command -v python3)"
|
|
381
|
+
if [ -z "$minimum" ]; then
|
|
382
|
+
echo "$sys_py"
|
|
383
|
+
return 0
|
|
384
|
+
fi
|
|
385
|
+
sys_ver="$(har_python_version_from_bin "$sys_py")"
|
|
386
|
+
if [ -n "$sys_ver" ] && har_python_version_ge "$sys_ver" "$minimum"; then
|
|
387
|
+
echo "$sys_py"
|
|
388
|
+
return 0
|
|
389
|
+
fi
|
|
390
|
+
echo "$sys_py"
|
|
391
|
+
return 0
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
return 1
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
har_python_warn_if_below_minimum() {
|
|
398
|
+
local bin="$1"
|
|
399
|
+
local minimum="$2"
|
|
400
|
+
local actual=""
|
|
401
|
+
|
|
402
|
+
[ -n "$minimum" ] || return 0
|
|
403
|
+
actual="$(har_python_version_from_bin "$bin")"
|
|
404
|
+
[ -n "$actual" ] || return 0
|
|
405
|
+
if ! har_python_version_ge "$actual" "$minimum"; then
|
|
406
|
+
pt_log "WARNING: resolved Python ${actual} is older than required ${minimum} (requires-python / .python-version)"
|
|
407
|
+
fi
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
har_python_create_venv() {
|
|
411
|
+
local dir="$1"
|
|
412
|
+
local venv_dir="$2"
|
|
413
|
+
local interpreter="$3"
|
|
414
|
+
local venv_rel="${venv_dir#"$dir"/}"
|
|
415
|
+
|
|
416
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
417
|
+
pt_log "Creating Python venv with uv at ${venv_rel}..."
|
|
418
|
+
if (cd "$dir" && uv venv --python "$interpreter" "$venv_dir" 2>/dev/null) \
|
|
419
|
+
|| (cd "$dir" && uv venv "$venv_dir" 2>/dev/null); then
|
|
420
|
+
return 0
|
|
421
|
+
fi
|
|
422
|
+
pt_log "uv venv failed — falling back to ${interpreter} -m venv"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
pt_log "Creating Python venv at ${venv_rel}..."
|
|
426
|
+
"$interpreter" -m venv "$venv_dir"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
har_python_install_deps() {
|
|
430
|
+
local dir="$1"
|
|
431
|
+
local venv_dir="$2"
|
|
432
|
+
|
|
433
|
+
if har_python_is_uv_project "$dir" && command -v uv >/dev/null 2>&1; then
|
|
434
|
+
pt_log "Syncing Python dependencies with uv..."
|
|
435
|
+
if (cd "$dir" && UV_PROJECT_ENVIRONMENT="$venv_dir" uv sync --quiet 2>/dev/null); then
|
|
436
|
+
return 0
|
|
437
|
+
fi
|
|
438
|
+
if (cd "$dir" && uv sync --quiet 2>/dev/null); then
|
|
439
|
+
return 0
|
|
440
|
+
fi
|
|
441
|
+
pt_log "uv sync failed — falling back to pip"
|
|
442
|
+
return 1
|
|
443
|
+
fi
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
231
447
|
provision_python() {
|
|
232
448
|
local dir="$1"
|
|
233
449
|
local venv_rel="${HARNESS_PYTHON_VENV_DIR:-.har/venv}"
|
|
234
450
|
local venv_dir="$dir/$venv_rel"
|
|
235
|
-
local python_bin="
|
|
451
|
+
local python_bin=""
|
|
452
|
+
local interpreter=""
|
|
453
|
+
local project_min=""
|
|
454
|
+
|
|
455
|
+
project_min="$(har_python_project_minimum "$dir" || true)"
|
|
456
|
+
interpreter="$(har_python_resolve_interpreter "$dir" "$project_min" || true)"
|
|
236
457
|
|
|
237
|
-
if ! command -v
|
|
238
|
-
pt_log "
|
|
458
|
+
if [ -z "$interpreter" ] || ! command -v "$interpreter" >/dev/null 2>&1; then
|
|
459
|
+
pt_log "No suitable Python interpreter found — skipping venv provisioning"
|
|
239
460
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
240
461
|
append_env "PYTHON_BIN" "${PYTHON_BIN:-python3}"
|
|
241
462
|
return
|
|
242
463
|
fi
|
|
243
464
|
|
|
465
|
+
if [ -n "$project_min" ]; then
|
|
466
|
+
har_python_warn_if_below_minimum "$interpreter" "$project_min"
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
if [ -d "$venv_dir" ] && [ -x "$venv_dir/bin/python" ] && [ -n "$project_min" ]; then
|
|
470
|
+
local venv_ver=""
|
|
471
|
+
venv_ver="$(har_python_version_from_bin "$venv_dir/bin/python")"
|
|
472
|
+
if [ -n "$venv_ver" ] && ! har_python_version_ge "$venv_ver" "$project_min"; then
|
|
473
|
+
pt_log "Existing venv Python ${venv_ver} is older than required ${project_min} — recreating..."
|
|
474
|
+
rm -rf "$venv_dir"
|
|
475
|
+
fi
|
|
476
|
+
fi
|
|
477
|
+
|
|
244
478
|
if [ ! -d "$venv_dir" ]; then
|
|
245
|
-
|
|
246
|
-
if ! python3 -m venv "$venv_dir"; then
|
|
479
|
+
if ! har_python_create_venv "$dir" "$venv_dir" "$interpreter"; then
|
|
247
480
|
rm -rf "$venv_dir"
|
|
248
|
-
pt_log "
|
|
481
|
+
pt_log "venv creation failed (on Debian/Ubuntu install python3-venv) — using resolved interpreter"
|
|
249
482
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
250
|
-
append_env "PYTHON_BIN" "$
|
|
483
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
251
484
|
return
|
|
252
485
|
fi
|
|
253
486
|
fi
|
|
254
487
|
|
|
255
488
|
if [ ! -x "$venv_dir/bin/python" ]; then
|
|
256
|
-
pt_log "Python venv at $venv_rel is missing or broken — using
|
|
489
|
+
pt_log "Python venv at $venv_rel is missing or broken — using resolved interpreter"
|
|
257
490
|
append_env "HARNESS_ECOSYSTEM" "python"
|
|
258
|
-
append_env "PYTHON_BIN" "$
|
|
491
|
+
append_env "PYTHON_BIN" "$interpreter"
|
|
259
492
|
return
|
|
260
493
|
fi
|
|
261
494
|
|
|
262
495
|
python_bin="$venv_dir/bin/python"
|
|
496
|
+
if [ -n "$project_min" ]; then
|
|
497
|
+
har_python_warn_if_below_minimum "$python_bin" "$project_min"
|
|
498
|
+
fi
|
|
499
|
+
|
|
263
500
|
# shellcheck disable=SC1091
|
|
264
501
|
source "$venv_dir/bin/activate"
|
|
265
502
|
|
|
266
503
|
if ! run_install_cmd "$dir"; then
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
504
|
+
if ! har_python_install_deps "$dir" "$venv_dir"; then
|
|
505
|
+
pt_log "Installing Python dependencies..."
|
|
506
|
+
if [ -f "$dir/pyproject.toml" ]; then
|
|
507
|
+
(cd "$dir" && pip install -q -e ".[dev]" 2>/dev/null) || (cd "$dir" && pip install -q -e .)
|
|
508
|
+
elif [ -f "$dir/requirements.txt" ]; then
|
|
509
|
+
(cd "$dir" && pip install -q -r requirements.txt)
|
|
510
|
+
elif [ -f "$dir/setup.py" ] || [ -f "$dir/setup.cfg" ]; then
|
|
511
|
+
(cd "$dir" && pip install -q -e .)
|
|
512
|
+
elif [ -f "$dir/Pipfile" ] && command -v pipenv >/dev/null 2>&1; then
|
|
513
|
+
(cd "$dir" && pipenv install --dev)
|
|
514
|
+
python_bin="$(cd "$dir" && pipenv --py)"
|
|
515
|
+
fi
|
|
277
516
|
fi
|
|
278
517
|
fi
|
|
279
518
|
|