| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The LUCI Authors. All rights reserved. | 2 # Copyright 2016 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 import argparse | 6 import argparse |
| 7 import contextlib | 7 import contextlib |
| 8 import glob | 8 import glob |
| 9 import logging | 9 import logging |
| 10 import os | 10 import os |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 67 return entries | 67 return entries |
| 68 | 68 |
| 69 | 69 |
| 70 def sha_for(deps_entry): | 70 def sha_for(deps_entry): |
| 71 if 'rev' in deps_entry: | 71 if 'rev' in deps_entry: |
| 72 return deps_entry['rev'] | 72 return deps_entry['rev'] |
| 73 else: | 73 else: |
| 74 return deps_entry['gs'].split('.')[0] | 74 return deps_entry['gs'].split('.')[0] |
| 75 | 75 |
| 76 | 76 |
| 77 def get_links(deps): | 77 def install(deps, cache_root): |
| 78 import pip.wheel # pylint: disable=E0611 | |
| 79 plat_tag = platform_tag() | |
| 80 | |
| 81 links = [] | |
| 82 | |
| 83 for name, dep in deps.iteritems(): | |
| 84 version, source_sha = dep['version'] , sha_for(dep) | |
| 85 prefix = 'wheels/{}-{}-{}_{}'.format(name, version, dep['build'], | |
| 86 source_sha) | |
| 87 generic_link = None | |
| 88 binary_link = None | |
| 89 local_link = None | |
| 90 | |
| 91 for entry in ls(prefix): | |
| 92 fname = entry['name'].split('/')[-1] | |
| 93 md5hash = entry['md5Hash'] | |
| 94 wheel_info = pip.wheel.Wheel.wheel_file_re.match(fname) | |
| 95 if not wheel_info: | |
| 96 LOGGER.warn('Skipping invalid wheel: %r', fname) | |
| 97 continue | |
| 98 | |
| 99 if pip.wheel.Wheel(fname).supported(): | |
| 100 if entry['local']: | |
| 101 link = LOCAL_OBJECT_URL.format(entry['name']) | |
| 102 local_link = link | |
| 103 continue | |
| 104 else: | |
| 105 link = OBJECT_URL.format(entry['name'], md5hash) | |
| 106 if fname.endswith('none-any.whl'): | |
| 107 if generic_link: | |
| 108 LOGGER.error( | |
| 109 'Found more than one generic matching wheel for %r: %r', | |
| 110 prefix, dep) | |
| 111 continue | |
| 112 generic_link = link | |
| 113 elif plat_tag in fname: | |
| 114 if binary_link: | |
| 115 LOGGER.error( | |
| 116 'Found more than one binary matching wheel for %r: %r', | |
| 117 prefix, dep) | |
| 118 continue | |
| 119 binary_link = link | |
| 120 | |
| 121 if not binary_link and not generic_link and not local_link: | |
| 122 raise NoWheelException(name, version, dep['build'], source_sha) | |
| 123 | |
| 124 links.append(local_link or binary_link or generic_link) | |
| 125 | |
| 126 return links | |
| 127 | |
| 128 | |
| 129 @contextlib.contextmanager | |
| 130 def html_index(links): | |
| 131 tf = tempfile.mktemp('.html') | |
| 132 try: | |
| 133 with open(tf, 'w') as f: | |
| 134 print >> f, '<html><body>' | |
| 135 for link in links: | |
| 136 print >> f, '<a href="%s">wat</a>' % link | |
| 137 print >> f, '</body></html>' | |
| 138 yield tf | |
| 139 finally: | |
| 140 os.unlink(tf) | |
| 141 | |
| 142 | |
| 143 def install(deps): | |
| 144 bin_dir = 'Scripts' if sys.platform.startswith('win') else 'bin' | 78 bin_dir = 'Scripts' if sys.platform.startswith('win') else 'bin' |
| 145 pip = os.path.join(sys.prefix, bin_dir, 'pip') | 79 pip = os.path.join(sys.prefix, bin_dir, 'pip') |
| 146 | 80 |
| 147 links = get_links(deps) | 81 requirements = [] |
| 148 with html_index(links) as ipath: | 82 # TODO(iannucci): Do this as a requirements.txt |
| 149 requirements = [] | 83 for name, deps_entry in deps.iteritems(): |
| 150 # TODO(iannucci): Do this as a requirements.txt | 84 if not deps_entry.get('implicit'): |
| 151 for name, deps_entry in deps.iteritems(): | 85 requirements.append('%s==%s' % (name, deps_entry['version'])) |
| 152 if not deps_entry.get('implicit'): | 86 |
| 153 requirements.append('%s==%s' % (name, deps_entry['version'])) | 87 if not cache_root: |
| 154 subprocess.check_call( | 88 cache_root = os.path.join(ROOT, '.bootstrap_cache') |
| 155 [pip, 'install', '--no-index', '--download-cache', | 89 wheel_cache_dir = os.path.join(cache_root, 'wheels') |
| 156 os.path.join(ROOT, '.wheelcache'), '-f', ipath] + requirements) | 90 download_cache_dir = os.path.join(cache_root, 'download_cache') |
| 91 |
| 92 for path in (wheel_cache_dir, download_cache_dir): |
| 93 if not os.path.exists(path): |
| 94 os.makedirs(path) |
| 95 |
| 96 env = os.environ.copy() |
| 97 env['PIP_CACHE_DIR'] = download_cache_dir |
| 98 |
| 99 # Download the package into our cache directory. |
| 100 subprocess.check_call([ |
| 101 pip, 'download', |
| 102 '--find-links', 'file://' + wheel_cache_dir, |
| 103 '--dest', wheel_cache_dir, |
| 104 ] + requirements, |
| 105 env=env) |
| 106 |
| 107 # Install the downloaded packages. |
| 108 subprocess.check_call([ |
| 109 pip, 'install', |
| 110 '--find-links', 'file://' + wheel_cache_dir, |
| 111 '--no-index', |
| 112 ] + requirements, |
| 113 env=env) |
| 157 | 114 |
| 158 | 115 |
| 159 def activate_env(env, deps, quiet=False): | 116 def activate_env(env, deps, quiet=False, cache_root=None): |
| 160 if hasattr(sys, 'real_prefix'): | 117 if hasattr(sys, 'real_prefix'): |
| 161 LOGGER.error('Already activated environment!') | 118 LOGGER.error('Already activated environment!') |
| 162 return | 119 return |
| 163 | 120 |
| 164 if not quiet: | 121 if not quiet: |
| 165 print 'Activating environment: %r' % env | 122 print 'Activating environment: %r' % env |
| 166 assert isinstance(deps, dict) | 123 assert isinstance(deps, dict) |
| 167 | 124 |
| 168 manifest_path = os.path.join(env, 'manifest.pyl') | 125 manifest_path = os.path.join(env, 'manifest.pyl') |
| 169 cur_deps = read_deps(manifest_path) | 126 cur_deps = read_deps(manifest_path) |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 213 # Ensure hermeticity during activation. | 170 # Ensure hermeticity during activation. |
| 214 os.environ.pop('PYTHONPATH', None) | 171 os.environ.pop('PYTHONPATH', None) |
| 215 bin_dir = 'Scripts' if sys.platform.startswith('win') else 'bin' | 172 bin_dir = 'Scripts' if sys.platform.startswith('win') else 'bin' |
| 216 activate_this = os.path.join(env, bin_dir, 'activate_this.py') | 173 activate_this = os.path.join(env, bin_dir, 'activate_this.py') |
| 217 execfile(activate_this, dict(__file__=activate_this)) | 174 execfile(activate_this, dict(__file__=activate_this)) |
| 218 | 175 |
| 219 if cur_deps is None: | 176 if cur_deps is None: |
| 220 if not quiet: | 177 if not quiet: |
| 221 print ' Installing deps' | 178 print ' Installing deps' |
| 222 print_deps(deps, indent=2, with_implicit=False) | 179 print_deps(deps, indent=2, with_implicit=False) |
| 223 install(deps) | 180 install(deps, cache_root) |
| 224 virtualenv.make_environment_relocatable(env) | 181 virtualenv.make_environment_relocatable(env) |
| 225 with open(manifest_path, 'wb') as f: | 182 with open(manifest_path, 'wb') as f: |
| 226 f.write(repr(deps) + '\n') | 183 f.write(repr(deps) + '\n') |
| 227 | 184 |
| 228 # Create bin\python.bat on Windows to unify path where Python is found. | 185 # Create bin\python.bat on Windows to unify path where Python is found. |
| 229 if sys.platform.startswith('win'): | 186 if sys.platform.startswith('win'): |
| 230 bin_path = os.path.join(env, 'bin') | 187 bin_path = os.path.join(env, 'bin') |
| 231 if not os.path.isdir(bin_path): | 188 if not os.path.isdir(bin_path): |
| 232 os.makedirs(bin_path) | 189 os.makedirs(bin_path) |
| 233 python_bat_path = os.path.join(bin_path, 'python.bat') | 190 python_bat_path = os.path.join(bin_path, 'python.bat') |
| 234 if not os.path.isfile(python_bat_path): | 191 if not os.path.isfile(python_bat_path): |
| 235 with open(python_bat_path, 'w') as python_bat_file: | 192 with open(python_bat_path, 'w') as python_bat_file: |
| 236 python_bat_file.write(PYTHON_BAT_WIN) | 193 python_bat_file.write(PYTHON_BAT_WIN) |
| 237 | 194 |
| 238 if not quiet: | 195 if not quiet: |
| 239 print 'Done creating environment' | 196 print 'Done creating environment' |
| 240 | 197 |
| 241 | 198 |
| 242 def main(args): | 199 def main(args): |
| 243 parser = argparse.ArgumentParser() | 200 parser = argparse.ArgumentParser() |
| 244 parser.add_argument('--deps-file', '--deps_file', action='append', | 201 parser.add_argument('--deps-file', '--deps_file', action='append', |
| 245 help='Path to deps.pyl file (may be used multiple times)') | 202 help='Path to deps.pyl file (may be used multiple times)') |
| 203 parser.add_argument('--cache-root', |
| 204 help='Store download/wheel caches in this directory.') |
| 246 parser.add_argument('-q', '--quiet', action='store_true', default=False, | 205 parser.add_argument('-q', '--quiet', action='store_true', default=False, |
| 247 help='Supress all output') | 206 help='Supress all output') |
| 248 parser.add_argument('env_path', | 207 parser.add_argument('env_path', |
| 249 help='Path to place environment (default: %(default)s)', | 208 help='Path to place environment (default: %(default)s)', |
| 250 default='ENV') | 209 default='ENV') |
| 251 opts = parser.parse_args(args) | 210 opts = parser.parse_args(args) |
| 252 | 211 |
| 253 deps = merge_deps(opts.deps_file) | 212 deps = merge_deps(opts.deps_file) |
| 254 activate_env(opts.env_path, deps, opts.quiet) | 213 activate_env(opts.env_path, deps, opts.quiet, cache_root=opts.cache_root) |
| 255 | 214 |
| 256 | 215 |
| 257 if __name__ == '__main__': | 216 if __name__ == '__main__': |
| 258 logging.basicConfig() | 217 logging.basicConfig() |
| 259 LOGGER.setLevel(logging.DEBUG) | 218 LOGGER.setLevel(logging.DEBUG) |
| 260 sys.exit(main(sys.argv[1:])) | 219 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |