Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion bin/base-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Usage:

Options:
--project <name> Run using ~/.base.d/<name>/.venv.
--preserve-project-context
Keep the inherited BASE_PROJECT while selecting a venv.
-h, --help Show this help text.

Purpose:
Expand All @@ -61,6 +63,7 @@ EOF

main() {
local base_cli_path base_cli_source base_home project package python_bin old_pythonpath base_pythonpath
local preserve_project_context=0

base_home="$(base_wrapper_base_home)" || base_wrapper_die "Unable to resolve Base home."
project="${BASE_PROJECT:-base}"
Expand All @@ -78,6 +81,9 @@ main() {
[[ -n "${1:-}" ]] || base_wrapper_die "Option '--project' requires an argument."
project="$1"
;;
--preserve-project-context)
preserve_project_context=1
;;
--)
shift
break
Expand Down Expand Up @@ -111,7 +117,9 @@ main() {
esac

BASE_HOME="$base_home"
BASE_PROJECT="$project"
if [[ "$preserve_project_context" != 1 || -z "${BASE_PROJECT:-}" ]]; then
BASE_PROJECT="$project"
fi
# This wrapper always launches Base's control-plane Python packages. The
# selected project only chooses the virtual environment; it must not move
# Base-owned runtime artifacts into a project's namespace.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# Fake Python executables are Bash scripts loaded through BASH_ENV. Normalize
# interpreter isolation and the owned entrypoint into the module arguments
# those stubs model. Real Python integration tests exercise the actual launcher.
if [[ "${0##*/}" == python* && "${1:-}" == -I ]]; then
if [[ "${BASE_TEST_PRESERVE_PYTHON_ENTRYPOINT:-}" != 1 &&
"${0##*/}" == python* && "${1:-}" == -I ]]; then
shift
if [[ "${1:-}" == */base_cli_adapters/module_entrypoint.py ]]; then
shift
Expand Down
23 changes: 23 additions & 0 deletions cli/bash/commands/basectl/tests/runtime-dispatch.bats
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,29 @@ EOF
[[ "$output" == *"ARGS=-m base_setup --dry-run demo"* ]]
}

@test "base-wrapper can select the Base venv without replacing active project context" {
local python_bin="$TEST_HOME/.base.d/base/.venv/bin/python"

mkdir -p "$(dirname "$python_bin")"
cat > "$python_bin" <<'EOF'
#!/usr/bin/env bash
printf 'BASE_PROJECT=%s\n' "$BASE_PROJECT"
printf 'ARGS=%s\n' "$*"
EOF
chmod +x "$python_bin"

run env \
HOME="$TEST_HOME" \
BASE_PROJECT=demo \
BASE_TEST_PRESERVE_PYTHON_ENTRYPOINT=1 \
"$BASE_REPO_ROOT/bin/base-wrapper" --project base --preserve-project-context \
base_projects activation-sources demo --format command-protocol

[ "$status" -eq 0 ]
[[ "$output" == *"BASE_PROJECT=demo"* ]]
[[ "$output" == *"ARGS=-I $BASE_REPO_ROOT/cli/python/base_cli_adapters/module_entrypoint.py base_projects activation-sources demo --format command-protocol"* ]]
}

@test "basectl propagates supported wrapper diagnostics to explicit Base scripts" {
local script_path="$TEST_TMPDIR/wrapper-diagnostics-script"

Expand Down
85 changes: 78 additions & 7 deletions cli/bash/commands/basectl/tests/runtime-shell.bats
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ EOF
#!/usr/bin/env bash
# Bash 3 reads BASH_ENV before assigning script arguments.
source "${BASH_ENV:?}"
if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "activation-sources" && "${4:-}" == "demo" ]]; then
if [[ "${1:-}" == "-I" &&
"${2:-}" == "$BASE_HOME/cli/python/base_cli_adapters/module_entrypoint.py" &&
"${3:-}" == "base_projects" && "${4:-}" == "activation-sources" && "${5:-}" == "demo" ]]; then
base_test_protocol_begin activation-source 0
base_test_protocol_end
exit 0
Expand All @@ -103,6 +105,8 @@ printf 'unexpected base_projects args: %s\n' "$*" >&2
exit 1
EOF
chmod +x "$venv_dir/bin/python"
mkdir -p "$TEST_HOME/.base.d/base/.venv/bin"
cp "$venv_dir/bin/python" "$TEST_HOME/.base.d/base/.venv/bin/python"

