Skip to content

Commit

Permalink
Add Windows support to the custom installer
Browse files Browse the repository at this point in the history
  • Loading branch information
sdispater committed Aug 8, 2018
1 parent 58d8617 commit 1d35987
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 18 deletions.
6 changes: 4 additions & 2 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ install:
# about it being out of date.
- "python -m pip install --disable-pip-version-check --user --upgrade pip"

# Create poetry virtualenv
- "python -m pip install poetry --pre"
# Installing Poetry
- "python get-poetry --preview -y"
# Add Poetry to PATH
- set PATH=%PATH%;C:\Users\appveyor\.poetry\bin

# Install dependencies
- "poetry install -v"
Expand Down
211 changes: 195 additions & 16 deletions get-poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@
pass


try:
try:
import winreg
except ImportError:
import _winreg as winreg
except ImportError:
winreg = None


WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")


Expand Down Expand Up @@ -172,6 +181,8 @@ def expanduser(path):
main()
"""

BAT = "@echo off\r\npython %USERPROFILE%/.poetry/bin/poetry %*\r\n"


PRE_MESSAGE = """# Welcome to {poetry}!
Expand All @@ -189,6 +200,17 @@ def expanduser(path):
and these changes will be reverted.
"""

PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go!
This will uninstall {poetry}.
It will remove the `poetry` command from {poetry}'s bin directory, located at:
{poetry_home_bin}
This will also remove {poetry} from your system's PATH.
"""


PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by
modifying the profile file{plural} located at:
Expand Down Expand Up @@ -279,6 +301,15 @@ def run(self):

return 0

def uninstall(self):
self.display_pre_uninstall_message()

if not self.customize_uninstall():
return

self.remove_home()
self.remove_from_path()

def get_version(self):
print(colorize("info", "Retrieving Poetry metadata"))

Expand Down Expand Up @@ -345,6 +376,31 @@ def _compare_versions(x, y):

return version, current_version

def customize_install(self):
if not self._accept_all:
print("Before we start, please answer the following questions.")
print("You may simple press the Enter key to keave unchanged.")

modify_path = input("Modify PATH variable? ([y]/n) ") or "y"
if modify_path.lower() in {"n", "no"}:
self._modify_path = False

print("")

def customize_uninstall(self):
if not self._accept_all:
print()

uninstall = (
input("Are you sure you want to uninstall Poetry? (y/[n]) ") or "n"
)
if uninstall.lower() not in {"y", "yes"}:
return False

print("")

return True

def customize_install(self):
if not self._accept_all:
print("Before we start, please answer the following questions.")
Expand All @@ -363,6 +419,15 @@ def ensure_home(self):
if not os.path.exists(POETRY_HOME):
os.mkdir(POETRY_HOME, 0o755)

def remove_home(self):
"""
Removes $POETRY_HOME.
"""
if not os.path.exists(POETRY_HOME):
return

shutil.rmtree(POETRY_HOME)

def install(self, version, upgrade=False):
"""
Installs Poetry in $POETRY_HOME.
Expand Down Expand Up @@ -477,10 +542,15 @@ def make_bin(self):
if not os.path.exists(POETRY_BIN):
os.mkdir(POETRY_BIN, 0o755)

if not WINDOWS:
with open(os.path.join(POETRY_BIN, "poetry"), "w") as f:
f.write(BIN)
ext = ""
if WINDOWS:
with open(os.path.join(POETRY_BIN, "poetry.bat"), "w") as f:
f.write(BAT)

with open(os.path.join(POETRY_BIN, "poetry"), "w") as f:
f.write(BIN)

if not WINDOWS:
# Making the file executable
st = os.stat(os.path.join(POETRY_BIN, "poetry"))
os.chmod(os.path.join(POETRY_BIN, "poetry"), st.st_mode | stat.S_IEXEC)
Expand All @@ -497,7 +567,7 @@ def update_path(self):
Tries to update the $PATH automatically.
"""
if WINDOWS:
return self._update_windows_path()
return self.add_to_windows_path()

# Updating any profile we can on UNIX systems
export_string = self.get_export_string()
Expand All @@ -519,6 +589,91 @@ def update_path(self):

updated.append(os.path.relpath(profile, HOME))

def add_to_windows_path(self):
try:
old_path = self.get_windows_path_var()
except WindowsError:
old_path = None

