diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index b2c2e821d..ebfafa82a 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -20,7 +20,7 @@ jobs: GLOBAL: https://raw.githubusercontent.com/conda/infra/main/.github/global.yml LOCAL: .github/labels.yml steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: has_local uses: andstor/file-existence-action@v2.0.0 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e81b4ded..67083b04e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.13.0 hooks: - id: pyupgrade args: ["--py37-plus", "--keep-percent-format"] diff --git a/CONSTRUCT.md b/CONSTRUCT.md index 1bf559977..7440b3967 100644 --- a/CONSTRUCT.md +++ b/CONSTRUCT.md @@ -388,6 +388,30 @@ _type:_ string
Application name in the Windows "Programs and Features" control panel. Defaults to `${NAME} ${VERSION} (Python ${PYVERSION} ${ARCH})`. +### `script_env_variables` + +_required:_ no
+_type:_ dictionary
+ +Dictionary of additional environment variables to be made available to +the pre_install and post_install scripts, in the form of VAR:VALUE +pairs. These environment variables are in addition to those in the +`post_install` section above and take precedence in the case of name +collisions. + +On Unix the variable values are automatically single quoted, allowing +you to supply strings with spaces, without needing to worry about +escaping. As a consequence, string interpolation is disabled: if you +need string interpolation, you can apply it in the +pre_install/post_install script(s). If you need to include single quotes +in your value, you can escape them by replacing each single quote with +`'''`. + +On Windows, single quotes and double quotes are not supported. + +Note that the # (hash) character cannot be used as it denotes yaml +comments for all platforms. + ### `pre_install` _required:_ no
diff --git a/constructor/construct.py b/constructor/construct.py index 75af79ea1..ea246d400 100644 --- a/constructor/construct.py +++ b/constructor/construct.py @@ -276,6 +276,26 @@ Defaults to `${NAME} ${VERSION} (Python ${PYVERSION} ${ARCH})`. '''), + ('script_env_variables', False, (dict,), ''' +Dictionary of additional environment variables to be made available to +the pre_install and post_install scripts, in the form of VAR:VALUE +pairs. These environment variables are in addition to those in the +`post_install` section above and take precedence in the case of name +collisions. + +On Unix the variable values are automatically single quoted, allowing +you to supply strings with spaces, without needing to worry about +escaping. As a consequence, string interpolation is disabled: if you +need string interpolation, you can apply it in the +pre_install/post_install script(s). If you need to include single quotes +in your value, you can escape them by replacing each single quote with +`'\''`. + +On Windows, single quotes and double quotes are not supported. + +Note that the # (hash) character cannot be used as it denotes yaml +comments for all platforms. +'''), ('pre_install', False, str, ''' Path to a pre-install script, run after the package cache has been set, but before the files are linked to their final locations. As a result, you should diff --git a/constructor/header.sh b/constructor/header.sh index 9fd254daa..48db69a8f 100644 --- a/constructor/header.sh +++ b/constructor/header.sh @@ -23,6 +23,8 @@ fi # Export variables to make installer metadata available to pre/post install scripts # NOTE: If more vars are added, make sure to update the examples/scripts tests too + +_SCRIPT_ENV_VARIABLES_='' # Templated extra environment variable(s) export INSTALLER_NAME='__NAME__' export INSTALLER_VER='__VERSION__' export INSTALLER_PLAT='__PLAT__' @@ -266,10 +268,10 @@ __LICENSE__ EOF printf "\\n" printf "Do you accept the license terms? [yes|no]\\n" - printf "[no] >>> " + printf ">>> " read -r ans ans=$(echo "${ans}" | tr '[:lower:]' '[:upper:]') - while [ "$ans" != "YES" ] && [ "$ans" != "NO" ] && [ "$ans" != "" ] + while [ "$ans" != "YES" ] && [ "$ans" != "NO" ] do printf "Please answer 'yes' or 'no':'\\n" printf ">>> " @@ -546,8 +548,14 @@ if [ "$BATCH" = "0" ]; then #if has_conda and initialize_conda is True # Interactive mode. - printf "Do you wish the installer to initialize %s\\n" "${INSTALLER_NAME}" - printf "by running conda init? [yes|no]\\n" + printf "Do you wish to update your shell profile to automatically initialize conda?\\n" + printf "This will activate conda on startup and change the command prompt when activated.\\n" + printf "If you'd prefer that conda's base environment not be activated on startup,\\n" + printf " run the following command when conda is activated:\\n" + printf "\\n" + printf "conda config --set auto_activate_base false\\n" + printf "\\n" + printf "You can undo this by running \`conda init --reverse \$SHELL\`? [yes|no]\\n" printf "[%s] >>> " "$DEFAULT" read -r ans if [ "$ans" = "" ]; then @@ -580,11 +588,6 @@ if [ "$BATCH" = "0" ]; then esac fi fi - printf "If you'd prefer that conda's base environment not be activated on startup, \\n" - printf " set the auto_activate_base parameter to false: \\n" - printf "\\n" - printf "conda config --set auto_activate_base false\\n" - printf "\\n" #endif printf "Thank you for installing %s!\\n" "${INSTALLER_NAME}" diff --git a/constructor/nsis/main.nsi.tmpl b/constructor/nsis/main.nsi.tmpl index c4897dac4..a2ae2fb28 100644 --- a/constructor/nsis/main.nsi.tmpl +++ b/constructor/nsis/main.nsi.tmpl @@ -1113,6 +1113,7 @@ Section "Install" File /nonfatal /r __INDEX_CACHE__ File /r __REPODATA_RECORD__ + @SCRIPT_ENV_VARIABLES@ System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_SAFETY_CHECKS", "disabled").r0' System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_EXTRA_SAFETY_CHECKS", "no").r0' System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_PKGS_DIRS", "$INSTDIR\pkgs")".r0' diff --git a/constructor/osx/run_user_script.sh b/constructor/osx/run_user_script.sh index 53bd4501a..9f6291518 100644 --- a/constructor/osx/run_user_script.sh +++ b/constructor/osx/run_user_script.sh @@ -31,6 +31,7 @@ export INSTALLER_VER="__VERSION__" export INSTALLER_PLAT="__PLAT__" export INSTALLER_TYPE="PKG" export PRE_OR_POST="__PRE_OR_POST__" +_SCRIPT_ENV_VARIABLES_='' # Templated extra environment variable(s) # Run user-provided script if [ -f "$PREFIX/pkgs/user_${PRE_OR_POST}" ]; then diff --git a/constructor/osxpkg.py b/constructor/osxpkg.py index 5758ab0a9..8c72638c7 100644 --- a/constructor/osxpkg.py +++ b/constructor/osxpkg.py @@ -6,14 +6,21 @@ from os.path import abspath, dirname, exists, isdir, join from pathlib import Path from plistlib import dump as plist_dump -from subprocess import check_call from tempfile import NamedTemporaryFile from . import preconda from .conda_interface import conda_context from .construct import ns_platform, parse from .imaging import write_images -from .utils import add_condarc, approx_size_kb, fill_template, get_final_channels, preprocess, rm_rf +from .utils import ( + add_condarc, + approx_size_kb, + explained_check_call, + fill_template, + get_final_channels, + preprocess, + rm_rf, +) OSX_DIR = join(dirname(__file__), "osx") CACHE_DIR = PACKAGE_ROOT = PACKAGES_DIR = SCRIPTS_DIR = None @@ -224,10 +231,13 @@ def modify_xml(xml_path, info): 'initialize_by_default', True) else 'false') path_choice.set('title', "Add conda initialization to the shell") path_description = """ - If this box is checked, "conda init" will be executed to ensure that - conda is available in your preferred shell upon startup. If unchecked, - you must this initialization yourself or activate the environment - manually for each shell in which you wish to use it.""" + If this box is checked, conda will be automatically activated in your + preferred shell on startup. This will change the command prompt when + activated. If your prefer that conda's base environment not be activated + on startup, run `conda config --set auto_activate_base false`. You can + undo this by running `conda init --reverse ${SHELL}`. + If unchecked, you must this initialization yourself or activate the + environment manually for each shell in which you wish to use it.""" path_choice.set('description', ' '.join(path_description.split())) elif ident.endswith('cacheclean'): path_choice.set('visible', 'true') @@ -299,8 +309,12 @@ def move_script(src, dst, info, ensure_shebang=False, user_script_type=None): 'REGISTER_ENVS': str(info.get("register_envs", True)).lower(), } data = preprocess(data, ppd) + custom_variables = info.get('script_env_variables', {}) data = fill_template(data, replace) + data = data.replace("_SCRIPT_ENV_VARIABLES_=''", '\n'.join( + [f"export {key}='{value}'" for key, value in custom_variables.items()])) + with open(dst, 'w') as fo: if ( ensure_shebang @@ -340,7 +354,7 @@ def pkgbuild(name, identifier=None, version=None, install_location=None): args += ["--install-location", install_location] output = os.path.join(PACKAGES_DIR, f"{name}.pkg") args += [output] - check_call(args) + explained_check_call(args) return output @@ -360,7 +374,7 @@ def pkgbuild_prepare_installation(info): # set to the sum of the compressed tarballs, which is not representative try: # expand to apply patches - check_call(["pkgutil", "--expand", pkg, f"{pkg}.expanded"]) + explained_check_call(["pkgutil", "--expand", pkg, f"{pkg}.expanded"]) payload_xml = os.path.join(f"{pkg}.expanded", "PackageInfo") tree = ET.parse(payload_xml) root = tree.getroot() @@ -368,7 +382,7 @@ def pkgbuild_prepare_installation(info): payload.set("installKBytes", str(approx_pkgs_size_kb)) tree.write(payload_xml) # repack - check_call(["pkgutil", "--flatten", f"{pkg}.expanded", pkg]) + explained_check_call(["pkgutil", "--flatten", f"{pkg}.expanded", pkg]) return pkg finally: shutil.rmtree(f"{pkg}.expanded") @@ -461,7 +475,7 @@ def create(info, verbose=False): "com.apple.security.cs.allow-dyld-environment-variables": True, } plist_dump(plist, f) - check_call( + explained_check_call( [ # hardcode to system location to avoid accidental clobber in PATH "/usr/bin/codesign", @@ -522,11 +536,11 @@ def create(info, verbose=False): for name in names: args.extend(['--package', join(PACKAGES_DIR, "%s.pkg" % name)]) args.append(xml_path) - check_call(args) + explained_check_call(args) modify_xml(xml_path, info) identity_name = info.get('signing_identity_name') - check_call([ + explained_check_call([ "/usr/bin/productbuild", "--distribution", xml_path, "--package-path", PACKAGES_DIR, @@ -534,7 +548,7 @@ def create(info, verbose=False): "tmp.pkg" if identity_name else info['_outpath'] ]) if identity_name: - check_call([ + explained_check_call([ # hardcode to system location to avoid accidental clobber in PATH '/usr/bin/productsign', '--sign', identity_name, "tmp.pkg", diff --git a/constructor/shar.py b/constructor/shar.py index b725da430..513c6bbf4 100644 --- a/constructor/shar.py +++ b/constructor/shar.py @@ -93,8 +93,11 @@ def get_header(conda_exec, tarball, info): data = read_header_template() data = preprocess(data, ppd) + custom_variables = info.get('script_env_variables', {}) data = fill_template(data, replace) + data = data.replace("_SCRIPT_ENV_VARIABLES_=''", '\n'.join( + [f"export {key}='{value}'" for key, value in custom_variables.items()])) return data diff --git a/constructor/utils.py b/constructor/utils.py index 37eedb41d..b763fab9e 100644 --- a/constructor/utils.py +++ b/constructor/utils.py @@ -12,12 +12,21 @@ from os import sep, unlink from os.path import basename, isdir, isfile, islink, normpath from shutil import rmtree +from subprocess import check_call from ruamel import yaml logger = logging.getLogger(__name__) +def explained_check_call(args): + """ + Execute a system process and debug the invocation + """ + logger.debug("Executing: %s", " ".join(args)) + return check_call(args) + + def filename_dist(dist): """ Return the filename of a distribution. """ if hasattr(dist, 'to_filename'): diff --git a/constructor/winexe.py b/constructor/winexe.py index c17e852a9..3ea7c47b8 100644 --- a/constructor/winexe.py +++ b/constructor/winexe.py @@ -87,6 +87,23 @@ def insert_tempfiles_commands(paths: os.PathLike) -> List[str]: return lines +def setup_script_env_variables(info) -> List[str]: + """Helper function to insert extra environment variables into nsis template. + + Args: + info: Dictionary of information parsed from construct.yaml + + Returns: + List[str]: Commands to be inserted into nsi template + """ + lines = [] + for name, value in info.get('script_env_variables', {}).items(): + lines.append( + "System::Call 'kernel32::SetEnvironmentVariable(t,t)i" + + f"""("{name}", {str_esc(value)}).r0'""") + return lines + + def custom_nsi_insert_from_file(filepath: os.PathLike) -> str: """Insert NSI script commands from file. @@ -336,6 +353,7 @@ def make_nsi(info, dir_path, extra_files=None, temp_extra_files=None): '${NAME} ${VERSION} (Python ${PYVERSION} ${ARCH})' )), ('@EXTRA_FILES@', '\n '.join(extra_files_commands(extra_files, dir_path))), + ('@SCRIPT_ENV_VARIABLES@', '\n '.join(setup_script_env_variables(info))), ( '@CUSTOM_WELCOME_FILE@', custom_nsi_insert_from_file(info.get('welcome_file', '')) diff --git a/docs/source/construct-yaml.md b/docs/source/construct-yaml.md index 1bf559977..7440b3967 100644 --- a/docs/source/construct-yaml.md +++ b/docs/source/construct-yaml.md @@ -388,6 +388,30 @@ _type:_ string
Application name in the Windows "Programs and Features" control panel. Defaults to `${NAME} ${VERSION} (Python ${PYVERSION} ${ARCH})`. +### `script_env_variables` + +_required:_ no
+_type:_ dictionary
+ +Dictionary of additional environment variables to be made available to +the pre_install and post_install scripts, in the form of VAR:VALUE +pairs. These environment variables are in addition to those in the +`post_install` section above and take precedence in the case of name +collisions. + +On Unix the variable values are automatically single quoted, allowing +you to supply strings with spaces, without needing to worry about +escaping. As a consequence, string interpolation is disabled: if you +need string interpolation, you can apply it in the +pre_install/post_install script(s). If you need to include single quotes +in your value, you can escape them by replacing each single quote with +`'''`. + +On Windows, single quotes and double quotes are not supported. + +Note that the # (hash) character cannot be used as it denotes yaml +comments for all platforms. + ### `pre_install` _required:_ no
diff --git a/examples/scripts/construct.yaml b/examples/scripts/construct.yaml index 97d64e197..e57bdffa0 100644 --- a/examples/scripts/construct.yaml +++ b/examples/scripts/construct.yaml @@ -5,6 +5,14 @@ channels: - http://repo.anaconda.com/pkgs/main/ specs: - python + +script_env_variables: + CUSTOM_VARIABLE_1: FIR$T-CUSTOM_'\''STRING'\'' WITH SPACES AND @*! "CHARACTERS" # [not win] + CUSTOM_VARIABLE_2: $ECOND-CUSTOM_'\''STRING'\'' WITH SPACES AND @*! "CHARACTERS" # [not win] + CUSTOM_VARIABLE_1: FIR$T-CUSTOM_STRING WITH SPACES AND @*! CHARACTERS # [win] + CUSTOM_VARIABLE_2: $ECOND-CUSTOM_STRING WITH SPACES AND @*! CHARACTERS # [win] + + pre_install: pre_install.sh # [unix] pre_install: pre_install.bat # [win] pre_install_desc: "Adding this description makes the script selectable in the UI" diff --git a/examples/scripts/post_install.bat b/examples/scripts/post_install.bat index 6b80f986a..7a435cafc 100644 --- a/examples/scripts/post_install.bat +++ b/examples/scripts/post_install.bat @@ -4,4 +4,6 @@ if not "%INSTALLER_VER%" == "X" exit 1 if not "%INSTALLER_PLAT%" == "win-64" exit 1 if not "%INSTALLER_TYPE%" == "EXE" exit 1 if "%PREFIX%" == "" exit 1 +if not "%CUSTOM_VARIABLE_1%" == "FIR$T-CUSTOM_STRING WITH SPACES AND @*! CHARACTERS" exit 1 +if not "%CUSTOM_VARIABLE_2%" == "$ECOND-CUSTOM_STRING WITH SPACES AND @*! CHARACTERS" exit 1 if not exist "%PREFIX%\pre_install_sentinel.txt" exit 1 diff --git a/examples/scripts/post_install.sh b/examples/scripts/post_install.sh index 54d2b268e..1db67d136 100644 --- a/examples/scripts/post_install.sh +++ b/examples/scripts/post_install.sh @@ -9,10 +9,17 @@ echo "INSTALLER_NAME=${INSTALLER_NAME}" echo "INSTALLER_VER=${INSTALLER_VER}" echo "INSTALLER_PLAT=${INSTALLER_PLAT}" echo "INSTALLER_TYPE=${INSTALLER_TYPE}" +echo "CUSTOM_VARIABLE_1=${CUSTOM_VARIABLE_1}" +echo "CUSTOM_VARIABLE_2=${CUSTOM_VARIABLE_2}" echo "PREFIX=${PREFIX}" test "${INSTALLER_NAME}" = "Scripts" test "${INSTALLER_VER}" = "X" +# shellcheck disable=SC2016 # String interpolation disabling is deliberate +test "${CUSTOM_VARIABLE_1}" = 'FIR$T-CUSTOM_'\''STRING'\'' WITH SPACES AND @*! "CHARACTERS"' +# shellcheck disable=SC2016 # String interpolation disabling is deliberate +test "${CUSTOM_VARIABLE_2}" = '$ECOND-CUSTOM_'\''STRING'\'' WITH SPACES AND @*! "CHARACTERS"' + if [[ $(uname -s) == Linux ]]; then if [[ ${INSTALLER_PLAT} != linux-* ]]; then exit 1 diff --git a/examples/scripts/pre_install.bat b/examples/scripts/pre_install.bat index d1bd0598a..5ece67c31 100644 --- a/examples/scripts/pre_install.bat +++ b/examples/scripts/pre_install.bat @@ -3,4 +3,6 @@ if not "%INSTALLER_VER%" == "X" exit 1 if not "%INSTALLER_PLAT%" == "win-64" exit 1 if not "%INSTALLER_TYPE%" == "EXE" exit 1 if "%PREFIX%" == "" exit 1 +if not "%CUSTOM_VARIABLE_1%" == "FIR$T-CUSTOM_STRING WITH SPACES AND @*! CHARACTERS" exit 1 +if not "%CUSTOM_VARIABLE_2%" == "$ECOND-CUSTOM_STRING WITH SPACES AND @*! CHARACTERS" exit 1 echo Added by pre-install script > "%PREFIX%\pre_install_sentinel.txt" diff --git a/examples/scripts/pre_install.sh b/examples/scripts/pre_install.sh index a865dcc0e..df0806980 100644 --- a/examples/scripts/pre_install.sh +++ b/examples/scripts/pre_install.sh @@ -6,10 +6,17 @@ echo "INSTALLER_NAME=${INSTALLER_NAME}" echo "INSTALLER_VER=${INSTALLER_VER}" echo "INSTALLER_PLAT=${INSTALLER_PLAT}" echo "INSTALLER_TYPE=${INSTALLER_TYPE}" +echo "CUSTOM_VARIABLE_1=${CUSTOM_VARIABLE_1}" +echo "CUSTOM_VARIABLE_2=${CUSTOM_VARIABLE_2}" echo "PREFIX=${PREFIX}" test "${INSTALLER_NAME}" = "Scripts" test "${INSTALLER_VER}" = "X" +# shellcheck disable=SC2016 # String interpolation disabling is deliberate +test "${CUSTOM_VARIABLE_1}" = 'FIR$T-CUSTOM_'\''STRING'\'' WITH SPACES AND @*! "CHARACTERS"' +# shellcheck disable=SC2016 # String interpolation disabling is deliberate +test "${CUSTOM_VARIABLE_2}" = '$ECOND-CUSTOM_'\''STRING'\'' WITH SPACES AND @*! "CHARACTERS"' + if [[ $(uname -s) == Linux ]]; then if [[ ${INSTALLER_PLAT} != linux-* ]]; then exit 1 diff --git a/news/713-update-conda-init-wording b/news/713-update-conda-init-wording new file mode 100644 index 000000000..c1061b881 --- /dev/null +++ b/news/713-update-conda-init-wording @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* Clarify consequences for when `conda init` is run during the installation. (#713) diff --git a/news/718-script_env_variables-option b/news/718-script_env_variables-option new file mode 100644 index 000000000..9ff8e5f67 --- /dev/null +++ b/news/718-script_env_variables-option @@ -0,0 +1,21 @@ +### Enhancements + +* Added support for `script_env_variables` allowing specification of + environment variables in the `construct.yaml` for use by pre- and + post-install scripts. + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/news/722-license-yn b/news/722-license-yn new file mode 100644 index 000000000..3c74bb242 --- /dev/null +++ b/news/722-license-yn @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* Fix a regression in the license prompt on SH installers to require a explicit answer instead of defaulting to `no` on Enter. (#721 via #722) + +### Deprecations + +* + +### Docs + +* + +### Other + +*