run env \
HOME="$TEST_HOME" \
Expand All @@ -111,6 +115,7 @@ EOF
BASE_PROJECT_ROOT="$project_root" \
BASE_PROJECT_MANIFEST="$project_root/base_manifest.yaml" \
BASE_PROJECT_VENV_DIR="$venv_dir" \
BASE_TEST_PRESERVE_PYTHON_ENTRYPOINT=1 \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
"$BASH" --rcfile "$BASE_REPO_ROOT/lib/bash/runtime/bashrc" -i -c '\
declare -p BASE_PROJECT; \
Expand Down Expand Up @@ -139,7 +144,9 @@ EOF
#!/usr/bin/env bash
# Bash 3 reads BASH_ENV before assigning script arguments.
source "${BASH_ENV:?}"
if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "activation-sources" && "${4:-}" == "demo" ]]; then
if [[ "${1:-}" == "-I" &&
"${2:-}" == "$BASE_HOME/cli/python/base_cli_adapters/module_entrypoint.py" &&
"${3:-}" == "base_projects" && "${4:-}" == "activation-sources" && "${5:-}" == "demo" ]]; then
base_test_protocol_begin activation-source 1
base_test_protocol_activation_source_record 0 "${BASE_TEST_ACTIVATION_SOURCE:?}"
base_test_protocol_end
Expand All @@ -156,6 +163,8 @@ project_activation_function() {
}
EOF
chmod +x "$venv_dir/bin/python"
mkdir -p "$TEST_HOME/.base.d/base/.venv/bin"
cp "$venv_dir/bin/python" "$TEST_HOME/.base.d/base/.venv/bin/python"

run env \
HOME="$TEST_HOME" \
Expand All @@ -165,6 +174,7 @@ EOF
BASE_PROJECT_MANIFEST="$project_root/base_manifest.yaml" \
BASE_PROJECT_VENV_DIR="$venv_dir" \
BASE_TEST_ACTIVATION_SOURCE="$activation_script" \
BASE_TEST_PRESERVE_PYTHON_ENTRYPOINT=1 \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
"$BASH" --rcfile "$BASE_REPO_ROOT/lib/bash/runtime/bashrc" -i -c '\
printf "PROJECT_ACTIVATION_PROJECT=%s\n" "${PROJECT_ACTIVATION_PROJECT:-}"; \
Expand Down Expand Up @@ -197,20 +207,30 @@ EOF
#!/usr/bin/env bash
# Bash 3 reads BASH_ENV before assigning script arguments.
source "${BASH_ENV:?}"
if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "activation-sources" && "${4:-}" == "demo" ]]; then
if [[ "${1:-}" == "-I" &&
"${2:-}" == "$BASE_HOME/cli/python/base_cli_adapters/module_entrypoint.py" &&
"${3:-}" == "base_projects" && "${4:-}" == "activation-sources" && "${5:-}" == "demo" ]]; then
printf 'startup-run-root=%s\n' "${BASE_CLI_RUN_ROOT:-unset}" >&2
printf 'startup-primary-log=%s\n' "${BASE_BASH_LIBS_PRIMARY_LOG:-unset}" >&2
printf 'startup-history-scope=%s\n' "${BASE_CLI_HISTORY_SCOPE:-unset}" >&2
base_test_protocol_begin activation-source 0
base_test_protocol_end
exit 0
fi
if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "list" ]]; then
if [[ ("${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "list") ||
("${1:-}" == "-I" && "${2:-}" == "$BASE_HOME/cli/python/base_cli_adapters/module_entrypoint.py" &&
"${3:-}" == "base_projects" && "${4:-}" == "list") ]]; then
printf 'demo\t%s/demo\n' "${BASE_TEST_WORKSPACE:?}"
exit 0
fi
if [[ "${1:-}" == "-m" && "${2:-}" == "base_history.record" ]]; then
printf '%s\n' "$*" >> "${BASE_TEST_RUNTIME_HISTORY_STATE:?}"
if [[ ("${1:-}" == "-m" && "${2:-}" == "base_history.record") ||
("${1:-}" == "-I" && "${2:-}" == "$BASE_HOME/cli/python/base_cli_adapters/module_entrypoint.py" &&
"${3:-}" == "base_history.record") ]]; then
if [[ "${1:-}" == "-I" ]]; then
printf '%s\n' "-m ${*:3}" >> "${BASE_TEST_RUNTIME_HISTORY_STATE:?}"
else
printf '%s\n' "$*" >> "${BASE_TEST_RUNTIME_HISTORY_STATE:?}"
fi
exit 0
fi
printf 'unexpected base_projects args: %s\n' "$*" >&2
Expand Down Expand Up @@ -245,6 +265,7 @@ EOF
BASE_CLI_PROJECT_NAME=demo \
BASE_CLI_PROJECT_ROOT="$project_root" \
BASE_CLI_PROJECT_MANIFEST="$project_root/base_manifest.yaml" \
BASE_TEST_PRESERVE_PYTHON_ENTRYPOINT=1 \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
"$BASH" --rcfile "$BASE_REPO_ROOT/lib/bash/runtime/bashrc" -i -c '\
for variable in \
Expand Down Expand Up @@ -338,14 +359,18 @@ EOF
#!/usr/bin/env bash
# Bash 3 reads BASH_ENV before assigning script arguments.
source "${BASH_ENV:?}"
if [[ "${1:-}" == "-m" && "${2:-}" == "base_projects" && "${3:-}" == "activation-sources" && "${4:-}" == "demo" ]]; then
if [[ "${1:-}" == "-I" &&
"${2:-}" == "$BASE_HOME/cli/python/base_cli_adapters/module_entrypoint.py" &&
"${3:-}" == "base_projects" && "${4:-}" == "activation-sources" && "${5:-}" == "demo" ]]; then
printf 'ERROR: %s: activate.source[1] script %q does not exist.\n' "$BASE_PROJECT_MANIFEST" ".base/missing.sh" >&2
exit 1
fi
printf 'unexpected base_projects args: %s\n' "$*" >&2
exit 1
EOF
chmod +x "$venv_dir/bin/python"
mkdir -p "$TEST_HOME/.base.d/base/.venv/bin"
cp "$venv_dir/bin/python" "$TEST_HOME/.base.d/base/.venv/bin/python"

run env \
HOME="$TEST_HOME" \
Expand All @@ -354,6 +379,7 @@ EOF
BASE_PROJECT_ROOT="$project_root" \
BASE_PROJECT_MANIFEST="$project_root/base_manifest.yaml" \
BASE_PROJECT_VENV_DIR="$venv_dir" \
BASE_TEST_PRESERVE_PYTHON_ENTRYPOINT=1 \
BASE_CLI_RUNTIME_OWNER=base \
BASE_CLI_RUN_ID=failed-activation-run-id \
BASE_CLI_RUN_ROOT="$activation_run_root" \
Expand All @@ -376,6 +402,51 @@ EOF
[[ "$output" == *"body-history-scope=unset"* ]]
}

