|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# -*- python -*- |
| 3 | +""" |
| 4 | +%prog SUBMODULE... |
| 5 | +
|
| 6 | +Hack to pipe submodules of Numpy through 2to3 and build them in-place |
| 7 | +one-by-one. |
| 8 | +
|
| 9 | +Example usage: |
| 10 | +
|
| 11 | + python3 tools/py3tool.py testing distutils core |
| 12 | +
|
| 13 | +This will copy files to _py3k/numpy, add a dummy __init__.py and |
| 14 | +version.py on the top level, and copy and 2to3 the files of the three |
| 15 | +submodules. |
| 16 | +
|
| 17 | +When running py3tool again, only changed files are re-processed, which |
| 18 | +makes the test-bugfix cycle faster. |
| 19 | +
|
| 20 | +""" |
| 21 | +from optparse import OptionParser |
| 22 | +import shutil |
| 23 | +import os |
| 24 | +import sys |
| 25 | +import re |
| 26 | +import subprocess |
| 27 | +import fnmatch |
| 28 | + |
| 29 | +BASE = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) |
| 30 | +TEMP = os.path.normpath(os.path.join(BASE, '_py3k')) |
| 31 | + |
| 32 | +SCRIPT_2TO3 = os.path.join(BASE, 'tools', '2to3.py') |
| 33 | + |
| 34 | +EXTRA_2TO3_FLAGS = { |
| 35 | + '*/*.py': '-x import', |
| 36 | +# 'numpy/core/code_generators/generate_umath.py': '-x import', |
| 37 | +# 'numpy/core/code_generators/generate_numpy_api.py': '-x import', |
| 38 | +# 'numpy/core/code_generators/generate_ufunc_api.py': '-x import', |
| 39 | +# 'numpy/core/defchararray.py': '-x unicode', |
| 40 | +# 'numpy/compat/py3k.py': '-x unicode', |
| 41 | +# 'numpy/ma/timer_comparison.py': 'skip', |
| 42 | +# 'numpy/distutils/system_info.py': '-x reduce', |
| 43 | +# 'numpy/f2py/auxfuncs.py': '-x reduce', |
| 44 | +# 'numpy/lib/arrayterator.py': '-x reduce', |
| 45 | +# 'numpy/lib/tests/test_arrayterator.py': '-x reduce', |
| 46 | +# 'numpy/ma/core.py': '-x reduce', |
| 47 | +# 'numpy/ma/tests/test_core.py': '-x reduce', |
| 48 | +# 'numpy/ma/tests/test_old_ma.py': '-x reduce', |
| 49 | +# 'numpy/ma/timer_comparison.py': '-x reduce', |
| 50 | +# 'numpy/oldnumeric/ma.py': '-x reduce', |
| 51 | +} |
| 52 | + |
| 53 | +def main(): |
| 54 | + p = OptionParser(usage=__doc__.strip()) |
| 55 | + p.add_option("--clean", "-c", action="store_true", |
| 56 | + help="clean source directory") |
| 57 | + options, args = p.parse_args() |
| 58 | + |
| 59 | + if not args: |
| 60 | + p.error('no submodules given') |
| 61 | + else: |
| 62 | + dirs = ['quantities/%s' % x for x in map(os.path.basename, args)] |
| 63 | + |
| 64 | + # Prepare |
| 65 | + if not os.path.isdir(TEMP): |
| 66 | + os.makedirs(TEMP) |
| 67 | + |
| 68 | + # Set up dummy files (for building only submodules) |
| 69 | + dummy_files = { |
| 70 | +# '__init__.py': 'from quantities.version __version__', |
| 71 | +# 'version.py': 'version = "1.4.0.dev"' |
| 72 | + } |
| 73 | + |
| 74 | + for fn, content in dummy_files.items(): |
| 75 | + fn = os.path.join(TEMP, 'quantities', fn) |
| 76 | + if not os.path.isfile(fn): |
| 77 | + try: |
| 78 | + os.makedirs(os.path.dirname(fn)) |
| 79 | + except OSError: |
| 80 | + pass |
| 81 | + f = open(fn, 'wb+') |
| 82 | + f.write(content.encode('ascii')) |
| 83 | + f.close() |
| 84 | + |
| 85 | + # Environment |
| 86 | + pp = [os.path.abspath(TEMP)] |
| 87 | + def getenv(): |
| 88 | + env = dict(os.environ) |
| 89 | + env.update({'PYTHONPATH': ':'.join(pp)}) |
| 90 | + return env |
| 91 | + |
| 92 | + # Copy |
| 93 | + for d in dirs: |
| 94 | + src = os.path.join(BASE, d) |
| 95 | + dst = os.path.join(TEMP, d) |
| 96 | + |
| 97 | + # Run 2to3 |
| 98 | + sync_2to3(dst=dst, |
| 99 | + src=src, |
| 100 | + patchfile=os.path.join(TEMP, os.path.basename(d) + '.patch'), |
| 101 | + clean=options.clean) |
| 102 | + |
| 103 | + # Run setup.py, falling back to Pdb post-mortem on exceptions |
| 104 | + setup_py = os.path.join(dst, 'setup.py') |
| 105 | + if os.path.isfile(setup_py): |
| 106 | + code = """\ |
| 107 | +import pdb, sys, traceback |
| 108 | +p = pdb.Pdb() |
| 109 | +try: |
| 110 | + import __main__ |
| 111 | + __main__.__dict__.update({ |
| 112 | + "__name__": "__main__", "__file__": "setup.py", |
| 113 | + "__builtins__": __builtins__}) |
| 114 | + fp = open("setup.py", "rb") |
| 115 | + try: |
| 116 | + exec(compile(fp.read(), "setup.py", 'exec')) |
| 117 | + finally: |
| 118 | + fp.close() |
| 119 | +except SystemExit: |
| 120 | + raise |
| 121 | +except: |
| 122 | + traceback.print_exc() |
| 123 | + t = sys.exc_info()[2] |
| 124 | + p.interaction(None, t) |
| 125 | +""" |
| 126 | + ret = subprocess.call([sys.executable, '-c', code, |
| 127 | + 'build_ext', '-i'], |
| 128 | + cwd=dst, |
| 129 | + env=getenv()) |
| 130 | + if ret != 0: |
| 131 | + raise RuntimeError("Build failed.") |
| 132 | + |
| 133 | + # Run nosetests |
| 134 | + subprocess.call(['nosetests3', '-v', d], cwd=TEMP) |
| 135 | + |
| 136 | +def custom_mangling(filename): |
| 137 | + import_mangling = [ |
| 138 | +# os.path.join('core', '__init__.py'), |
| 139 | +# os.path.join('core', 'numeric.py'), |
| 140 | +# os.path.join('core', '_internal.py'), |
| 141 | +# os.path.join('core', 'arrayprint.py'), |
| 142 | +# os.path.join('core', 'fromnumeric.py'), |
| 143 | +# os.path.join('numpy', '__init__.py'), |
| 144 | +# os.path.join('lib', 'io.py'), |
| 145 | +# os.path.join('lib', 'function_base.py'), |
| 146 | +# os.path.join('fft', 'fftpack.py'), |
| 147 | +# os.path.join('random', '__init__.py'), |
| 148 | + ] |
| 149 | + |
| 150 | + if any(filename.endswith(x) for x in import_mangling): |
| 151 | + f = open(filename, 'r') |
| 152 | + text = f.read() |
| 153 | + f.close() |
| 154 | + for mod in ['multiarray', 'scalarmath', 'umath', '_sort', |
| 155 | + '_compiled_base', 'core', 'lib', 'testing', 'fft', |
| 156 | + 'polynomial', 'random', 'ma', 'linalg', 'compat', |
| 157 | + 'mtrand']: |
| 158 | + text = re.sub(r'^(\s*)import %s' % mod, |
| 159 | + r'\1from . import %s' % mod, |
| 160 | + text, flags=re.M) |
| 161 | + text = re.sub(r'^(\s*)from %s import' % mod, |
| 162 | + r'\1from .%s import' % mod, |
| 163 | + text, flags=re.M) |
| 164 | + text = text.replace('from matrixlib', 'from .matrixlib') |
| 165 | + f = open(filename, 'w') |
| 166 | + f.write(text) |
| 167 | + f.close() |
| 168 | + |
| 169 | + if filename.endswith(os.path.join('lib', 'io.py')): |
| 170 | + f = open(filename, 'r') |
| 171 | + text = f.read() |
| 172 | + f.close() |
| 173 | + text = text.replace('from . import io', 'import io') |
| 174 | + f = open(filename, 'w') |
| 175 | + f.write(text) |
| 176 | + f.close() |
| 177 | + |
| 178 | +def walk_sync(dir1, dir2, _seen=None): |
| 179 | + if _seen is None: |
| 180 | + seen = {} |
| 181 | + else: |
| 182 | + seen = _seen |
| 183 | + |
| 184 | + if not dir1.endswith(os.path.sep): |
| 185 | + dir1 = dir1 + os.path.sep |
| 186 | + |
| 187 | + # Walk through stuff (which we haven't yet gone through) in dir1 |
| 188 | + for root, dirs, files in os.walk(dir1): |
| 189 | + sub = root[len(dir1):] |
| 190 | + if sub in seen: |
| 191 | + dirs = [x for x in dirs if x not in seen[sub][0]] |
| 192 | + files = [x for x in files if x not in seen[sub][1]] |
| 193 | + seen[sub][0].extend(dirs) |
| 194 | + seen[sub][1].extend(files) |
| 195 | + else: |
| 196 | + seen[sub] = (dirs, files) |
| 197 | + if not dirs and not files: |
| 198 | + continue |
| 199 | + yield os.path.join(dir1, sub), os.path.join(dir2, sub), dirs, files |
| 200 | + |
| 201 | + if _seen is None: |
| 202 | + # Walk through stuff (which we haven't yet gone through) in dir2 |
| 203 | + for root2, root1, dirs, files in walk_sync(dir2, dir1, _seen=seen): |
| 204 | + yield root1, root2, dirs, files |
| 205 | + |
| 206 | +def sync_2to3(src, dst, patchfile=None, clean=False): |
| 207 | + import lib2to3.main |
| 208 | + from io import StringIO |
| 209 | + |
| 210 | + to_convert = [] |
| 211 | + |
| 212 | + for src_dir, dst_dir, dirs, files in walk_sync(src, dst): |
| 213 | + for fn in dirs + files: |
| 214 | + src_fn = os.path.join(src_dir, fn) |
| 215 | + dst_fn = os.path.join(dst_dir, fn) |
| 216 | + |
| 217 | + # skip temporary etc. files |
| 218 | + if fn.startswith('.#') or fn.endswith('~'): |
| 219 | + continue |
| 220 | + |
| 221 | + # remove non-existing |
| 222 | + if os.path.exists(dst_fn) and not os.path.exists(src_fn): |
| 223 | + if clean: |
| 224 | + if os.path.isdir(dst_fn): |
| 225 | + shutil.rmtree(dst_fn) |
| 226 | + else: |
| 227 | + os.unlink(dst_fn) |
| 228 | + continue |
| 229 | + |
| 230 | + # make directories |
| 231 | + if os.path.isdir(src_fn): |
| 232 | + if not os.path.isdir(dst_fn): |
| 233 | + os.makedirs(dst_fn) |
| 234 | + continue |
| 235 | + |
| 236 | + dst_dir = os.path.dirname(dst_fn) |
| 237 | + if os.path.isfile(dst_fn) and not os.path.isdir(dst_dir): |
| 238 | + os.makedirs(dst_dir) |
| 239 | + |
| 240 | + # don't replace up-to-date files |
| 241 | + try: |
| 242 | + if os.path.isfile(dst_fn) and \ |
| 243 | + os.stat(dst_fn).st_mtime >= os.stat(src_fn).st_mtime: |
| 244 | + continue |
| 245 | + except OSError: |
| 246 | + pass |
| 247 | + |
| 248 | + # copy file |
| 249 | + shutil.copyfile(src_fn, dst_fn) |
| 250 | + |
| 251 | + # add .py files to 2to3 list |
| 252 | + if dst_fn.endswith('.py'): |
| 253 | + to_convert.append((src_fn, dst_fn)) |
| 254 | + |
| 255 | + # run 2to3 |
| 256 | + flag_sets = {} |
| 257 | + for fn, dst_fn in to_convert: |
| 258 | + flag = '' |
| 259 | + for pat, opt in EXTRA_2TO3_FLAGS.items(): |
| 260 | + if fnmatch.fnmatch(fn, pat): |
| 261 | + flag = opt |
| 262 | + break |
| 263 | + flag_sets.setdefault(flag, []).append(dst_fn) |
| 264 | + |
| 265 | + if patchfile: |
| 266 | + p = open(patchfile, 'wb+') |
| 267 | + else: |
| 268 | + p = open(os.devnull, 'wb') |
| 269 | + |
| 270 | + for flags, filenames in flag_sets.items(): |
| 271 | + if flags == 'skip': |
| 272 | + continue |
| 273 | + |
| 274 | + _old_stdout = sys.stdout |
| 275 | + try: |
| 276 | + sys.stdout = StringIO() |
| 277 | + lib2to3.main.main("lib2to3.fixes", ['-w'] + flags.split()+filenames) |
| 278 | + finally: |
| 279 | + sys.stdout = _old_stdout |
| 280 | + |
| 281 | + for fn, dst_fn in to_convert: |
| 282 | + # perform custom mangling |
| 283 | + custom_mangling(dst_fn) |
| 284 | + |
| 285 | + p.close() |
| 286 | + |
| 287 | +if __name__ == "__main__": |
| 288 | + main() |
0 commit comments