if old_path is None:
print(
colorize(
"warning",
"Unable to get the PATH value. It will not be updated automatically",
)
)
self._modify_path = False

return

new_path = POETRY_BIN
if POETRY_BIN in old_path:
old_path = old_path.replace(POETRY_BIN + ";", "")

if old_path:
new_path += ";"
new_path += old_path

self.set_windows_path_var(new_path)

def get_windows_path_var(self):
with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
path, _ = winreg.QueryValueEx(key, "PATH")

return path

def set_windows_path_var(self, value):
import ctypes
from ctypes.wintypes import HWND
from ctypes.wintypes import LPARAM
from ctypes.wintypes import LPVOID
from ctypes.wintypes import UINT
from ctypes.wintypes import WPARAM

with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, value)

# Tell other processes to update their environment
HWND_BROADCAST = 0xFFFF
WM_SETTINGCHANGE = 0x1A

SMTO_ABORTIFHUNG = 0x0002

result = ctypes.c_long()
SendMessageTimeoutW = ctypes.windll.user32.SendMessageTimeoutW
SendMessageTimeoutW(
HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
u"Environment",
SMTO_ABORTIFHUNG,
5000,
ctypes.byref(result),
)

def remove_from_path(self):
if WINDOWS:
return self.remove_from_windows_path()

return self.remove_from_unix_path()

def remove_from_windows_path(self):
path = self.get_windows_path_var()

poetry_path = POETRY_BIN
if poetry_path in path:
path = path.replace(POETRY_BIN + ";", "")

if poetry_path in path:
path = path.replace(POETRY_BIN, "")

self.set_windows_path_var(path)

def remove_from_unix_path(self):
pass

def get_export_string(self):
path = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
export_string = 'export PATH="{}:$PATH"'.format(path)
Expand All @@ -539,15 +694,15 @@ def get_unix_profiles(self):

return profiles

def update_windows_path(self):
return False

def display_pre_message(self):
if WINDOWS:
home = POETRY_BIN.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%")
else:
home = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")

kwargs = {
"poetry": colorize("info", "Poetry"),
"poetry_home_bin": colorize(
"comment", POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
),
"poetry_home_bin": colorize("comment", home),
}

if not self._modify_path:
Expand All @@ -566,29 +721,47 @@ def display_pre_message(self):

print(PRE_MESSAGE.format(**kwargs))

def display_pre_uninstall_message(self):
kwargs = {
"poetry": colorize("info", "Poetry"),
"poetry_home_bin": colorize(
"comment",
POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME").replace(
os.getenv("USERPROFILE", ""), "%USERPROFILE%"
),
),
}

print(PRE_UNINSTALL_MESSAGE.format(**kwargs))

def display_post_message(self, version):
print("")

kwargs = {
"poetry": colorize("info", "Poetry"),
"poetry_home_bin": colorize(
"comment", POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
),
"poetry_home_env": colorize(
"comment", POETRY_ENV.replace(os.getenv("HOME", ""), "$HOME")
),
"version": colorize("comment", version),
}

if WINDOWS:
message = POST_MESSAGE_WINDOWS
if not self._modify_path:
message = POST_MESSAGE_WINDOWS_NO_MODIFY_PATH

poetry_home_bin = POETRY_BIN.replace(
os.getenv("USERPROFILE", ""), "%USERPROFILE%"
)
else:
message = POST_MESSAGE_UNIX
if not self._modify_path:
message = POST_MESSAGE_UNIX_NO_MODIFY_PATH

poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
kwargs["poetry_home_env"] = colorize(
"comment", POETRY_ENV.replace(os.getenv("HOME", ""), "$HOME")
)

kwargs["poetry_home_bin"] = colorize("comment", poetry_home_bin)

print(message.format(**kwargs))

def call(self, *args):
Expand All @@ -609,6 +782,9 @@ def main():
parser.add_argument(
"-y", "--yes", dest="accept_all", action="store_true", default=False
)
parser.add_argument(
"--uninstall", dest="uninstall", action="store_true", default=False
)

args = parser.parse_args()

Expand All @@ -619,6 +795,9 @@ def main():
accept_all=args.accept_all,
)

if args.uninstall:
return installer.uninstall()

return installer.run()


Expand Down

0 comments on commit 1d35987

Please sign in to comment.