Skip to content

Commit 075d3ac

Browse files
Add support for conda init --condabin, which adds condabin/ to PATH (#965)
Co-authored-by: Marco Esters <[email protected]>
1 parent 505b9c8 commit 075d3ac

File tree

19 files changed

+420
-77
lines changed

19 files changed

+420
-77
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ jobs:
127127
conda activate constructor-dev
128128
echo "CONSTRUCTOR_CONDA_EXE=$CONDA_PREFIX/standalone_conda/conda.exe" >> $GITHUB_ENV
129129
fi
130+
echo "CONDA_PREFIX=$CONDA_PREFIX" >> $GITHUB_ENV
130131
- name: conda info
131132
run: conda info
132133
- name: conda list

CONSTRUCT.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,16 @@ If `header_image` is not provided, use this text when generating the image
491491

492492
Add an option to the installer so the user can choose whether to run `conda init`
493493
after the installation (Unix), or to add certain subdirectories of the installation
494-
to PATH (Windows). See also `initialize_by_default`.
494+
to PATH (Windows). Requires `conda` to be part of the `base` environment. Valid options:
495+
496+
- `classic` or `True`: runs `conda init` on Unix, which injects a shell function in the
497+
shell profiles. On Windows, it adds `$INSTDIR`, `$INSTDIR/Scripts`, `$INSTDIR/Library/bin`
498+
to `PATH`. This is the default.
499+
- `condabin`: only adds `$INSTDIR/condabin` to `PATH`. On Unix, `conda>=25.5.0` is required
500+
in `base`.
501+
- `False`: the installer doesn't perform initialization.
502+
503+
See also `initialize_by_default`.
495504

496505
### `initialize_by_default`
497506

@@ -500,7 +509,7 @@ is true for GUI installers (EXE, PKG) and false for shell installers. The user
500509
is able to change the default during interactive installation. NOTE: For Windows,
501510
`AddToPath` is disabled when `InstallationType=AllUsers`.
502511

503-
Only applies if `initialize_conda` is true.
512+
Only applies if `initialize_conda` is not false.
504513

505514
### `register_python`
506515

constructor/_schema.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class PkgDomains(StrEnum):
5050
LOCAL_SYSTEM = "enable_localSystem"
5151

5252

53+
class CondaInitialization(StrEnum):
54+
CLASSIC = "classic"
55+
CONDABIN = "condabin"
56+
57+
5358
class ChannelRemap(BaseModel):
5459
model_config: ConfigDict = _base_config_dict
5560

@@ -649,11 +654,20 @@ class ConstructorConfiguration(BaseModel):
649654
If `header_image` is not provided, use this text when generating the image
650655
(Windows only). Defaults to `name`.
651656
"""
652-
initialize_conda: bool = True
657+
initialize_conda: CondaInitialization | bool = True
653658
"""
654659
Add an option to the installer so the user can choose whether to run `conda init`
655660
after the installation (Unix), or to add certain subdirectories of the installation
656-
to PATH (Windows). See also `initialize_by_default`.
661+
to PATH (Windows). Requires `conda` to be part of the `base` environment. Valid options:
662+
663+
- `classic` or `True`: runs `conda init` on Unix, which injects a shell function in the
664+
shell profiles. On Windows, it adds `$INSTDIR`, `$INSTDIR/Scripts`, `$INSTDIR/Library/bin`
665+
to `PATH`. This is the default.
666+
- `condabin`: only adds `$INSTDIR/condabin` to `PATH`. On Unix, `conda>=25.5.0` is required
667+
in `base`.
668+
- `False`: the installer doesn't perform initialization.
669+
670+
See also `initialize_by_default`.
657671
"""
658672
initialize_by_default: bool | None = None
659673
"""
@@ -662,7 +676,7 @@ class ConstructorConfiguration(BaseModel):
662676
is able to change the default during interactive installation. NOTE: For Windows,
663677
`AddToPath` is disabled when `InstallationType=AllUsers`.
664678
665-
Only applies if `initialize_conda` is true.
679+
Only applies if `initialize_conda` is not false.
666680
"""
667681
register_python: bool = True
668682
"""

constructor/data/construct.schema.json

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
"title": "ChannelRemap",
3636
"type": "object"
3737
},
38+
"CondaInitialization": {
39+
"enum": [
40+
"classic",
41+
"condabin"
42+
],
43+
"title": "CondaInitialization",
44+
"type": "string"
45+
},
3846
"ExtraEnv": {
3947
"additionalProperties": false,
4048
"properties": {
@@ -749,14 +757,21 @@
749757
}
750758
],
751759
"default": null,
752-
"description": "Default value for the option added by `initialize_conda`. The default is true for GUI installers (EXE, PKG) and false for shell installers. The user is able to change the default during interactive installation. NOTE: For Windows, `AddToPath` is disabled when `InstallationType=AllUsers`.\nOnly applies if `initialize_conda` is true.",
760+
"description": "Default value for the option added by `initialize_conda`. The default is true for GUI installers (EXE, PKG) and false for shell installers. The user is able to change the default during interactive installation. NOTE: For Windows, `AddToPath` is disabled when `InstallationType=AllUsers`.\nOnly applies if `initialize_conda` is not false.",
753761
"title": "Initialize By Default"
754762
},
755763
"initialize_conda": {
764+
"anyOf": [
765+
{
766+
"$ref": "#/$defs/CondaInitialization"
767+
},
768+
{
769+
"type": "boolean"
770+
}
771+
],
756772
"default": true,
757-
"description": "Add an option to the installer so the user can choose whether to run `conda init` after the installation (Unix), or to add certain subdirectories of the installation to PATH (Windows). See also `initialize_by_default`.",
758-
"title": "Initialize Conda",
759-
"type": "boolean"
773+
"description": "Add an option to the installer so the user can choose whether to run `conda init` after the installation (Unix), or to add certain subdirectories of the installation to PATH (Windows). Requires `conda` to be part of the `base` environment. Valid options:\n- `classic` or `True`: runs `conda init` on Unix, which injects a shell function in the shell profiles. On Windows, it adds `$INSTDIR`, `$INSTDIR/Scripts`, `$INSTDIR/Library/bin` to `PATH`. This is the default.\n- `condabin`: only adds `$INSTDIR/condabin` to `PATH`. On Unix, `conda>=25.5.0` is required in `base`.\n- `False`: the installer doesn't perform initialization.\nSee also `initialize_by_default`.",
774+
"title": "Initialize Conda"
760775
},
761776
"install_in_dependency_order": {
762777
"anyOf": [

constructor/header.sh

Lines changed: 98 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ SKIP_SCRIPTS=0
9393
{%- if enable_shortcuts == "true" %}
9494
SKIP_SHORTCUTS=0
9595
{%- endif %}
96+
INIT_CONDA=0
9697
TEST=0
9798
REINSTALL=0
9899
USAGE="
@@ -123,18 +124,30 @@ Installs ${INSTALLER_NAME} ${INSTALLER_VER}
123124
-u update an existing installation
124125
{%- if has_conda %}
125126
-t run package tests after installation (may install conda-build)
127+
{%- if initialize_conda %}
128+
-c run 'conda init{{ ' --condabin' if initialize_conda == 'condabin' else ''}}' after installation (only applies to batch mode)
129+
{%- endif %}
126130
{%- endif %}
127131
"
128132

133+
{#-
129134
# We used to have a getopt version here, falling back to getopts if needed
130135
# However getopt is not standardized and the version on Mac has different
131136
# behaviour. getopts is good enough for what we need :)
132137
# More info: https://unix.stackexchange.com/questions/62950/
138+
#}
139+
{%- set getopts_str = "bifhkp:s" %}
133140
{%- if enable_shortcuts == "true" %}
134-
while getopts "bifhkp:smut" x; do
135-
{%- else %}
136-
while getopts "bifhkp:sut" x; do
141+
{%- set getopts_str = getopts_str ~ "m" %}
142+
{%- endif %}
143+
{%- set getopts_str = getopts_str ~ "u" %}
144+
{%- if has_conda %}
145+
{%- set getopts_str = getopts_str ~ "t" %}
146+
{%- if initialize_conda %}
147+
{%- set getopts_str = getopts_str ~ "c" %}
148+
{%- endif %}
137149
{%- endif %}
150+
while getopts "{{ getopts_str }}" x; do
138151
case "$x" in
139152
h)
140153
printf "%s\\n" "$USAGE"
@@ -170,6 +183,11 @@ while getopts "bifhkp:sut" x; do
170183
t)
171184
TEST=1
172185
;;
186+
{%- if initialize_conda %}
187+
c)
188+
INIT_CONDA=1
189+
;;
190+
{%- endif %}
173191
{%- endif %}
174192
?)
175193
printf "ERROR: did not recognize option '%s', please try -h\\n" "$x"
@@ -514,7 +532,7 @@ if [ "$(id -u)" -ne 0 ]; then
514532
fi
515533
516534
# the third binary payload: the tarball of packages
517-
printf "Unpacking payload ...\n"
535+
printf "Unpacking payload...\n"
518536
extract_range "${boundary2}" "${boundary3}" | \
519537
CONDA_QUIET="$BATCH" "$CONDA_EXEC" constructor --extract-tarball --prefix "$PREFIX"
520538
@@ -547,12 +565,14 @@ MSGS="$PREFIX/.messages.txt"
547565
touch "$MSGS"
548566
export FORCE
549567
568+
{#-
550569
# original issue report:
551570
# https://github.com/ContinuumIO/anaconda-issues/issues/11148
552571
# First try to fix it (this apparently didn't work; QA reported the issue again)
553572
# https://github.com/conda/conda/pull/9073
554573
# Avoid silent errors when $HOME is not writable
555574
# https://github.com/conda/constructor/pull/669
575+
#}
556576
test -d ~/.conda || mkdir -p ~/.conda >/dev/null 2>/dev/null || test -d ~/.conda || mkdir ~/.conda
557577
558578
printf "\nInstalling base environment...\n\n"
@@ -635,7 +655,6 @@ rm -rf "$PREFIX/install_tmp"
635655
export TMP="$TMP_BACKUP"
636656
637657
638-
#The templating doesn't support nested if statements
639658
{%- if has_post_install %}
640659
if [ "$SKIP_SCRIPTS" = "1" ]; then
641660
printf "WARNING: skipping post_install.sh by user request\\n" >&2
@@ -675,9 +694,66 @@ if [ "${PYTHONPATH:-}" != "" ]; then
675694
printf " directories of packages that are compatible with the Python interpreter\\n"
676695
printf " in %s: %s\\n" "${INSTALLER_NAME}" "$PREFIX"
677696
fi
697+
{% if has_conda %}
698+
{%- if initialize_conda == 'condabin' %}
699+
_maybe_run_conda_init_condabin() {
700+
case $SHELL in
701+
# We call the module directly to avoid issues with spaces in shebang
702+
*zsh) "$PREFIX/bin/python" -m conda init --condabin zsh ;;
703+
*) "$PREFIX/bin/python" -m conda init --condabin ;;
704+
esac
705+
}
706+
{%- elif initialize_conda %}
707+
_maybe_run_conda_init() {
708+
case $SHELL in
709+
# We call the module directly to avoid issues with spaces in shebang
710+
*zsh) "$PREFIX/bin/python" -m conda init zsh ;;
711+
*) "$PREFIX/bin/python" -m conda init ;;
712+
esac
713+
if [ -f "$PREFIX/bin/mamba" ]; then
714+
# If the version of mamba is <2.0.0, we preferably use the `mamba` python module
715+
# to perform the initialization.
716+
#
717+
# Otherwise (i.e. as of 2.0.0), we use the `mamba shell init` command
718+
if [ "$("$PREFIX/bin/mamba" --version | head -n 1 | cut -d' ' -f2 | cut -d'.' -f1)" -lt 2 ]; then
719+
case $SHELL in
720+
# We call the module directly to avoid issues with spaces in shebang
721+
*zsh) "$PREFIX/bin/python" -m mamba.mamba init zsh ;;
722+
*) "$PREFIX/bin/python" -m mamba.mamba init ;;
723+
esac
724+
else
725+
case $SHELL in
726+
*zsh) "$PREFIX/bin/mamba" shell init --shell zsh ;;
727+
*) "$PREFIX/bin/mamba" shell init ;;
728+
esac
729+
fi
730+
fi
731+
}
732+
{%- endif %}
733+
{%- endif %}
678734
679735
if [ "$BATCH" = "0" ]; then
680-
{%- if has_conda and initialize_conda %}
736+
{%- if has_conda %}
737+
{%- if initialize_conda == 'condabin' %}
738+
DEFAULT={{ 'yes' if initialize_by_default else 'no' }}
739+
740+
printf "Do you wish to update your shell profile to add '%s/condabin' to PATH?\\n" "$PREFIX"
741+
printf "This will enable you to run 'conda' anywhere, without injecting a shell function.\\n"
742+
printf "You can undo this by running \`conda init --condabin --reverse? [yes|no]\\n"
743+
printf "[%s] >>> " "$DEFAULT"
744+
read -r ans
745+
if [ "$ans" = "" ]; then
746+
ans=$DEFAULT
747+
fi
748+
ans=$(echo "${ans}" | tr '[:lower:]' '[:upper:]')
749+
if [ "$ans" != "YES" ] && [ "$ans" != "Y" ]
750+
then
751+
printf "\\n"
752+
printf "'%s/condabin' will not be added to PATH.\\n" "$PREFIX"
753+
else
754+
_maybe_run_conda_init_condabin
755+
fi
756+
{%- elif initialize_conda %}
681757
DEFAULT={{ 'yes' if initialize_by_default else 'no' }}
682758
# Interactive mode.
683759
@@ -708,34 +784,26 @@ if [ "$BATCH" = "0" ]; then
708784
printf "conda init\\n"
709785
printf "\\n"
710786
else
711-
case $SHELL in
712-
# We call the module directly to avoid issues with spaces in shebang
713-
*zsh) "$PREFIX/bin/python" -m conda init zsh ;;
714-
*) "$PREFIX/bin/python" -m conda init ;;
715-
esac
716-
if [ -f "$PREFIX/bin/mamba" ]; then
717-
# If the version of mamba is <2.0.0, we preferably use the `mamba` python module
718-
# to perform the initialization.
719-
#
720-
# Otherwise (i.e. as of 2.0.0), we use the `mamba shell init` command
721-
if [ "$("$PREFIX/bin/mamba" --version | head -n 1 | cut -d' ' -f2 | cut -d'.' -f1)" -lt 2 ]; then
722-
case $SHELL in
723-
# We call the module directly to avoid issues with spaces in shebang
724-
*zsh) "$PREFIX/bin/python" -m mamba.mamba init zsh ;;
725-
*) "$PREFIX/bin/python" -m mamba.mamba init ;;
726-
esac
727-
else
728-
case $SHELL in
729-
*zsh) "$PREFIX/bin/mamba" shell init --shell zsh ;;
730-
*) "$PREFIX/bin/mamba" shell init ;;
731-
esac
732-
fi
733-
fi
787+
_maybe_run_conda_init
734788
fi
789+
{%- endif %}
735790
{%- endif %}
736791
737792
printf "Thank you for installing %s!\\n" "${INSTALLER_NAME}"
738-
fi # !BATCH
793+
{#- End of Interactive mode #}
794+
{#- Batch mode #}
795+
{%- if has_conda and initialize_conda %}
796+
elif [ "$INIT_CONDA" = "1" ]; then
797+
{%- if initialize_conda == 'condabin' %}
798+
printf "Adding '%s/condabin' to PATH...\\n" "$PREFIX"
799+
_maybe_run_conda_init_condabin
800+
{%- else %}
801+
printf "Initializing '%s' with 'conda init'...\\n" "$PREFIX"
802+
_maybe_run_conda_init
803+
{%- endif %}
804+
{%- endif %}
805+
{#- End of Batch mode #}
806+
fi
739807
740808
741809
{%- if has_conda %}

constructor/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,16 @@ def main_build(
262262
# '_dists': list[Dist]
263263
# '_urls': list[Tuple[url, md5]]
264264

265+
if initialize_conda := info.get("initialize_conda"):
266+
if not info.get("_has_conda"):
267+
sys.exit("Error: 'initialize_conda' requires 'conda' in the base environment.")
268+
if initialize_conda == "condabin" and platform.startswith(("linux-", "osx-")):
269+
conda = next(record for record in info.get("_records", ()) if record.name == "conda")
270+
if Version(conda.version) < Version("25.5.0"):
271+
sys.exit(
272+
"Error: 'initialize_conda == condabin' requires 'conda >=25.5.0' in base env."
273+
)
274+
265275
os.makedirs(output_dir, exist_ok=True)
266276
info_dicts = []
267277
for itype in itypes:

0 commit comments

Comments
 (0)