3 """Software Carpentry Windows Installer
5 Helps mimic a *nix environment on Windows with as little work as possible.
8 * Installs GNU Make and makes it accessible from msysGit
9 * Installs nano and makes it accessible from msysGit
10 * Installs SQLite and makes it accessible from msysGit
11 * Creates a ~/nano.rc with links to syntax highlighting configs
12 * Provides standard nosetests behavior for msysGit
13 * Adds R's bin directory to the path (if we can find it)
17 1. Install Python, IPython, and Nose. An easy way to do this is with
18 the Anaconda Python distribution
19 http://continuum.io/downloads
21 https://github.com/msysgit/msysgit/releases
22 3. Install R (if your workshop uses R)
23 http://cran.r-project.org/bin/windows/base/rw-FAQ.html#Installation-and-Usage
24 4. Run swc-windows-installer.py.
25 You should be able to simply double click the file in Windows
32 from io import BytesIO as _BytesIO
33 except ImportError: # Python 2
34 from StringIO import StringIO as _BytesIO
41 from urllib.request import urlopen as _urlopen
42 except ImportError: # Python 2
43 from urllib2 import urlopen as _urlopen
49 LOG = logging.getLogger('swc-windows-installer')
50 LOG.addHandler(logging.StreamHandler())
51 LOG.setLevel(logging.INFO)
54 if sys.version_info >= (3, 0): # Python 3
57 def open3(file, mode='r', newline=None):
60 raise NotImplementedError(newline)
61 f = open(file, mode + 'b')
67 def download(url, sha1):
68 """Download a file and verify its hash"""
69 LOG.debug('download {}'.format(url))
71 byte_content = r.read()
72 download_sha1 = hashlib.sha1(byte_content).hexdigest()
73 if download_sha1 != sha1:
75 'downloaded {!r} has the wrong SHA-1 hash: {} != {}'.format(
76 url, download_sha1, sha1))
77 LOG.debug('SHA-1 for {} matches the expected {}'.format(url, sha1))
82 """Split a path into a list of components
84 >>> splitall('nano-2.2.6/doc/Makefile.am')
85 ['nano-2.2.6', 'doc', 'Makefile.am']
89 head, tail = os.path.split(path)
101 def transform(tarinfo, strip_components=0):
102 """Transform TarInfo objects for extraction"""
103 path_components = splitall(tarinfo.name)
105 tarinfo.name = os.path.join(*path_components[strip_components:])
107 if len(path_components) <= strip_components:
113 def tar_install(url, sha1, install_directory, compression='*',
115 """Download and install a tar bundle"""
116 if not os.path.isdir(install_directory):
117 tar_bytes = download(url=url, sha1=sha1)
118 tar_io = _BytesIO(tar_bytes)
119 filename = os.path.basename(url)
120 mode = 'r:{}'.format(compression)
121 tar_file = tarfile.open(filename, mode, tar_io)
122 LOG.info('installing {} into {}'.format(url, install_directory))
123 os.makedirs(install_directory)
125 transform(tarinfo=tarinfo, strip_components=strip_components)
126 for tarinfo in tar_file]
128 path=install_directory,
129 members=[m for m in members if m is not None])
131 LOG.info('existing installation at {}'.format(install_directory))
134 def zip_install(url, sha1, install_directory):
135 """Download and install a zipped bundle"""
136 if not os.path.isdir(install_directory):
137 zip_bytes = download(url=url, sha1=sha1)
138 zip_io = _BytesIO(zip_bytes)
139 zip_file = zipfile.ZipFile(zip_io)
140 LOG.info('installing {} into {}'.format(url, install_directory))
141 os.makedirs(install_directory)
142 zip_file.extractall(install_directory)
144 LOG.info('existing installation at {}'.format(install_directory))
147 def install_msysgit_binary(name, sha1, install_directory,
148 tag='Git-1.9.4-preview20140815'):
149 """Download and install a binary from msysGit's bin directory"""
151 url='https://github.com/msysgit/msysgit/raw/{}/bin/{}'.format(
154 LOG.info('installing {} into {}'.format(name, install_directory))
155 with open(os.path.join(install_directory, name), 'wb') as f:
159 def install_nano(install_directory):
160 """Download and install the nano text editor"""
162 url='http://www.nano-editor.org/dist/v2.2/NT/nano-2.2.6.zip',
163 sha1='f5348208158157060de0a4df339401f36250fe5b',
164 install_directory=install_directory)
167 def install_nanorc(install_directory):
168 """Download and install nano syntax highlighting"""
170 url='http://www.nano-editor.org/dist/v2.2/nano-2.2.6.tar.gz',
171 sha1='f2a628394f8dda1b9f28c7e7b89ccb9a6dbd302a',
172 install_directory=install_directory,
174 home = os.path.expanduser('~')
175 nanorc = os.path.join(home, 'nano.rc')
176 if not os.path.isfile(nanorc):
177 syntax_dir = os.path.join(install_directory, 'doc', 'syntax')
178 LOG.info('include nanorc from {} in {}'.format(syntax_dir, nanorc))
179 with open3(nanorc, 'w', newline='\n') as f:
180 for filename in os.listdir(syntax_dir):
181 if filename.endswith('.nanorc'):
182 path = os.path.join(syntax_dir, filename)
183 rel_path = os.path.relpath(path, home)
184 include_path = make_posix_path(os.path.join('~', rel_path))
185 f.write('include {}\n'.format(include_path))
188 def install_sqlite(install_directory):
189 """Download and install the SQLite shell"""
191 url='https://sqlite.org/2014/sqlite-shell-win32-x86-3080403.zip',
192 sha1='1a8ab0ca9f4c51afeffeb49bd301e1d7f64741bb',
193 install_directory=install_directory)
196 def create_nosetests_entry_point(python_scripts_directory):
197 """Creates a terminal-based nosetests entry point for msysGit"""
198 contents = '\n'.join([
199 '#!/usr/bin/env/ python',
202 "if __name__ == '__main__':",
203 ' sys.exit(nose.core.main())',
206 if not os.path.isdir(python_scripts_directory):
207 os.makedirs(python_scripts_directory)
208 path = os.path.join(python_scripts_directory, 'nosetests')
209 LOG.info('create nosetests entrypoint {}'.format(path))
210 with open(path, 'w') as f:
214 def get_r_bin_directory():
215 """Locate the R bin directory (if R is installed)
217 version_re = re.compile('^R-(\d+)[.](\d+)[.](\d+)$')
220 os.environ.get('ProgramW6432', r'c:\Program Files'),
221 os.environ.get('ProgramFiles', r'c:\Program Files'),
222 os.environ.get('ProgramFiles(x86)', r'c:\Program Files(x86)'),
224 bin_glob = os.path.join(pf, 'R', 'R-[0-9]*.[0-9]*.[0-9]*', 'bin')
225 for path in glob.glob(bin_glob):
226 version_dir = os.path.basename(os.path.dirname(path))
227 version_match = version_re.match(version_dir)
228 if version_match and version_match.groups() not in paths:
229 paths[version_match.groups()] = path
231 LOG.info('no R installation found under {}'.format(pf))
233 LOG.debug('detected R installs:\n* {}'.format('\n* '.join([
234 v for k,v in sorted(paths.items())])))
235 version = sorted(paths.keys())[-1]
236 LOG.info('using R v{} bin directory at {}'.format(
237 '.'.join(version), paths[version]))
238 return paths[version]
241 def update_bash_profile(extra_paths=()):
242 """Create or append to a .bash_profile for Software Carpentry
244 Adds nano to the path, sets the default editor to nano, and adds
245 additional paths for other executables.
249 '# Add paths for Software-Carpentry-installed scripts and executables',
250 'export PATH=\"$PATH:{}\"'.format(':'.join(
251 make_posix_path(path) for path in extra_paths),),
253 '# Make nano the default editor',
254 'export EDITOR=nano',
257 config_path = os.path.join(os.path.expanduser('~'), '.bash_profile')
258 LOG.info('update bash profile at {}'.format(config_path))
259 LOG.debug('extra paths:\n* {}'.format('\n* '.join(extra_paths)))
260 with open(config_path, 'a') as f:
261 f.write('\n'.join(lines))
264 def make_posix_path(windows_path):
265 """Convert a Windows path to a posix path"""
267 (re.compile(r'\\'), '/'),
268 (re.compile('^[Cc]:'), '/c'),
270 windows_path = regex.sub(sub, windows_path)
275 swc_dir = os.path.join(os.path.expanduser('~'), '.swc')
276 bin_dir = os.path.join(swc_dir, 'bin')
277 nano_dir = os.path.join(swc_dir, 'lib', 'nano')
278 nanorc_dir = os.path.join(swc_dir, 'share', 'nanorc')
279 sqlite_dir = os.path.join(swc_dir, 'lib', 'sqlite')
280 create_nosetests_entry_point(python_scripts_directory=bin_dir)
281 install_msysgit_binary(
282 name='make.exe', sha1='ad11047985c33ff57074f8e09d347fe122e047a4',
283 install_directory=bin_dir)
284 install_nano(install_directory=nano_dir)
285 install_nanorc(install_directory=nanorc_dir)
286 install_sqlite(install_directory=sqlite_dir)
287 paths = [nano_dir, sqlite_dir, bin_dir]
288 r_dir = get_r_bin_directory()
291 update_bash_profile(extra_paths=paths)
294 if __name__ == '__main__':
297 parser = argparse.ArgumentParser(
299 formatter_class=argparse.RawDescriptionHelpFormatter)
302 choices=['critical', 'error', 'warning', 'info', 'debug'],
303 help='Verbosity (defaults to {!r})'.format(
304 logging.getLevelName(LOG.level).lower()))
306 '--version', action='version',
307 version='%(prog)s {}'.format(__version__))
309 args = parser.parse_args()
312 level = getattr(logging, args.verbose.upper())
315 LOG.info('Preparing your Software Carpentry awesomeness!')
316 LOG.info('installer version {}'.format(__version__))
318 LOG.info('Installation complete.')