Skip to content

Commit

Permalink
Expand Jinja templating (#922)
Browse files Browse the repository at this point in the history
* Replace remaining template variables with Jinja2

* Convert custom example NSIS template to jinja2

* Replace Python code with jinja2 for sh installers

* Replace custom python code with jinja2 fro pkg installers

* Replace custom Python code with jinja2 for exe installers

* Skip sentinel file checks for pkg installers if not on CI

* Set micromamba channel to conda-forge explicitly

* Improve formatting

* Add news item

* Fix unit test inputs

* Add missing sh template variables

* Rename Windows script variable name
  • Loading branch information
marcoesters authored Jan 10, 2025
1 parent 4b35ca7 commit 0c80f9b
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 272 deletions.
9 changes: 7 additions & 2 deletions constructor/header.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ 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 }}
{%- for key, val in script_env_variables|items %}
export {{ key }}='{{ val }}'
{%- endfor %}
export INSTALLER_NAME='{{ installer_name }}'
export INSTALLER_VER='{{ installer_version }}'
export INSTALLER_PLAT='{{ installer_platform }}'
Expand Down Expand Up @@ -529,6 +531,7 @@ shortcuts="--no-shortcuts"
shortcuts=""
{%- endif %}
{%- set channels = final_channels|join(",") %}
# shellcheck disable=SC2086
CONDA_ROOT_PREFIX="$PREFIX" \
CONDA_REGISTER_ENVS="{{ register_envs }}" \
Expand Down Expand Up @@ -582,7 +585,9 @@ for env_pkgs in "${PREFIX}"/pkgs/envs/*/; do
done
{%- endif %}
{{ install_commands }}
{%- for condarc in write_condarc %}
{{ condarc }}
{%- endfor %}
POSTCONDA="$PREFIX/postconda.tar.bz2"
CONDA_QUIET="$BATCH" \
Expand Down
149 changes: 134 additions & 15 deletions constructor/nsis/main.nsi.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ var /global StdOutHandleSet
!define ARCH {{ arch }}
!define PLATFORM {{ installer_platform }}
!define CONSTRUCTOR_VERSION {{ constructor_version }}
!define PY_VER {{ py_ver }}
!define PYVERSION_JUSTDIGITS {{ pyversion_justdigits }}
!define PYVERSION {{ pyversion }}
!define PYVERSION_MAJOR {{ pyversion_major }}
!define PY_VER {{ pyver_components[:2] | join(".") }}
!define PYVERSION_JUSTDIGITS {{ pyver_components | join("") }}
!define PYVERSION {{ pyver_components | join(".") }}
!define PYVERSION_MAJOR {{ pyver_components[0] }}
!define DEFAULT_PREFIX {{ default_prefix }}
!define DEFAULT_PREFIX_DOMAIN_USER {{ default_prefix_domain_user }}
!define DEFAULT_PREFIX_ALL_USERS {{ default_prefix_all_users }}
Expand Down Expand Up @@ -191,9 +191,9 @@ Page Custom InstModePage_Create InstModePage_Leave
Page Custom mui_AnaCustomOptions_Show
!insertmacro MUI_PAGE_INSTFILES

{%- if post_install_pages %}
{{ POST_INSTALL_PAGES }}
{%- endif %}
{%- for page in POST_INSTALL_PAGES %}
{{ page }}
{%- endfor %}

{%- if with_conclusion_text %}
!define MUI_FINISHPAGE_TITLE {{ conclusion_title }}
Expand Down Expand Up @@ -557,7 +557,12 @@ Function .onInit
Push $R2

InitPluginsDir
{{ TEMP_EXTRA_FILES }}
{%- if TEMP_EXTRA_FILES | length != 0 %}
SetOutPath $PLUGINSDIR
{%- for file in TEMP_EXTRA_FILES %}
File {{ file }}
{%- endfor %}
{%- endif %}
!insertmacro ParseCommandLineArgs

# Select the correct registry to look at, depending
Expand Down Expand Up @@ -1260,8 +1265,12 @@ Section "Install"
File {{ conda_exe }}
File {{ pre_uninstall }}

# Copy extra files (code generated on winexe.py)
{{ EXTRA_FILES }}
{%- for path, files in extra_files | items %}
SetOutPath {{ path }}
{%- for file in files %}
File {{ file }}
{%- endfor %}
{%- endfor %}

${If} $InstMode = ${JUST_ME}
SetOutPath "$INSTDIR"
Expand All @@ -1279,7 +1288,10 @@ Section "Install"
File /nonfatal /r {{ index_cache }}
File /r {{ repodata_record }}

{{ SCRIPT_ENV_VARIABLES }}

{%- for key, escaped_val in SCRIPT_ENV_VARIABLES | items %}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("{{ key }}", {{ escaped_val }}).r0'
{%- endfor %}
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_ROOT_PREFIX", "$INSTDIR")".r0'
Expand Down Expand Up @@ -1314,7 +1326,9 @@ Section "Install"
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_SOLVER", "").r0'
${EndIf}

{{ PKG_COMMANDS }}
{%- for dist in DISTS %}
File {{ dist }}
{%- endfor %}

SetDetailsPrint TextOnly
${Print} "Setting up the package cache..."
Expand Down Expand Up @@ -1344,9 +1358,53 @@ Section "Install"
call AbortRetryNSExecWait
NoPreInstall:

{{ SETUP_ENVS }}
{%- for env in SETUP_ENVS %}
{%- set channels = env.final_channels|join(",") %}
# Set up {{ env.name }} env
SetDetailsPrint both
${Print} "Setting up the {{ env.name }} environment..."
SetDetailsPrint listonly

# List of packages to install
SetOutPath "{{ env.env_txt_dir }}"
File "{{ env.env_txt_abspath }}"

# A conda-meta\history file is required for a valid conda prefix
SetOutPath "{{ env.conda_meta }}"
File "{{ env.history_abspath }}"

# Set channels
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_CHANNELS", "{{ channels }}").r0'
# Set register_envs
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_REGISTER_ENVS", "{{ env.register_envs }}").r0'

# Run conda install
${If} $Ana_CreateShortcuts_State = ${BST_CHECKED}
${Print} "Installing packages for {{ env.name }}, creating shortcuts if necessary..."
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.env_txt }}" {{ env.shortcuts }} {{ env.no_rcs_arg }}'
${Else}
${Print} "Installing packages for {{ env.name }}..."
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.env_txt }}" --no-shortcuts {{ env.no_rcs_arg }}'
${EndIf}
push 'Failed to link extracted packages to {{ env.prefix }}!'
push 'WithLog'
SetDetailsPrint listonly
call AbortRetryNSExecWait
SetDetailsPrint both

# Cleanup {{ env.name }} env.txt
SetOutPath "$INSTDIR"
Delete "{{ env.env_txt }}"

# Restore shipped conda-meta\history for remapped
# channels and retain only the first transaction
SetOutPath "{{ env.conda_meta }}"
File "{{ env.history_abspath }}"
{%- endfor %}

{{ WRITE_CONDARC }}
{%- for condarc in WRITE_CONDARC %}
{{ condarc }}
{%- endfor %}

AddSize {{ SIZE }}

Expand Down Expand Up @@ -1507,7 +1565,68 @@ Section "Uninstall"
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("INSTALLER_UNATTENDED", "0").r0'
${EndIf}

{{ UNINSTALL_COMMANDS }}
{%- if uninstall_with_conda_exe %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"

# Parse arguments
StrCpy $R0 ""

${If} $UninstRemoveConfigFiles_User_State == ${BST_CHECKED}
${If} $UninstRemoveConfigFiles_System_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-config-files=all"
${Else}
StrCpy $R0 "$R0 --remove-config-files=user"
${EndIf}
${ElseIf} $UninstRemoveConfigFiles_System_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-config-files=system"
${EndIf}

${If} $UninstRemoveUserData_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-user-data"
${EndIf}

${If} $UninstRemoveCaches_State == ${BST_CHECKED}
StrCpy $R0 "$R0 --remove-caches"
${EndIf}

${Print} "Removing files and folders..."
push '"$INSTDIR\_conda.exe" constructor uninstall $R0 --prefix "$INSTDIR"'
push 'Failed to remove files and folders. Please see the log for more information.'
push 'WithLog'
SetDetailsPrint listonly
call un.AbortRetryNSExecWait
SetDetailsPrint both

# The uninstallation may leave the install.log, the uninstaller,
# and .conda_trash files behind, so remove those manually.
${If} ${FileExists} "$INSTDIR"
RMDir /r /REBOOTOK "$INSTDIR"
${EndIf}
{%- else %}
{%- for env in SETUP_ENVS | reverse %}
{%- set subdir = ("\envs\%(name)s" | format(name=env.name)) if env.name != "base" else "" %}
SetDetailsPrint both
${Print} "Deleting ${NAME} menus in {{ env.name }}..."
SetDetailsPrint listonly
push '"$INSTDIR\_conda.exe" constructor --prefix "$INSTDIR{{ subdir }}" --rm-menus'
push 'Failed to delete menus in {{ env.name }}'
push 'WithLog'
call un.AbortRetryNSExecWait
SetDetailsPrint both
{%- endfor %}
!insertmacro AbortRetryNSExecWaitLibNsisCmd "pre_uninstall"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmpath"
!insertmacro AbortRetryNSExecWaitLibNsisCmd "rmreg"

${Print} "Removing files and folders..."
nsExec::Exec 'cmd.exe /D /C RMDIR /Q /S "$INSTDIR"'

# In case the last command fails, run the slow method to remove leftover
RMDir /r /REBOOTOK "$INSTDIR"

{%- endif %}

${If} $INSTALLER_NAME_FULL != ""
DeleteRegKey SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$INSTALLER_NAME_FULL"
Expand Down
4 changes: 2 additions & 2 deletions constructor/osx/checks_before_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ if [[ -e "$PREFIX" ]]; then

# By default, osascript doesn't allow user interaction, so we have to work
# around it. http://stackoverflow.com/a/11874852/161801
logger -p "install.info" "ERROR: __PATH_EXISTS_ERROR_TEXT__" || echo "ERROR: __PATH_EXISTS_ERROR_TEXT__"
logger -p "install.info" "ERROR: {{ path_exists_error_text }}" || echo "ERROR: {{ path_exists_error_text }}"
(osascript -e "try
tell application (path to frontmost application as text)
set theAlertText to \"Chosen path already exists!\"
set theAlertMessage to \"__PATH_EXISTS_ERROR_TEXT__\"
set theAlertMessage to \"{{ path_exists_error_text }}\"
display alert theAlertText message theAlertMessage as critical buttons {\"OK\"} default button {\"OK\"}
end
activate app (path to frontmost application as text)
Expand Down
8 changes: 6 additions & 2 deletions constructor/osx/run_installation.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ notify() {
# shellcheck disable=SC2050
{%- if progress_notifications %}
osascript <<EOF
display notification "$1" with title "📦 Install __NAME__ __VERSION__"
display notification "$1" with title "📦 Install {{ installer_name }} {{ installer_version }}"
EOF
{%- endif %}
logger -p "install.info" "$1" || echo "$1"
}

{%- set channels = final_channels|join(",") %}

unset DYLD_LIBRARY_PATH

PREFIX="$2/{{ pkg_name_lower }}"
Expand Down Expand Up @@ -103,7 +105,9 @@ done
# Cleanup!
find "$PREFIX/pkgs" -type d -empty -exec rmdir {} \; 2>/dev/null || :

{{ write_condarc }}
{%- for condarc in write_condarc %}
{{ condarc }}
{%- endfor %}

if ! "$PREFIX/bin/python" -V; then
echo "ERROR running Python"
Expand Down
6 changes: 4 additions & 2 deletions constructor/osx/run_user_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ notify() {
# shellcheck disable=SC2050
{%- if progress_notifications %}
osascript <<EOF
display notification "$1" with title "📦 Install __NAME__ __VERSION__"
display notification "$1" with title "📦 Install {{ installer_name }} {{ installer_version }}"
EOF
{%- endif %}
logger -p "install.info" "$1" || echo "$1"
Expand Down Expand Up @@ -39,7 +39,9 @@ if [[ "${INSTALLER_UNATTENDED}" != "0" ]]; then
INSTALLER_UNATTENDED="1"
fi
export PRE_OR_POST="{{ pre_or_post }}"
{{ script_env_variables }}
{%- for key, val in script_env_variables|items %}
export {{ key }}='{{ val }}'
{%- endfor %}

# Run user-provided script
if [ -f "$PREFIX/pkgs/user_${PRE_OR_POST}" ]; then
Expand Down
7 changes: 3 additions & 4 deletions constructor/osxpkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ def move_script(src, dst, info, ensure_shebang=False, user_script_type=None):
variables["installer_name"] = info['name']
variables["installer_version"] = info['version']
variables["installer_platform"] = info['_platform']
variables["channels"] = ','.join(get_final_channels(info))
variables["write_condarc"] = '\n'.join(add_condarc(info))
variables["final_channels"] = get_final_channels(info)
variables["write_condarc"] = list(add_condarc(info))
variables["path_exists_error_text"] = path_exists_error_text
variables["progress_notifications"] = info.get('progress_notifications', False)
variables["pre_or_post"] = user_script_type or '__PRE_OR_POST__'
Expand All @@ -346,8 +346,7 @@ def move_script(src, dst, info, ensure_shebang=False, user_script_type=None):
variables["register_envs"] = str(info.get("register_envs", True)).lower()
variables["virtual_specs"] = shlex.join(virtual_specs)
variables["no_rcs_arg"] = info.get('_ignore_condarcs_arg', '')
variables["script_env_variables"] = '\n'.join(
[f"export {key}='{value}'" for key, value in info.get('script_env_variables', {}).items()])
variables["script_env_variables"] = info.get('script_env_variables', {})

data = render_template(data, **variables)

Expand Down
8 changes: 3 additions & 5 deletions constructor/shar.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ def get_header(conda_exec, tarball, info):
variables['has_conda'] = info['_has_conda']
variables['enable_shortcuts'] = str(info['_enable_shortcuts']).lower()
variables['check_path_spaces'] = info.get("check_path_spaces", True)
install_lines = list(add_condarc(info))
# Omit __osx and __glibc because those are tested with shell code direcly
virtual_specs = [
spec
Expand All @@ -87,8 +86,8 @@ def get_header(conda_exec, tarball, info):
variables['default_prefix'] = info.get('default_prefix', '${HOME:-/opt}/%s' % name.lower())
variables['first_payload_size'] = getsize(conda_exec)
variables['second_payload_size'] = getsize(tarball)
variables['install_commands'] = '\n'.join(install_lines)
variables['channels'] = ','.join(get_final_channels(info))
variables['write_condarc'] = list(add_condarc(info))
variables['final_channels'] = get_final_channels(info)
variables['conclusion_text'] = info.get("conclusion_text", "installation finished.")
variables['pycache'] = '__pycache__'
variables['shortcuts'] = shortcuts_flags(info)
Expand All @@ -105,8 +104,7 @@ def get_header(conda_exec, tarball, info):
min_glibc_version = virtual_specs.get("__glibc", {}).get("min") or ""
variables['min_glibc_version'] = min_glibc_version

variables['script_env_variables'] = '\n'.join(
[f"export {key}='{value}'" for key, value in info.get('script_env_variables', {}).items()])
variables['script_env_variables'] = info.get('script_env_variables', {})

return render_template(read_header_template(), **variables)

Expand Down
Loading

0 comments on commit 0c80f9b

Please sign in to comment.