Skip to content

Commit

Permalink
Bash on windows now supports modifying existing env vars
Browse files Browse the repository at this point in the history
- Add function to convert a single windows file path to a cygwin compatible one
- Env var paths are converted to cygwin compatible paths and uses ':' for
the pathsep to ensure proper conversion
- Check that we can include spaces in env var paths
  • Loading branch information
MHendricks committed Apr 20, 2023
1 parent 040d2b6 commit d9b58ea
Show file tree
Hide file tree
Showing 32 changed files with 254 additions and 72 deletions.
5 changes: 2 additions & 3 deletions hab/formatter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import string

from . import utils
Expand Down Expand Up @@ -38,7 +39,7 @@ class Formatter(string.Formatter):
# Using bash on windows
'shwin': {
'env_var': '${}',
';': ';',
';': ':',
},
# Delay the format for future calls. This allows us to process the environment
# without changing these, and then when write_script is called the target
Expand Down Expand Up @@ -96,8 +97,6 @@ def merge_kwargs(self, kwargs):
still allows us to override them if required
"""
ret = dict(self.shell_formats[self.language], **kwargs)
import os

ret = dict(os.environ, **ret)
return ret

Expand Down
4 changes: 2 additions & 2 deletions hab/templates/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export PS1="[{{ hab_cfg.uri }}] $PS1"
# Setting global environment variables:
{% for key, value in hab_cfg.environment.items() %}
{% if value %}
{% set value = utils.Platform.collapse_paths(value) %}
{% set value = utils.Platform.collapse_paths(value, ext=ext) %}
{% set value = formatter.format(value, key=key, value=value) %}
export {{ key }}="{{ value }}"
{% else %}
Expand All @@ -27,7 +27,7 @@ function {{ alias }}() {
hab_bac_{{ alias_norm }}=`export -p`
{% for k, v in alias_env.items() %}
{% if v %}
{% set v = utils.Platform.collapse_paths(v) %}
{% set v = utils.Platform.collapse_paths(v, ext=ext) %}
{% set v = formatter.format(v, key=key, value=v) %}
export {{ k }}="{{ v }}"
{% else %}
Expand Down
70 changes: 67 additions & 3 deletions hab/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import errno
import json as _json
import ntpath
import os
import re
import sys
Expand Down Expand Up @@ -31,6 +32,50 @@ class _JsonException(BaseException):
"""A regex that can be used to check if a string is a single windows file path."""


def cygpath(path, spaces=False):
"""Convert a windows path to a cygwin compatible path. For example `c:\\test`
converts to `/c/test`. This also works for unc file paths.
Note: This doesn't use cygpath.exe for greater portability. Ie its accessible
even if the current shell doesn't have access to cygpath.exe. It also doesn't
require extra suprocess calls.
Args:
path (str): The file path to convert.
spaces (bool, optional): Add a backslash before every non-escaped space.
"""
path = str(path)

# Escape spaces and convert any remaining backslashes to forward slashes
def process_separator(match):
sep = path[match.start() : match.end()]
slash_count = sep.count('\\')
if " " not in sep:
# It's not a space, simply replace with forward-slash
return sep.replace('\\', '/')
# Treat odd numbers of slashes as already escaped not directories.
elif not spaces or slash_count % 2:
return sep
# Add a backslash to escape spaces if enabled
return sep.replace(' ', '\\ ')

pattern = (
# Capture spaces including any leading backslashes to escape
r"(\\* )"
# If we can't find any spaces, capture backslashes to convert to forward-slash
r"|(\\+)"
)
path = re.sub(pattern, process_separator, path)

# Finally, convert `C:\` drive specifier to `/c/`. Unc paths don't need any
# additional processing, just converting \ to / which was done previously.
drive, tail = ntpath.splitdrive(path)
if len(drive) == 2 and drive[1] == ":":
# It's a drive letter
path = f'/{drive[0]}{tail}'
return path


def decode_freeze(txt):
"""Decodes the provided frozen hab string. See `encode_freeze` for
details on how these strings are encoded. These will start with a version
Expand Down Expand Up @@ -320,11 +365,11 @@ def check_name(cls, name):
"""Checks if the provided name is valid for this class"""

@classmethod
def collapse_paths(cls, paths):
def collapse_paths(cls, paths, ext=None):
"""Converts a list of paths into a string compatible with the os."""
if isinstance(paths, str):
return paths
return cls.pathsep().join([str(p) for p in paths])
return cls.pathsep(ext=ext).join([str(p) for p in paths])

@classmethod
def expand_paths(cls, paths):
Expand Down Expand Up @@ -379,7 +424,7 @@ def path_split(cls, path, pathsep=None):
return path.split(pathsep)

@classmethod
def pathsep(cls):
def pathsep(cls, ext=None):
"""The path separator used by this platform."""
return cls._sep

Expand All @@ -401,6 +446,25 @@ class WinPlatform(BasePlatform):
def check_name(cls, name):
return name in ("win32", "windows")

@classmethod
def collapse_paths(cls, paths, ext=None):
"""Converts a list of paths into a string compatible with the os."""
if isinstance(paths, str):
return paths
if ext in (".sh", ""):
paths = [cygpath(p) for p in paths]
else:
paths = [str(p) for p in paths]
return cls.pathsep(ext=ext).join(paths)

@classmethod
def pathsep(cls, ext=None):
"""The path separator used by this platform."""
if ext in (".sh", ""):
# For shwin scripts we should use linux style pathsep
return ":"
return cls._sep


