Coverage for devshell.py: 34%

72 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-06 19:50 +0100

1#!/usr/bin/env python3 

2 

3""" 

4 developer shell 

5 ~~~~~~~~~~~~~~~ 

6 

7 Just call this file, and the magic happens ;) 

8 

9 This file is from: https://pypi.org/project/dev-shell/ 

10 Source: https://github.com/jedie/dev-shell/blob/main/devshell.py 

11 

12 :copyleft: 2021 by Jens Diemer 

13 :license: GNU GPL v3 or above 

14""" 

15 

16import argparse 

17import hashlib 

18import signal 

19import subprocess 

20import sys 

21import venv 

22from pathlib import Path 

23 

24 

25try: 

26 import ensurepip # noqa 

27except ModuleNotFoundError as err: 

28 print(err) 

29 print('-' * 100) 

30 print('Error: Pip not available!') 

31 print('Hint: "apt-get install python3-venv"\n') 

32 raise 

33 

34 

35assert sys.version_info >= (3, 7), 'Python version is too old!' 

36 

37 

38if sys.platform == 'win32': # wtf 38 ↛ 40line 38 didn't jump to line 40, because the condition on line 38 was never true

39 # Files under Windows, e.g.: .../.venv/Scripts/python.exe 

40 BIN_NAME = 'Scripts' 

41 FILE_EXT = '.exe' 

42else: 

43 # Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python 

44 BIN_NAME = 'bin' 

45 FILE_EXT = '' 

46 

47BASE_PATH = Path(__file__).parent 

48VENV_PATH = BASE_PATH / '.venv' 

49BIN_PATH = VENV_PATH / BIN_NAME 

50PYTHON_PATH = BIN_PATH / f'python{FILE_EXT}' 

51PIP_PATH = BIN_PATH / f'pip{FILE_EXT}' 

52POETRY_PATH = BIN_PATH / f'poetry{FILE_EXT}' 

53 

54DEP_LOCK_PATH = BASE_PATH / 'poetry.lock' 

55DEP_HASH_PATH = VENV_PATH / '.dep_hash' 

56 

57# script file defined in pyproject.toml as [tool.poetry.scripts] 

58# (Under Windows: ".exe" not added!) 

59PROJECT_SHELL_SCRIPT = BIN_PATH / 'devshell' 

60 

61 

62def get_dep_hash(): 

63 """Get SHA512 hash from poetry.lock content.""" 

64 return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest() 

65 

66 

67def store_dep_hash(): 

68 """Generate /.venv/.dep_hash""" 

69 DEP_HASH_PATH.write_text(get_dep_hash()) 

70 

71 

72def venv_up2date(): 

73 """Is existing .venv is up-to-date?""" 

74 if DEP_HASH_PATH.is_file(): 

75 return DEP_HASH_PATH.read_text() == get_dep_hash() 

76 return False 

77 

78 

79def verbose_check_call(*popen_args): 

80 popen_args = [str(arg) for arg in popen_args] # e.g.: Path() -> str for python 3.7 

81 print(f'\n+ {" ".join(popen_args)}\n') 

82 return subprocess.check_call(popen_args) 

83 

84 

85def noop_signal_handler(signal_num, frame): 

86 """ 

87 Signal handler that does nothing: Used to ignore "Ctrl-C" signals 

88 """ 

89 pass 

90 

91 

92def main(argv): 

93 if len(argv) == 2 and argv[1] in ('--update', '--help'): 

94 parser = argparse.ArgumentParser( 

95 prog=Path(__file__).name, description='Developer shell', epilog='...live long and prosper...' 

96 ) 

97 parser.add_argument( 

98 '--update', default=False, action='store_true', help='Force create/upgrade virtual environment' 

99 ) 

100 parser.add_argument( 

101 'command_args', 

102 nargs=argparse.ZERO_OR_MORE, 

103 help='arguments to pass to dev-setup shell/cli', 

104 ) 

105 options = parser.parse_args(argv) 

106 force_update = options.update 

107 extra_args = argv[2:] 

108 else: 

109 force_update = False 

110 extra_args = argv[1:] 

111 

112 # Create virtual env in ".../.venv/": 

113 if not PYTHON_PATH.is_file() or force_update: 

114 print('Create virtual env here:', VENV_PATH.absolute()) 

115 builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True) 

116 builder.create(env_dir=VENV_PATH) 

117 

118 # install/update "pip" and "poetry": 

119 if not POETRY_PATH.is_file() or force_update: 

120 # Note: Under Windows pip.exe can't replace this own .exe file, so use the module way: 

121 verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip', 'setuptools') 

122 verbose_check_call(PIP_PATH, 'install', 'poetry') 

123 

124 if not DEP_LOCK_PATH.is_file(): 

125 verbose_check_call(POETRY_PATH, 'update') 

126 

127 # install via poetry, if: 

128 # 1. .venv not exists 

129 # 2. "--update" used 

130 # 3. poetry.lock file was changed 

131 if not PROJECT_SHELL_SCRIPT.is_file() or force_update or not venv_up2date(): 

132 verbose_check_call(POETRY_PATH, 'install') 

133 store_dep_hash() 

134 

135 # The cmd2 shell should not abort on Ctrl-C => ignore "Interrupt from keyboard" signal: 

136 signal.signal(signal.SIGINT, noop_signal_handler) 

137 

138 # Run project cmd shell via "setup.py" entrypoint: 

139 # (Call it via python, because Windows sucks calling the file direct) 

140 try: 

141 verbose_check_call(PYTHON_PATH, PROJECT_SHELL_SCRIPT, *extra_args) 

142 except subprocess.CalledProcessError as err: 

143 sys.exit(err.returncode) 

144 

145 

146if __name__ == '__main__': 

147 main(sys.argv)