@test "Base runtime shell resolves activation sources with the Base virtual environment" {
local project_root="$TEST_TMPDIR/demo"
local project_venv_dir="$TEST_TMPDIR/demo-venv"
local base_venv_dir="$TEST_HOME/.base.d/base/.venv"

mkdir -p "$project_root" "$project_venv_dir/bin" "$base_venv_dir/bin"
touch "$project_root/base_manifest.yaml"
cat > "$project_venv_dir/bin/activate" <<'EOF'
VIRTUAL_ENV="$BASE_PROJECT_VENV_DIR"
export VIRTUAL_ENV
EOF
cat > "$project_venv_dir/bin/python" <<'EOF'
#!/usr/bin/env bash
printf 'project virtual environment Python was used unexpectedly\n' >&2
exit 1
EOF
cat > "$base_venv_dir/bin/python" <<'EOF'
#!/usr/bin/env bash
source "${BASH_ENV:?}"
if [[ "${3:-}" == "base_projects" && "${4:-}" == "activation-sources" && "${5:-}" == "demo" ]]; then
base_test_protocol_begin activation-source 0
base_test_protocol_end
exit 0
fi
printf 'unexpected Base Python args: %s\n' "$*" >&2
exit 1
EOF
chmod +x "$project_venv_dir/bin/python" "$base_venv_dir/bin/python"

run env \
HOME="$TEST_HOME" \
BASE_HOME="$BASE_REPO_ROOT" \
BASE_PROJECT=demo \
BASE_PROJECT_ROOT="$project_root" \
BASE_PROJECT_MANIFEST="$project_root/base_manifest.yaml" \
BASE_PROJECT_VENV_DIR="$project_venv_dir" \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
"$BASH" --rcfile "$BASE_REPO_ROOT/lib/bash/runtime/bashrc" -i -c '\
printf "VIRTUAL_ENV=%s\n" "$VIRTUAL_ENV"'

[ "$status" -eq 0 ]
[[ "$output" == *"VIRTUAL_ENV=$project_venv_dir"* ]]
[[ "$output" != *"project virtual environment Python was used unexpectedly"* ]]
}

@test "Base runtime shell loads base_init before user bashrc and owns final prompt" {
cat > "$TEST_HOME/.bashrc" <<'EOF'
alias user_bashrc_alias='printf user-bashrc'
Expand Down
2 changes: 1 addition & 1 deletion lib/bash/runtime/bashrc
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ _base_runtime_source_project_activation_scripts() {
return 1
}

scripts_output="$("$wrapper" base_projects activation-sources "$BASE_PROJECT" --format command-protocol)" || {
scripts_output="$("$wrapper" --project base --preserve-project-context base_projects activation-sources "$BASE_PROJECT" --format command-protocol)" || {
_base_runtime_error "Unable to resolve project activation scripts for '$BASE_PROJECT'."
return 1
}
Expand Down
Loading