Mercurial > dotfiles
comparison install.py @ 416:222b477ad678
Install script improvements.
- Support a local script that adds more installs.
- Add `--force` flag to force certain things.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 19 Jan 2018 09:19:34 -0800 |
parents | c6da0c9f40ae |
children | 6dbef23ca6bd |
comparison
equal
deleted
inserted
replaced
415:e57b012539d5 | 416:222b477ad678 |
---|---|
1 import os | 1 import os |
2 import os.path | 2 import os.path |
3 import sys | 3 import sys |
4 import stat | 4 import stat |
5 import shutil | |
5 import argparse | 6 import argparse |
6 import functools | 7 import functools |
7 import subprocess | 8 import subprocess |
8 import configparser | 9 import configparser |
9 | 10 |
15 if sys.platform == "win32": | 16 if sys.platform == "win32": |
16 is_nix = False | 17 is_nix = False |
17 is_windows = True | 18 is_windows = True |
18 | 19 |
19 | 20 |
20 def _p(*paths): | 21 def _p(*paths, force_unix=False): |
21 return os.path.join(dotfiles_dir, *paths).replace('/', os.sep) | 22 res = os.path.join(dotfiles_dir, *paths) |
23 if force_unix: | |
24 res = res.replace('\\', '/') | |
25 else: | |
26 res = res.replace('/', os.sep) | |
27 return res | |
22 | 28 |
23 | 29 |
24 def nixslash(path): | 30 def nixslash(path): |
25 return path.replace('\\', '/') | 31 return path.replace('\\', '/') |
26 | 32 |
68 @functools.wraps(f) | 74 @functools.wraps(f) |
69 def decorator(*args, **kwargs): | 75 def decorator(*args, **kwargs): |
70 if is_windows: | 76 if is_windows: |
71 return f(*args, **kwargs) | 77 return f(*args, **kwargs) |
72 return decorator | 78 return decorator |
79 | |
80 | |
81 def supports_forcing(f): | |
82 f.__dotfiles_supports_forcing__ = True | |
83 return f | |
73 | 84 |
74 | 85 |
75 def needs_config(f): | 86 def needs_config(f): |
76 f.__dotfiles_needs_config__ = True | 87 f.__dotfiles_needs_config__ = True |
77 return f | 88 return f |
145 | 156 |
146 | 157 |
147 def install_git(): | 158 def install_git(): |
148 writelines('~/.gitconfig', [ | 159 writelines('~/.gitconfig', [ |
149 '[include]', | 160 '[include]', |
150 'path = %s' % _p('git/gitconfig') | 161 'path = %s' % _p('git/gitconfig', force_unix=True) |
151 ]) | 162 ]) |
152 if is_windows: | 163 if is_windows: |
153 subprocess.check_call( | 164 subprocess.check_call( |
154 ['setx', 'GIT_SSH', | 165 ['setx', 'GIT_SSH', |
155 '%USERPROFILE%\\Dropbox\\Utilities\\plink.exe'], | 166 '%USERPROFILE%\\Dropbox\\Utilities\\plink.exe'], |
172 'source "gpg2 -dq %s |"' % _p('mutt/variables.gpg'), | 183 'source "gpg2 -dq %s |"' % _p('mutt/variables.gpg'), |
173 'source "%s"' % _p('mutt/muttrc'), | 184 'source "%s"' % _p('mutt/muttrc'), |
174 'source "%s"' % _p('lib/mutt/mutt-colors-solarized/' | 185 'source "%s"' % _p('lib/mutt/mutt-colors-solarized/' |
175 'mutt-colors-solarized-dark-256.muttrc') | 186 'mutt-colors-solarized-dark-256.muttrc') |
176 ]) | 187 ]) |
177 | 188 |
178 | 189 |
179 def clone_git(url, path): | 190 def _on_error_try_make_readable(func, path, exc_info): |
191 if not os.access(path, os.W_OK): | |
192 os.chmod(path, stat.S_IWUSR) | |
193 func(path) | |
194 else: | |
195 raise | |
196 | |
197 | |
198 def clone_git(url, path, force=False): | |
180 if os.path.isdir(path): | 199 if os.path.isdir(path): |
181 print("Skipping git clone of %s -- directory exists" % path) | 200 if not force: |
201 print("Skipping git clone of %s -- directory exists" % path) | |
202 return | |
203 else: | |
204 print("Deleting existing: %s" % path) | |
205 shutil.rmtree(path, onerror=_on_error_try_make_readable) | |
206 | |
182 print("git clone %s %s" % (url, path)) | 207 print("git clone %s %s" % (url, path)) |
183 ensure_dir(os.path.dirname(path)) | 208 ensure_dir(os.path.dirname(path)) |
184 subprocess.check_call(['git', 'clone', url, path]) | 209 subprocess.check_call(['git', 'clone', url, path]) |
185 | 210 |
186 | 211 |
187 def clone_hg(url, path): | 212 def clone_hg(url, path, force=False): |
188 if os.path.isdir(path): | 213 if os.path.isdir(path): |
189 print("Skipping hg clone of %s -- directory exists" % path) | 214 if not force: |
215 print("Skipping hg clone of %s -- directory exists" % path) | |
216 return | |
217 else: | |
218 print("Deleting existing: %s" % path) | |
219 shutil.rmtree(path, onerror=_on_error_try_make_readable) | |
220 | |
190 print("hg clone %s %s" % (url, path)) | 221 print("hg clone %s %s" % (url, path)) |
191 ensure_dir(os.path.dirname(path)) | 222 ensure_dir(os.path.dirname(path)) |
192 env = dict(os.environ) | 223 env = dict(os.environ) |
193 env.update({'HGPLAIN': '1'}) | 224 env.update({'HGPLAIN': '1'}) |
194 subprocess.check_call(['hg', 'clone', url, path], env=env) | 225 subprocess.check_call(['hg', 'clone', url, path], env=env) |
195 | 226 |
196 | 227 |
197 @needs_config | 228 @needs_config |
229 @supports_forcing | |
198 @run_priority(100) | 230 @run_priority(100) |
199 def install_subrepos(cfg): | 231 def install_subrepos(cfg, force=False): |
200 if not cfg.has_section('subrepos'): | 232 if not cfg.has_section('subrepos'): |
201 return | 233 return |
202 | 234 |
203 for path, url in cfg.items('subrepos'): | 235 for path, url in cfg.items('subrepos'): |
204 full_path = _p(path) | 236 full_path = _p(path) |
205 if url.startswith('[git]'): | 237 if url.startswith('[git]'): |
206 clone_git(url[len('[git]'):], full_path) | 238 clone_git(url[len('[git]'):], full_path, force=force) |
207 else: | 239 else: |
208 clone_hg(url, full_path) | 240 clone_hg(url, full_path, force=force) |
209 | 241 |
210 | 242 |
211 def main(): | 243 def main(): |
212 print("dotfiles installer") | 244 print("dotfiles installer") |
213 print("python %s" % sys.version) | 245 print("python %s" % sys.version) |
215 print('') | 247 print('') |
216 | 248 |
217 cfg = configparser.ConfigParser() | 249 cfg = configparser.ConfigParser() |
218 cfg.read(_p('install.cfg')) | 250 cfg.read(_p('install.cfg')) |
219 | 251 |
252 # Get all the methods in this module that are named `install_xxx`. | |
220 mod_names = ['all'] | 253 mod_names = ['all'] |
221 this_mod = sys.modules[__name__] | 254 this_mod = sys.modules[__name__] |
222 for an in dir(this_mod): | 255 for an in dir(this_mod): |
223 if not an.startswith('install_'): | 256 if not an.startswith('install_'): |
224 continue | 257 continue |
225 | 258 |
226 name = an[len('install_'):] | 259 name = an[len('install_'):] |
227 mod_names.append(name) | 260 mod_names.append(name) |
228 | 261 |
262 # See if we have any local install script. | |
263 local_mod = None | |
264 local_install_py = os.path.join(dotfiles_dir, 'local', | |
265 'local_install.py') | |
266 if os.path.isfile(local_install_py): | |
267 import importlib.util | |
268 spec = importlib.util.spec_from_file_location('local_install', | |
269 local_install_py) | |
270 local_mod = importlib.util.module_from_spec(spec) | |
271 spec.loader.exec_module(local_mod) | |
272 sys.modules['local_install'] = local_mod | |
273 | |
274 # Create the parser, where you can specify one or more install target. | |
229 parser = argparse.ArgumentParser() | 275 parser = argparse.ArgumentParser() |
230 parser.add_argument( | 276 parser.add_argument( |
231 'module', nargs='*', | 277 'module', nargs='*', |
232 choices=mod_names, | 278 choices=mod_names, |
233 help="Which module(s) to install. Defaults to all modules.") | 279 help="Which module(s) to install. Defaults to all modules.") |
280 parser.add_argument( | |
281 '-f', '--force', action='store_true', | |
282 help="Force installation by overwriting things.") | |
234 args = parser.parse_args() | 283 args = parser.parse_args() |
235 | 284 |
285 # Get the list of methods to run. | |
236 funcs = [] | 286 funcs = [] |
237 selected_mods = set(args.module) | 287 selected_mods = set(args.module) |
238 if 'all' in selected_mods: | 288 if 'all' in selected_mods: |
239 selected_mods = set(mod_names) | 289 selected_mods = set(mod_names) |
240 selected_mods.remove('all') | 290 selected_mods.remove('all') |
241 for mn in selected_mods: | 291 for mn in selected_mods: |
242 func = getattr(this_mod, 'install_%s' % mn) | 292 func = getattr(this_mod, 'install_%s' % mn) |
243 funcs.append((mn, func)) | 293 funcs.append((mn, func)) |
294 # See if there's a local method too for this. | |
295 if local_mod is not None: | |
296 local_func = getattr(local_mod, 'install_%s' % mn, None) | |
297 if local_func is not None: | |
298 lmn = '%s (local)' % mn | |
299 funcs.append((lmn, local_func)) | |
244 | 300 |
245 funcs = sorted(funcs, key=_get_install_func_priority, reverse=True) | 301 funcs = sorted(funcs, key=_get_install_func_priority, reverse=True) |
246 for name, func in funcs: | 302 for name, func in funcs: |
247 print("Installing %s" % name) | 303 print("Installing %s" % name) |
248 if hasattr(func, '__dotfiles_needs_config__'): | 304 |
249 func(cfg) | 305 f_args = [] |
250 else: | 306 f_kwargs = {} |
251 func() | 307 if getattr(func, '__dotfiles_needs_config__', False): |
308 f_args.append(cfg) | |
309 if getattr(func, '__dotfiles_supports_forcing__', False): | |
310 f_kwargs['force'] = args.force | |
311 | |
312 try: | |
313 func(*f_args, **f_kwargs) | |
314 except Exception as ex: | |
315 print("ERROR: %s" % ex) | |
316 print("Aborting install.") | |
252 | 317 |
253 | 318 |
254 def _get_install_func_priority(func_info): | 319 def _get_install_func_priority(func_info): |
255 func = func_info[1] | 320 func = func_info[1] |
256 return getattr(func, '__dotfiles_priority__', 0) | 321 return getattr(func, '__dotfiles_priority__', 0) |