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)