-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdotfiles.py
executable file
·132 lines (113 loc) · 4.88 KB
/
dotfiles.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/env python3
# Dotfiles installer/updater/manager
# Run this script with --help to see the available options
import os
import sys
import subprocess
# To remove the hassle of having to install dependencies
# for this script, it will automatically create a venv
# The main function is located in df/__init__.py
# installation/updating behavior is defined in df/module/*.py
# (e.g. df/module/git.py)
def is_in_local_venv() -> bool:
"""
Check if the current Python interpreter is in a venv located in the same
directory as the current script.
"""
venv_dir = os.path.join(os.path.dirname(
os.path.realpath(__file__)), '.venv')
is_in_a_venv = bool(os.environ.get("VIRTUAL_ENV"))
if is_in_a_venv:
current_venv_dir = os.environ.get("VIRTUAL_ENV")
return current_venv_dir == venv_dir
return False
def create_venv_if_needed() -> None:
"""
Create a venv in the same directory as the current script if the current
Python interpreter is not in a venv. It will also install the dependencies
from requirements.txt if the venv is created or if the requirements file
is newer than the venv.
"""
if is_in_local_venv():
return
venv_dir = os.path.join(os.path.dirname(
os.path.realpath(__file__)), '.venv')
venv_pip = os.path.join(venv_dir, 'bin', 'pip')
if sys.platform == 'win32':
# On windows it's in a different location
venv_pip = os.path.join(venv_dir, 'Scripts', 'pip.exe')
requirements = os.path.join(os.path.dirname(
os.path.realpath(__file__)), 'requirements.txt')
if not os.path.exists(venv_dir):
# Create venv
subprocess.check_call([sys.executable, '-m', 'venv', venv_dir])
subprocess.check_call([venv_pip, 'install', '-r', requirements])
elif os.path.getmtime(requirements) > os.path.getmtime(venv_dir):
# Update venv, requirements file is newer
subprocess.check_call([venv_pip, 'install', '-r', requirements])
# Touch the venv directory to update its modification time
os.utime(venv_dir, None)
def restart_with_venv() -> None:
"""
Update the current process to run in the venv created by
create_venv_if_needed(). This is done by replacing the current process
"""
venv_dir = os.path.join(os.path.dirname(
os.path.realpath(__file__)), '.venv')
venv_python = os.path.join(venv_dir, 'bin', 'python')
if sys.platform == 'win32':
# On windows it's in a different location
venv_python = os.path.join(venv_dir, 'Scripts', 'python.exe')
# set the VIRTUAL_ENV environment variable to the venv directory
os.environ['VIRTUAL_ENV'] = venv_dir
# Backup original sys.executable
os.environ['DF_ORIGINAL_EXECUTABLE'] = sys.executable
if sys.platform == 'win32':
# Windows does not support replacing the current process
subprocess.run([venv_python] + sys.argv, check=True)
else:
os.execl(venv_python, venv_python, *sys.argv)
# Relaunch this script in the venv if needed
if __name__ == '__main__':
create_venv_if_needed()
if not is_in_local_venv():
restart_with_venv()
exit() # This line is never reached
# Test if the venv is working
try:
import textual
except ImportError:
# The venv was manually specified by the user, just give an error
if not os.environ.get("DF_ORIGINAL_EXECUTABLE"):
print("The venv is not working, try to run the script without specifying the venv, or fix the venv")
exit(1)
print("The venv is not working, trying to recreate it")
venv_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),".venv")
print("Deleting {}".format(venv_path))
import shutil
shutil.rmtree(venv_path, ignore_errors=True)
if not os.environ.get("DF_VENV_RETRY"):
os.environ["DF_VENV_RETRY"] = "1" # Avoid infinite loop
# Use the original executable to restart the script
del os.environ["VIRTUAL_ENV"]
if sys.platform == 'win32':
# Windows does not support replacing the current process
subprocess.run([os.environ['DF_ORIGINAL_EXECUTABLE']] + sys.argv, check=True)
else:
os.execl(os.environ['DF_ORIGINAL_EXECUTABLE'], os.environ['DF_ORIGINAL_EXECUTABLE'], *sys.argv)
else:
print("The venv is not working, please report this issue")
exit(1)
if os.environ.get("TEXTUAL"):
# The app is launched with textual devtools
# __name__ was not set to __main__ so we need to set it manually
# the user allready has the venv activated so we only need to
# set __name__ from here
__name__ = "__main__"
if __name__ == '__main__':
import df
dotfiles_dir = os.path.dirname(os.path.realpath(__file__))
config_file = os.path.join(dotfiles_dir, "config.json")
# Run the real main function
# (the app variable is required for textual devtools)
app = df.main(dotfiles_dir, config_file)