merge
diff --git a/Lib/imp.py b/Lib/imp.py
index 03b832e..dbec8d3 100644
--- a/Lib/imp.py
+++ b/Lib/imp.py
@@ -10,11 +10,10 @@
                   load_dynamic, get_frozen_object, is_frozen_package,
                   init_builtin, init_frozen, is_builtin, is_frozen,
                   _fix_co_filename)
-# Can (probably) move to importlib
-from _imp import (get_tag, get_suffixes, cache_from_source,
-                  source_from_cache)
 # Could move out of _imp, but not worth the code
 from _imp import get_magic
+# Can (probably) move to importlib
+from _imp import (get_tag, get_suffixes)
 # Should be re-implemented here (and mostly deprecated)
 from _imp import (find_module, NullImporter,
                   SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION,
@@ -22,11 +21,32 @@
                   PY_CODERESOURCE, IMP_HOOK)
 
 from importlib._bootstrap import _new_module as new_module
+from importlib._bootstrap import _cache_from_source as cache_from_source
 
 from importlib import _bootstrap
 import os
 
 
+def source_from_cache(path):
+    """Given the path to a .pyc./.pyo file, return the path to its .py file.
+
+    The .pyc/.pyo file does not need to exist; this simply returns the path to
+    the .py file calculated to correspond to the .pyc/.pyo file.  If path does
+    not conform to PEP 3147 format, ValueError will be raised.
+
+    """
+    head, pycache_filename = os.path.split(path)
+    head, pycache = os.path.split(head)
+    if pycache != _bootstrap.PYCACHE:
+        raise ValueError('{} not bottom-level directory in '
+                         '{!r}'.format(_bootstrap.PYCACHE, path))
+    if pycache_filename.count('.') != 2:
+        raise ValueError('expected only 2 dots in '
+                         '{!r}'.format(pycache_filename))
+    base_filename = pycache_filename.partition('.')[0]
+    return os.path.join(head, base_filename + _bootstrap.SOURCE_SUFFIXES[0])
+
+
 class _HackedGetData:
 
     """Compatibiilty support for 'file' arguments of various load_*()
@@ -55,6 +75,7 @@
     """Compatibility support for implementing load_source()."""
 
 
+# XXX deprecate after better API exposed in importlib
 def load_source(name, pathname, file=None):
     return _LoadSourceCompatibility(name, pathname, file).load_module(name)
 
@@ -65,10 +86,12 @@
     """Compatibility support for implementing load_compiled()."""
 
 
+# XXX deprecate
 def load_compiled(name, pathname, file=None):
     return _LoadCompiledCompatibility(name, pathname, file).load_module(name)
 
 
+# XXX deprecate
 def load_package(name, path):
     if os.path.isdir(path):
         extensions = _bootstrap._suffix_list(PY_SOURCE)
@@ -82,6 +105,7 @@
     return _bootstrap._SourceFileLoader(name, path).load_module(name)
 
 
+# XXX deprecate
 def load_module(name, file, filename, details):
     """Load a module, given information returned by find_module().
 
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index 9bc6505..ea98a68 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -7,11 +7,8 @@
 
 """
 
-# Injected modules are '_warnings', '_imp', 'sys', 'marshal', '_io',
-# and '_os' (a.k.a. 'posix', 'nt' or 'os2').
-# Injected attribute is path_sep.
-# Most injection is handled by _setup().
-#
+# See importlib._setup() for what is injected into the global namespace.
+
 # When editing this code be aware that code executed at import time CANNOT
 # reference any injected objects! This includes not only global code but also
 # anything specified at the class level.
@@ -64,12 +61,23 @@
     return x
 
 
-
-# XXX Could also expose Modules/getpath.c:joinpath()
 def _path_join(*args):
-    """Replacement for os.path.join."""
-    return path_sep.join(x[:-len(path_sep)] if x.endswith(path_sep) else x
-                         for x in args if x)
+    """Replacement for os.path.join()."""
+    sep = path_sep if args[0][-1:] not in path_separators else args[0][-1]
+    return sep.join(x[:-len(path_sep)] if x.endswith(path_sep) else x
+                    for x in args if x)
+
+
+def _path_split(path):
+    """Replacement for os.path.split()."""
+    for x in reversed(path):
+        if x in path_separators:
+            sep = x
+            break
+    else:
+        sep = path_sep
+    front, _, tail = path.rpartition(sep)
+    return front, tail
 
 
 def _path_exists(path):
@@ -170,6 +178,33 @@
 
 # Finder/loader utility code ##################################################
 
+PYCACHE = '__pycache__'
+
+SOURCE_SUFFIXES = ['.py']  # _setup() adds .pyw as needed.
+
+DEBUG_BYTECODE_SUFFIX = '.pyc'
+OPT_BYTECODE_SUFFIX = '.pyo'
+BYTECODE_SUFFIX = DEBUG_BYTECODE_SUFFIX if __debug__ else OPT_BYTECODE_SUFFIX
+
+def _cache_from_source(path, debug_override=None):
+    """Given the path to a .py file, return the path to its .pyc/.pyo file.
+
+    The .py file does not need to exist; this simply returns the path to the
+    .pyc/.pyo file calculated as if the .py file were imported.  The extension
+    will be .pyc unless __debug__ is not defined, then it will be .pyo.
+
+    If debug_override is not None, then it must be a boolean and is taken as
+    the value of __debug__ instead.
+
+    """
+    debug = __debug__ if debug_override is None else debug_override
+    suffix = DEBUG_BYTECODE_SUFFIX if debug else OPT_BYTECODE_SUFFIX
+    head, tail = _path_split(path)
+    base_filename, sep, _ = tail.partition('.')
+    filename = ''.join([base_filename, sep, _imp.get_tag(), suffix])
+    return _path_join(head, PYCACHE, filename)
+
+
 def verbose_message(message, *args):
     """Print the message to stderr if -v/PYTHONVERBOSE is turned on."""
     if sys.flags.verbose:
@@ -388,7 +423,7 @@
     def is_package(self, fullname):
         """Concrete implementation of InspectLoader.is_package by checking if
         the path returned by get_filename has a filename of '__init__.py'."""
-        filename = self.get_filename(fullname).rpartition(path_sep)[2]
+        filename = _path_split(self.get_filename(fullname))[1]
         return filename.rsplit('.', 1)[0] == '__init__'
 
     def _bytes_from_bytecode(self, fullname, data, bytecode_path, source_stats):
@@ -444,12 +479,12 @@
         code_object = self.get_code(name)
         module.__file__ = self.get_filename(name)
         if not sourceless:
-            module.__cached__ = _imp.cache_from_source(module.__file__)
+            module.__cached__ = _cache_from_source(module.__file__)
         else:
             module.__cached__ = module.__file__
         module.__package__ = name
         if self.is_package(name):
-            module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
+            module.__path__ = [_path_split(module.__file__)[0]]
         else:
             module.__package__ = module.__package__.rpartition('.')[0]
         module.__loader__ = self
@@ -507,7 +542,7 @@
 
         """
         source_path = self.get_filename(fullname)
