Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand Jinja templating #922

Merged
merged 12 commits into from
Jan 10, 2025
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, val in SCRIPT_ENV_VARIABLES | items %}
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("{{ key }}", {{ val }}).r0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need quotes for val too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok they are pre-escaped in Python. Let's rename this for clarity in the future then:

Suggested change
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("{{ key }}", {{ val }}).r0'
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("{{ key }}", {{ escaped_val }}).r0'

(and in the line above too)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised, too, but we don't do it in the current version either: https://github.com/conda/constructor/blob/main/constructor/winexe.py#L97

{%- 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
Loading