class LinuxPlatform(BasePlatform):
_name = "linux"
Expand Down
4 changes: 2 additions & 2 deletions tests/distros/aliased/2.0/.hab.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"cmd": ["python", "{relative_root}/list_vars.py"],
"environment": {
"append": {
"PATH": ["{PATH!e}", "{relative_root}/test"]
"PATH": ["{PATH!e}", "{relative_root}/PATH/env/with spaces"]
}
}
}
Expand Down Expand Up @@ -60,7 +60,7 @@
"cmd": ["python", "{relative_root}/list_vars.py"],
"environment": {
"append": {
"PATH": ["{PATH!e}", "{relative_root}/test"]
"PATH": ["{PATH!e}", "{relative_root}/PATH/env/with spaces"]
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions tests/distros/aliased/2.0/list_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ def print_var(var):
print_var("ALIASED_GLOBAL_D")
print_var("ALIASED_GLOBAL_E")
print_var("ALIASED_LOCAL")
print("")

print(' PATH env var '.center(80, '-'))
for p in os.environ["PATH"].split(os.path.pathsep):
print(p)

print(''.center(80, '-'))
2 changes: 1 addition & 1 deletion tests/reference_scripts/bat_activate/aliases/inherited.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ REM memory that can be called from the command prompt like in other shells.
REM Set alias specific environment variables that only persist for while
REM this script is running.
SETLOCAL
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/test"
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

REM Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py %*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ REM memory that can be called from the command prompt like in other shells.
REM Set alias specific environment variables that only persist for while
REM this script is running.
SETLOCAL
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/test"
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

REM Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py %*
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/bat_env/aliases/inherited.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ REM memory that can be called from the command prompt like in other shells.
REM Set alias specific environment variables that only persist for while
REM this script is running.
SETLOCAL
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/test"
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

REM Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py %*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ REM memory that can be called from the command prompt like in other shells.
REM Set alias specific environment variables that only persist for while
REM this script is running.
SETLOCAL
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/test"
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

REM Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py %*
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/bat_launch/aliases/inherited.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ REM memory that can be called from the command prompt like in other shells.
REM Set alias specific environment variables that only persist for while
REM this script is running.
SETLOCAL
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/test"
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

REM Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py %*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ REM memory that can be called from the command prompt like in other shells.
REM Set alias specific environment variables that only persist for while
REM this script is running.
SETLOCAL
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/test"
set "PATH=%PATH%;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

REM Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py %*
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/ps1_activate/hab_config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
$hab_bac_inherited = Get-ChildItem env:
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/test"
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py $args
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/ps1_activate_launch/hab_config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
$hab_bac_inherited = Get-ChildItem env:
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/test"
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py $args
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/ps1_env/hab_config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
$hab_bac_inherited = Get-ChildItem env:
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/test"
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py $args
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/ps1_env_launch/hab_config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
$hab_bac_inherited = Get-ChildItem env:
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/test"
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py $args
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/ps1_launch/hab_config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
$hab_bac_inherited = Get-ChildItem env:
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/test"
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py $args
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/ps1_launch_args/hab_config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
$hab_bac_inherited = Get-ChildItem env:
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/test"
$env:PATH="$env:PATH;{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py $args
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/sh_linux_activate/hab_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/sh_linux_env/hab_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/sh_linux_env_launch/hab_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/sh_linux_launch/hab_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
Expand Down
2 changes: 1 addition & 1 deletion tests/reference_scripts/sh_linux_launch_args/hab_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
Expand Down
14 changes: 7 additions & 7 deletions tests/reference_scripts/sh_win_activate/hab_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export PS1="[not_set/child] $PS1"
# Setting global environment variables:
unset UNSET_VARIABLE
export TEST="case"
export FMT_FOR_OS="a;b;c:$PATH;d"
export FMT_FOR_OS="a:b;c:$PATH:d"
unset ALIASED_GLOBAL_E
export ALIASED_GLOBAL_B="Global B"
export ALIASED_GLOBAL_C="Global C"
Expand All @@ -22,7 +22,7 @@ function as_dict() {
export ALIASED_LOCAL="{{ config_root }}/distros/aliased/2.0/test"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
python {{ config_root_alias }}/distros/aliased/2.0/list_vars.py "$@";

# Restore the previous environment without alias specific hab variables by
# removing variables hab added, then restore the original variable values.
Expand All @@ -37,10 +37,10 @@ function inherited() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_inherited=`export -p`
export PATH="$PATH;{{ config_root }}/distros/aliased/2.0/test"
export PATH="$PATH:{{ config_root }}/distros/aliased/2.0/PATH/env/with spaces"

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
python {{ config_root_alias }}/distros/aliased/2.0/list_vars.py "$@";

# Restore the previous environment without alias specific hab variables by
# removing variables hab added, then restore the original variable values.
Expand All @@ -52,7 +52,7 @@ function inherited() {
export -f inherited;

function as_list() {
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
python {{ config_root_alias }}/distros/aliased/2.0/list_vars.py "$@";
}
export -f as_list;

Expand All @@ -65,12 +65,12 @@ function global() {
# Set alias specific environment variables. Backup the previous variable
# value and export status, and add the hab managed variables
hab_bac_global=`export -p`
export ALIASED_GLOBAL_A="Local A Prepend;Global A;Local A Append"
export ALIASED_GLOBAL_A="Local A Prepend:Global A:Local A Append"
export ALIASED_GLOBAL_C="Local C Set"
unset ALIASED_GLOBAL_D

# Run alias command
python {{ config_root }}/distros/aliased/2.0/list_vars.py "$@";
python {{ config_root_alias }}/distros/aliased/2.0/list_vars.py "$@";

# Restore the previous environment without alias specific hab variables by
# removing variables hab added, then restore the original variable values.
Expand Down
Loading

0 comments on commit d9b58ea

Please sign in to comment.