-        bytecode_path = _imp.cache_from_source(source_path)
+        bytecode_path = _cache_from_source(source_path)
         source_mtime = None
         if bytecode_path is not None:
             try:
@@ -546,9 +581,6 @@
         verbose_message('code object from {}', source_path)
         if (not sys.dont_write_bytecode and bytecode_path is not None and
             source_mtime is not None):
-            # If e.g. Jython ever implements imp.cache_from_source to have
-            # their own cached file format, this block of code will most likely
-            # throw an exception.
             data = bytearray(_MAGIC_NUMBER)
             data.extend(_w_long(source_mtime))
             data.extend(_w_long(len(source_bytes)))
@@ -604,11 +636,11 @@
 
     def set_data(self, path, data):
         """Write bytes data to a file."""
-        parent, _, filename = path.rpartition(path_sep)
+        parent, filename = _path_split(path)
         path_parts = []
         # Figure out what directories are missing.
         while parent and not _path_isdir(parent):
-            parent, _, part = parent.rpartition(path_sep)
+            parent, part = _path_split(parent)
             path_parts.append(part)
         # Create needed directories.
         for part in reversed(path_parts):
@@ -1142,7 +1174,9 @@
             builtin_module = sys.modules[builtin_name]
         setattr(self_module, builtin_name, builtin_module)
 
-    for builtin_os, path_sep in [('posix', '/'), ('nt', '\\'), ('os2', '\\')]:
+    os_details = ('posix', ['/']), ('nt', ['\\', '/']), ('os2', ['\\', '/'])
+    for builtin_os, path_separators in os_details:
+        path_sep = path_separators[0]
         if builtin_os in sys.modules:
             os_module = sys.modules[builtin_os]
             break
