3 """Software Carpentry Windows Installer
5 Helps mimic a *nix environment on Windows with as little work as possible.
8 * Installs nano and makes it accessible from msysGit
9 * Installs SQLite and makes it accessible from msysGit
10 * Creates a ~/nano.rc with links to syntax highlighting configs
11 * Provides standard nosetests behavior for msysGit
12 * Adds R's bin directory to the path (if we can find it)
16 1. Install Python, IPython, and Nose. An easy way to do this is with
17 the Anaconda CE Python distribution
18 http://continuum.io/anacondace.html
20 https://github.com/msysgit/msysgit/releases
21 3. Install R (if your workshop uses R)
22 http://cran.r-project.org/bin/windows/base/rw-FAQ.html#Installation-and-Usage
23 4. Run swc-windows-installer.py.
24 You should be able to simply double click the file in Windows
31 from io import BytesIO as _BytesIO
32 except ImportError: # Python 2
33 from StringIO import StringIO as _BytesIO
40 from urllib.request import urlopen as _urlopen
41 except ImportError: # Python 2
42 from urllib2 import urlopen as _urlopen
48 LOG = logging.getLogger('swc-windows-installer')
49 LOG.addHandler(logging.StreamHandler())
50 LOG.setLevel(logging.INFO)
53 if sys.version_info >= (3, 0): # Python 3
56 def open3(file, mode='r', newline=None):
59 raise NotImplementedError(newline)
60 f = open(file, mode + 'b')
66 def download(url, sha1):
67 """Download a file and verify it's hash"""
68 LOG.debug('download {}'.format(url))
70 byte_content = r.read()
71 download_sha1 = hashlib.sha1(byte_content).hexdigest()
72 if download_sha1 != sha1:
74 'downloaded {!r} has the wrong SHA-1 hash: {} != {}'.format(
75 url, download_sha1, sha1))
76 LOG.debug('SHA-1 for {} matches the expected {}'.format(url, sha1))
81 """Split a path into a list of components
83 >>> splitall('nano-2.2.6/doc/Makefile.am')
84 ['nano-2.2.6', 'doc', 'Makefile.am']
88 head, tail = os.path.split(path)
100 def transform(tarinfo, strip_components=0):
101 """Transform TarInfo objects for extraction"""
102 path_components = splitall(tarinfo.name)
104 tarinfo.name = os.path.join(*path_components[strip_components:])
106 if len(path_components) <= strip_components:
112 def tar_install(url, sha1, install_directory, compression='*',
114 """Download and install a tar bundle"""
115 if not os.path.isdir(install_directory):
116 tar_bytes = download(url=url, sha1=sha1)
117 tar_io = _BytesIO(tar_bytes)
118 filename = os.path.basename(url)
119 mode = 'r:{}'.format(compression)
120 tar_file = tarfile.open(filename, mode, tar_io)
121 LOG.info('installing {} into {}'.format(url, install_directory))
122 os.makedirs(install_directory)
124 transform(tarinfo=tarinfo, strip_components=strip_components)
125 for tarinfo in tar_file]
127 path=install_directory,
128 members=[m for m in members if m is not None])
130 LOG.info('existing installation at {}'.format(install_directory))
133 def zip_install(url, sha1, install_directory):
134 """Download and install a zipped bundle"""
135 if not os.path.isdir(install_directory):
136 zip_bytes = download(url=url, sha1=sha1)
137 zip_io = _BytesIO(zip_bytes)
138 zip_file = zipfile.ZipFile(zip_io)
139 LOG.info('installing {} into {}'.format(url, install_directory))
140 os.makedirs(install_directory)
141 zip_file.extractall(install_directory)
143 LOG.info('existing installation at {}'.format(install_directory))
146 def install_nano(install_directory):
147 """Download and install the nano text editor"""
149 url='http://www.nano-editor.org/dist/v2.2/NT/nano-2.2.6.zip',
150 sha1='f5348208158157060de0a4df339401f36250fe5b',
151 install_directory=install_directory)
154 def install_nanorc(install_directory):
155 """Download and install nano syntax highlighting"""
157 url='http://www.nano-editor.org/dist/v2.2/nano-2.2.6.tar.gz',
158 sha1='f2a628394f8dda1b9f28c7e7b89ccb9a6dbd302a',
159 install_directory=install_directory,
161 home = os.path.expanduser('~')
162 nanorc = os.path.join(home, 'nano.rc')
163 if not os.path.isfile(nanorc):
164 syntax_dir = os.path.join(install_directory, 'doc', 'syntax')
165 LOG.info('include nanorc from {} in {}'.format(syntax_dir, nanorc))
166 with open3(nanorc, 'w', newline='\n') as f:
167 for filename in os.listdir(syntax_dir):
168 if filename.endswith('.nanorc'):
169 path = os.path.join(syntax_dir, filename)
170 rel_path = os.path.relpath(path, home)
171 include_path = make_posix_path(os.path.join('~', rel_path))
172 f.write('include {}\n'.format(include_path))
175 def install_sqlite(install_directory):
176 """Download and install the SQLite shell"""
178 url='https://sqlite.org/2014/sqlite-shell-win32-x86-3080403.zip',
179 sha1='1a8ab0ca9f4c51afeffeb49bd301e1d7f64741bb',
180 install_directory=install_directory)
183 def create_nosetests_entry_point(python_scripts_directory):
184 """Creates a terminal-based nosetests entry point for msysGit"""
185 contents = '\n'.join([
186 '#!/usr/bin/env/ python',
189 "if __name__ == '__main__':",
190 ' sys.exit(nose.core.main())',
193 if not os.path.isdir(python_scripts_directory):
194 os.makedirs(python_scripts_directory)
195 path = os.path.join(python_scripts_directory, 'nosetests')
196 LOG.info('create nosetests entrypoint {}'.format(path))
197 with open(path, 'w') as f:
201 def get_r_bin_directory():
202 """Locate the R bin directory (if R is installed
204 version_re = re.compile('^R-(\d+)[.](\d+)[.](\d+)$')
207 os.environ.get('ProgramW6432', r'c:\Program Files'),
208 os.environ.get('ProgramFiles', r'c:\Program Files'),
209 os.environ.get('ProgramFiles(x86)', r'c:\Program Files(x86)'),
211 bin_glob = os.path.join(pf, 'R', 'R-[0-9]*.[0-9]*.[0-9]*', 'bin')
212 for path in glob.glob(bin_glob):
213 version_dir = os.path.basename(os.path.dirname(path))
214 version_match = version_re.match(version_dir)
215 if version_match and version_match.groups() not in paths:
216 paths[version_match.groups()] = path
218 LOG.info('no R installation found under {}'.format(pf))
220 LOG.debug('detected R installs:\n* {}'.format('\n* '.join([
221 v for k,v in sorted(paths.items())])))
222 version = sorted(paths.keys())[-1]
223 LOG.info('using R v{} bin directory at {}'.format(
224 '.'.join(version), paths[version]))
225 return paths[version]
228 def update_bash_profile(extra_paths=()):
229 """Create or append to a .bash_profile for Software Carpentry
231 Adds nano to the path, sets the default editor to nano, and adds
232 additional paths for other executables.
236 '# Add paths for Software-Carpentry-installed scripts and executables',
237 'export PATH=\"$PATH:{}\"'.format(':'.join(
238 make_posix_path(path) for path in extra_paths),),
240 '# Make nano the default editor',
241 'export EDITOR=nano',
244 config_path = os.path.join(os.path.expanduser('~'), '.bash_profile')
245 LOG.info('update bash profile at {}'.format(config_path))
246 LOG.debug('extra paths:\n* {}'.format('\n* '.join(extra_paths)))
247 with open(config_path, 'a') as f:
248 f.write('\n'.join(lines))
251 def make_posix_path(windows_path):
252 """Convert a Windows path to a posix path"""
254 (re.compile(r'\\'), '/'),
255 (re.compile('^[Cc]:'), '/c'),
257 windows_path = regex.sub(sub, windows_path)
262 swc_dir = os.path.join(os.path.expanduser('~'), '.swc')
263 bin_dir = os.path.join(swc_dir, 'bin')
264 nano_dir = os.path.join(swc_dir, 'lib', 'nano')
265 nanorc_dir = os.path.join(swc_dir, 'share', 'nanorc')
266 sqlite_dir = os.path.join(swc_dir, 'lib', 'sqlite')
267 create_nosetests_entry_point(python_scripts_directory=bin_dir)
268 install_nano(install_directory=nano_dir)
269 install_nanorc(install_directory=nanorc_dir)
270 install_sqlite(install_directory=sqlite_dir)
271 paths = [nano_dir, sqlite_dir, bin_dir]
272 r_dir = get_r_bin_directory()
275 update_bash_profile(extra_paths=paths)
278 if __name__ == '__main__':
281 parser = argparse.ArgumentParser(
283 formatter_class=argparse.RawDescriptionHelpFormatter)
286 choices=['critical', 'error', 'warning', 'info', 'debug'],
287 help='Verbosity (defaults to {!r})'.format(
288 logging.getLevelName(LOG.level).lower()))
290 '--version', action='version',
291 version='%(prog)s {}'.format(__version__))
293 args = parser.parse_args()
296 level = getattr(logging, args.verbose.upper())
299 LOG.info('Preparing your Software Carpentry awesomeness!')
300 LOG.info('installer version {}'.format(__version__))
302 LOG.info('Installation complete.')