@@ -1151,7 +1185,7 @@
                 os_module = BuiltinImporter.load_module(builtin_os)
                 # TODO: rip out os2 code after 3.3 is released as per PEP 11
                 if builtin_os == 'os2' and 'EMX GCC' in sys.version:
-                    path_sep = '/'
+                    path_sep = path_separators[1]
                 break
             except ImportError:
                 continue
@@ -1159,9 +1193,12 @@
         raise ImportError('importlib requires posix or nt')
     setattr(self_module, '_os', os_module)
     setattr(self_module, 'path_sep', path_sep)
+    setattr(self_module, 'path_separators', set(path_separators))
     # Constants
     setattr(self_module, '_relax_case', _make_relax_case())
     setattr(self_module, '_MAGIC_NUMBER', _imp_module.get_magic())
+    if builtin_os == 'nt':
+        SOURCE_SUFFIXES.append('.pyw')
 
 
 def _install(sys_module, _imp_module):
diff --git a/Python/import.c b/Python/import.c
index 11d58ae..bcf6bd7 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -783,7 +783,6 @@
 
 static PyObject * get_sourcefile(PyObject *filename);
 static PyObject *make_source_pathname(PyObject *pathname);
-static PyObject* make_compiled_pathname(PyObject *pathname, int debug);
 
 /* Execute a code object in a module and return the module object
  * WITH INCREMENTED REFERENCE COUNT.  If an error occurs, name is
@@ -924,71 +923,6 @@
     return found;
 }
 
-/* Given a pathname for a Python source file, fill a buffer with the
-   pathname for the corresponding compiled file.  Return the pathname
-   for the compiled file, or NULL if there's no space in the buffer.
-   Doesn't set an exception.
-
-   foo.py -> __pycache__/foo.<tag>.pyc
-
-   pathstr is assumed to be "ready".
-*/
-
-static PyObject*
-make_compiled_pathname(PyObject *pathstr, int debug)
-{
-    PyObject *result;
-    Py_ssize_t fname, ext, len, i, pos, taglen;
-    Py_ssize_t pycache_len = sizeof(CACHEDIR) - 1;
-    int kind;
-    void *data;
-    Py_UCS4 lastsep;
-
-    /* Compute the output string size. */
-    len = PyUnicode_GET_LENGTH(pathstr);
-    /* If there is no separator, this returns -1, so
-       fname will be 0. */
-    fname = rightmost_sep_obj(pathstr, 0, len) + 1;
-    /* Windows: re-use the last separator character (/ or \\) when
-       appending the __pycache__ path. */
-    if (fname > 0)
-        lastsep = PyUnicode_READ_CHAR(pathstr, fname -1);
-    else
-        lastsep = SEP;
-    ext = fname - 1;
-    for(i = fname; i < len; i++)
-        if (PyUnicode_READ_CHAR(pathstr, i) == '.')
-            ext = i + 1;
-    if (ext < fname)
-        /* No dot in filename; use entire filename */
-        ext = len;
-
-    /* result = pathstr[:fname] + "__pycache__" + SEP +
-                pathstr[fname:ext] + tag + ".py[co]" */
-    taglen = strlen(pyc_tag);
-    result = PyUnicode_New(ext + pycache_len + 1 + taglen + 4,
-                           PyUnicode_MAX_CHAR_VALUE(pathstr));
-    if (!result)
-        return NULL;
-    kind = PyUnicode_KIND(result);
-    data = PyUnicode_DATA(result);
-    PyUnicode_CopyCharacters(result, 0, pathstr, 0, fname);
-    pos = fname;
-    for (i = 0; i < pycache_len; i++)
-        PyUnicode_WRITE(kind, data, pos++, CACHEDIR[i]);
-    PyUnicode_WRITE(kind, data, pos++, lastsep);
-    PyUnicode_CopyCharacters(result, pos, pathstr,
-                             fname, ext - fname);
-    pos += ext - fname;
-    for (i = 0; pyc_tag[i]; i++)
-        PyUnicode_WRITE(kind, data, pos++, pyc_tag[i]);
-    PyUnicode_WRITE(kind, data, pos++, '.');
-    PyUnicode_WRITE(kind, data, pos++, 'p');
-    PyUnicode_WRITE(kind, data, pos++, 'y');
-    PyUnicode_WRITE(kind, data, pos++, debug ? 'c' : 'o');
-    return result;
-}
-
 
 /* Given a pathname to a Python byte compiled file, return the path to the
    source file, if the path matches the PEP 3147 format.  This does not check
@@ -2991,79 +2925,6 @@
 \n\
 Reload the module.  The module must have been successfully imported before.");
 
-static PyObject *
-imp_cache_from_source(PyObject *self, PyObject *args, PyObject *kws)
-{
-    static char *kwlist[] = {"path", "debug_override", NULL};
-
-    PyObject *pathname, *cpathname;
-    PyObject *debug_override = NULL;
-    int debug = !Py_OptimizeFlag;
-
-    if (!PyArg_ParseTupleAndKeywords(
-                args, kws, "O&|O", kwlist,
-                PyUnicode_FSDecoder, &pathname, &debug_override))
-        return NULL;
-
-    if (debug_override != NULL &&
-        (debug = PyObject_IsTrue(debug_override)) < 0) {
-        Py_DECREF(pathname);
-        return NULL;
-    }
-
-    if (PyUnicode_READY(pathname) < 0)
-        return NULL;
-
-    cpathname = make_compiled_pathname(pathname, debug);
-    Py_DECREF(pathname);
-
-    if (cpathname == NULL) {
-        PyErr_Format(PyExc_SystemError, "path buffer too short");
-        return NULL;
-    }
-    return cpathname;
-}
-
-PyDoc_STRVAR(doc_cache_from_source,
-"cache_from_source(path, [debug_override]) -> path\n\
-Given the path to a .py file, return the path to its .pyc/.pyo file.\n\
-\n\
-The .py file does not need to exist; this simply returns the path to the\n\
-.pyc/.pyo file calculated as if the .py file were imported.  The extension\n\
-will be .pyc unless __debug__ is not defined, then it will be .pyo.\n\
-\n\
-If debug_override is not None, then it must be a boolean and is taken as\n\
-the value of __debug__ instead.");
-
-static PyObject *
-imp_source_from_cache(PyObject *self, PyObject *args, PyObject *kws)
-{
-    static char *kwlist[] = {"path", NULL};
-    PyObject *pathname, *source;
-
-    if (!PyArg_ParseTupleAndKeywords(
-                args, kws, "O&", kwlist,
-                PyUnicode_FSDecoder, &pathname))
-        return NULL;
-
-    source = make_source_pathname(pathname);
-    if (source == NULL) {
-        PyErr_Format(PyExc_ValueError, "Not a PEP 3147 pyc path: %R",
-                     pathname);
-        Py_DECREF(pathname);
-        return NULL;
-    }
-    Py_DECREF(pathname);
-    return source;
-}
-
-PyDoc_STRVAR(doc_source_from_cache,
-"source_from_cache(path) -> path\n\
-Given the path to a .pyc./.pyo file, return the path to its .py file.\n\
-\n\
-The .pyc/.pyo file does not need to exist; this simply returns the path to\n\
-the .py file calculated to correspond to the .pyc/.pyo file.  If path\n\
-does not conform to PEP 3147 format, ValueError will be raised.");
 
 /* Doc strings */
 
@@ -3116,11 +2977,6 @@
     {"acquire_lock", imp_acquire_lock, METH_NOARGS,  doc_acquire_lock},
     {"release_lock", imp_release_lock, METH_NOARGS,  doc_release_lock},
     {"reload",       imp_reload,       METH_O,       doc_reload},
-    {"cache_from_source", (PyCFunction)imp_cache_from_source,
-     METH_VARARGS | METH_KEYWORDS, doc_cache_from_source},
-    {"source_from_cache", (PyCFunction)imp_source_from_cache,
-     METH_VARARGS | METH_KEYWORDS, doc_source_from_cache},
-    /* The rest are obsolete */
     {"get_frozen_object",       imp_get_frozen_object,  METH_VARARGS},
     {"is_frozen_package",   imp_is_frozen_package,  METH_VARARGS},
     {"init_builtin",            imp_init_builtin,       METH_VARARGS},
diff --git a/Python/importlib.h b/Python/importlib.h
index 0e2c734..c369e3c 100644
--- a/Python/importlib.h
+++ b/Python/importlib.h
Binary files differ