Repository: pypa/setuptools
Pull request #1855 (2019-10-03 16:47:23)

Upgrade setuptools.depends to importlib from depracated imp
### Summary
Fix;
```console
(.venv) batuhan@x200-trisquel:~/astor$ python setup.py develop
/home/batuhan/.venv/lib/python3.8/site-packages/setuptools/depends.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
```

### Pull Request Checklist
- [x] Changes have tests
- [x] News fragment added in changelog.d. See [documentation](http://setuptools.readthedocs.io/en/latest/developer-guide.html#making-a-pull-request) for details (already added about same subject


isidentical: apparently the error is related with https://github.com/pytest-dev/pytest/issues/2506
isidentical: I'll change the `get_code` api calls to manually compiling / marshalling.
isidentical: I dont know why but when i force pushed my branch, github auto closed this PR for me? I can't reopen it either so i'll make another one
![image](https://user-images.githubusercontent.com/47358913/66223253-af03d100-e6db-11e9-89d0-2313214ad32e.png)

isidentical: All done, it is working as expectedly. No API changed.

Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index aeefc8f2c..5e7e8ee4a 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 41.5.0
+current_version = 41.2.0
 commit = True
 tag = True
 
diff --git a/.travis.yml b/.travis.yml
index 7088d1662..8b7cece8b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,9 +16,8 @@ jobs:
   - python: 3.5
   - &default_py
     python: 3.6
-  - python: 3.7
   - &latest_py3
-    python: 3.8
+    python: 3.7
   - <<: *latest_py3
     env: LANG=C
   - python: 3.8-dev
diff --git a/CHANGES.rst b/CHANGES.rst
index 7e9fc0f6e..dd47cb435 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,30 +1,3 @@
-v41.5.0
--------
-
-* #1811: Improve Visual C++ 14.X support, mainly for Visual Studio 2017 and 2019.
-* #1814: Fix ``pkg_resources.Requirement`` hash/equality implementation: take PEP 508 direct URL into account.
-* #1824: Fix tests when running under ``python3.10``.
-* #1878: Formally deprecated the ``test`` command, with the recommendation that users migrate to ``tox``.
-* #1860: Update documentation to mention the egg format is not supported by pip and dependency links support was dropped starting with pip 19.0.
-* #1862: Drop ez_setup documentation: deprecated for some time (last updated in 2016), and still relying on easy_install (deprecated too).
-* #1868: Drop most documentation references to (deprecated) EasyInstall.
-* #1884: Added a trove classifier to document support for Python 3.8.
-* #1886: Added Python 3.8 release to the Travis test matrix.
-
-
-v41.4.0
--------
-
-* #1847: In declarative config, now traps errors when invalid ``python_requires`` values are supplied.
-
-
-v41.3.0
--------
-
-* #1690: When storing extras, rely on OrderedSet to retain order of extras as indicated by the packager, which will also be deterministic on Python 2.7 (with PYTHONHASHSEED unset) and Python 3.6+.
-* #1858: Fixed failing integration test triggered by 'long_description_content_type' in packaging.
-
-
 v41.2.0
 -------
 
diff --git a/changelog.d/479.bugfix.rst b/changelog.d/479.bugfix.rst
new file mode 100644
index 000000000..3c33964ed
--- /dev/null
+++ b/changelog.d/479.bugfix.rst
@@ -0,0 +1,1 @@
+Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``.
diff --git a/docs/development.txt b/docs/development.txt
index 28e653fea..455f038af 100644
--- a/docs/development.txt
+++ b/docs/development.txt
@@ -7,7 +7,7 @@ Authority (PyPA) and led by Jason R. Coombs.
 
 This document describes the process by which Setuptools is developed.
 This document assumes the reader has some passing familiarity with
-*using* setuptools, the ``pkg_resources`` module, and pip.  It
+*using* setuptools, the ``pkg_resources`` module, and EasyInstall.  It
 does not attempt to explain basic concepts like inter-project
 dependencies, nor does it contain detailed lexical syntax for most
 file formats.  Neither does it explain concepts like "namespace
diff --git a/docs/easy_install.txt b/docs/easy_install.txt
index 544b9efd5..aa11f8908 100644
--- a/docs/easy_install.txt
+++ b/docs/easy_install.txt
@@ -317,8 +317,8 @@ Note that instead of changing your ``PATH`` to include the Python scripts
 directory, you can also retarget the installation location for scripts so they
 go on a directory that's already on the ``PATH``.  For more information see
 `Command-Line Options`_ and `Configuration Files`_.  During installation,
-pass command line options (such as ``--script-dir``) to control where
-scripts will be installed.
+pass command line options (such as ``--script-dir``) to
+``ez_setup.py`` to control where ``easy_install.exe`` will be installed.
 
 
 Windows Executable Launcher
diff --git a/docs/ez_setup.txt b/docs/ez_setup.txt
new file mode 100644
index 000000000..0126fee30
--- /dev/null
+++ b/docs/ez_setup.txt
@@ -0,0 +1,195 @@
+:orphan:
+
+``ez_setup`` distribution guide 
+===============================
+
+Using ``setuptools``...  Without bundling it!
+---------------------------------------------
+
+.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
+
+.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py
+
+.. _EasyInstall Installation Instructions: easy_install.html
+
+.. _Custom Installation Locations: easy_install.html
+
+Your users might not have ``setuptools`` installed on their machines, or even
+if they do, it might not be the right version.  Fixing this is easy; just
+download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
+script.  (Be sure to add it to your revision control system, too.)  Then add
+these two lines to the very top of your setup script, before the script imports
+anything from setuptools:
+
+.. code-block:: python
+
+    import ez_setup
+    ez_setup.use_setuptools()
+
+That's it.  The ``ez_setup`` module will automatically download a matching
+version of ``setuptools`` from PyPI, if it isn't present on the target system.
+Whenever you install an updated version of setuptools, you should also update
+your projects' ``ez_setup.py`` files, so that a matching version gets installed
+on the target machine(s).
+
+(By the way, if you need to distribute a specific version of ``setuptools``,
+you can specify the exact version and base download URL as parameters to the
+``use_setuptools()`` function.  See the function's docstring for details.)
+
+
+What Your Users Should Know
+---------------------------
+
+In general, a setuptools-based project looks just like any distutils-based
+project -- as long as your users have an internet connection and are installing
+to ``site-packages``, that is.  But for some users, these conditions don't
+apply, and they may become frustrated if this is their first encounter with
+a setuptools-based project.  To keep these users happy, you should review the
+following topics in your project's installation instructions, if they are
+relevant to your project and your target audience isn't already familiar with
+setuptools and ``easy_install``.
+
+Network Access
+    If your project is using ``ez_setup``, you should inform users of the
+    need to either have network access, or to preinstall the correct version of
+    setuptools using the `EasyInstall installation instructions`_.  Those
+    instructions also have tips for dealing with firewalls as well as how to
+    manually download and install setuptools.
+
+Custom Installation Locations
+    You should inform your users that if they are installing your project to
+    somewhere other than the main ``site-packages`` directory, they should
+    first install setuptools using the instructions for `Custom Installation
+    Locations`_, before installing your project.
+
+Your Project's Dependencies
+    If your project depends on other projects that may need to be downloaded
+    from PyPI or elsewhere, you should list them in your installation
+    instructions, or tell users how to find out what they are.  While most
+    users will not need this information, any users who don't have unrestricted
+    internet access may have to find, download, and install the other projects
+    manually.  (Note, however, that they must still install those projects
+    using ``easy_install``, or your project will not know they are installed,
+    and your setup script will try to download them again.)
+
+    If you want to be especially friendly to users with limited network access,
+    you may wish to build eggs for your project and its dependencies, making
+    them all available for download from your site, or at least create a page
+    with links to all of the needed eggs.  In this way, users with limited
+    network access can manually download all the eggs to a single directory,
+    then use the ``-f`` option of ``easy_install`` to specify the directory
+    to find eggs in.  Users who have full network access can just use ``-f``
+    with the URL of your download page, and ``easy_install`` will find all the
+    needed eggs using your links directly.  This is also useful when your
+    target audience isn't able to compile packages (e.g. most Windows users)
+    and your package or some of its dependencies include C code.
+
+Revision Control System Users and Co-Developers
+    Users and co-developers who are tracking your in-development code using
+    a revision control system should probably read this manual's sections
+    regarding such development.  Alternately, you may wish to create a
+    quick-reference guide containing the tips from this manual that apply to
+    your particular situation.  For example, if you recommend that people use
+    ``setup.py develop`` when tracking your in-development code, you should let
+    them know that this needs to be run after every update or commit.
+
+    Similarly, if you remove modules or data files from your project, you
+    should remind them to run ``setup.py clean --all`` and delete any obsolete
+    ``.pyc`` or ``.pyo``.  (This tip applies to the distutils in general, not
+    just setuptools, but not everybody knows about them; be kind to your users
+    by spelling out your project's best practices rather than leaving them
+    guessing.)
+
+Creating System Packages
+    Some users want to manage all Python packages using a single package
+    manager, and sometimes that package manager isn't ``easy_install``!
+    Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and
+    ``bdist_dumb`` formats for system packaging.  If a user has a locally-
+    installed "bdist" packaging tool that internally uses the distutils
+    ``install`` command, it should be able to work with ``setuptools``.  Some
+    examples of "bdist" formats that this should work with include the
+    ``bdist_nsi`` and ``bdist_msi`` formats for Windows.
+
+    However, packaging tools that build binary distributions by running
+    ``setup.py install`` on the command line or as a subprocess will require
+    modification to work with setuptools.  They should use the
+    ``--single-version-externally-managed`` option to the ``install`` command,
+    combined with the standard ``--root`` or ``--record`` options.
+    See the `install command`_ documentation below for more details.  The
+    ``bdist_deb`` command is an example of a command that currently requires
+    this kind of patching to work with setuptools.
+
+    Please note that building system packages may require you to install
+    some system software, for example ``bdist_rpm`` requires the ``rpmbuild``
+    command to be installed.
+
+    If you or your users have a problem building a usable system package for
+    your project, please report the problem via the mailing list so that
+    either the "bdist" tool in question or setuptools can be modified to
+    resolve the issue.
+
+Your users might not have ``setuptools`` installed on their machines, or even
+if they do, it might not be the right version.  Fixing this is easy; just
+download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
+script.  (Be sure to add it to your revision control system, too.)  Then add
+these two lines to the very top of your setup script, before the script imports
+anything from setuptools:
+
+.. code-block:: python
+
+    import ez_setup
+    ez_setup.use_setuptools()
+
+That's it.  The ``ez_setup`` module will automatically download a matching
+version of ``setuptools`` from PyPI, if it isn't present on the target system.
+Whenever you install an updated version of setuptools, you should also update
+your projects' ``ez_setup.py`` files, so that a matching version gets installed
+on the target machine(s).
+
+(By the way, if you need to distribute a specific version of ``setuptools``,
+you can specify the exact version and base download URL as parameters to the
+``use_setuptools()`` function.  See the function's docstring for details.)
+
+.. _install command:
+
+``install`` - Run ``easy_install`` or old-style installation
+============================================================
+
+The setuptools ``install`` command is basically a shortcut to run the
+``easy_install`` command on the current project.  However, for convenience
+in creating "system packages" of setuptools-based projects, you can also
+use this option:
+
+``--single-version-externally-managed``
+    This boolean option tells the ``install`` command to perform an "old style"
+    installation, with the addition of an ``.egg-info`` directory so that the
+    installed project will still have its metadata available and operate
+    normally.  If you use this option, you *must* also specify the ``--root``
+    or ``--record`` options (or both), because otherwise you will have no way
+    to identify and remove the installed files.
+
+This option is automatically in effect when ``install`` is invoked by another
+distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm``
+will create system packages of eggs.  It is also automatically in effect if
+you specify the ``--root`` option.
+
+
+``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages``
+==============================================================================
+
+Setuptools runs this command as part of ``install`` operations that use the
+``--single-version-externally-managed`` options.  You should not invoke it
+directly; it is documented here for completeness and so that distutils
+extensions such as system package builders can make use of it.  This command
+has only one option:
+
+``--install-dir=DIR, -d DIR``
+    The parent directory where the ``.egg-info`` directory will be placed.
+    Defaults to the same as the ``--install-dir`` option specified for the
+    ``install_lib`` command, which is usually the system ``site-packages``
+    directory.
+
+This command assumes that the ``egg_info`` command has been given valid options
+via the command line or ``setup.cfg``, as it will invoke the ``egg_info``
+command and use its options to locate the project's source ``.egg-info``
+directory.
diff --git a/docs/formats.txt b/docs/formats.txt
index 6c0456de2..a182eb99f 100644
--- a/docs/formats.txt
+++ b/docs/formats.txt
@@ -299,8 +299,11 @@ specified by the ``setup_requires`` parameter to the Distribution.
 A list of dependency URLs, one per line, as specified using the
 ``dependency_links`` keyword to ``setup()``.  These may be direct
 download URLs, or the URLs of web pages containing direct download
-links. Please see the setuptools manual for more information on
-specifying this option.
+links, and will be used by EasyInstall to find dependencies, as though
+the user had manually provided them via the ``--find-links`` command
+line option.  Please see the setuptools manual and EasyInstall manual
+for more information on specifying this option, and for information on
+how EasyInstall processes ``--find-links`` URLs.
 
 
 ``depends.txt`` -- Obsolete, do not create!
diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt
index b887a9239..806f1b146 100644
--- a/docs/pkg_resources.txt
+++ b/docs/pkg_resources.txt
@@ -245,8 +245,8 @@ abbreviation for ``pkg_resources.working_set.require()``:
     interactive interpreter hacking than for production use. If you're creating
     an actual library or application, it's strongly recommended that you create
     a "setup.py" script using ``setuptools``, and declare all your requirements
-    there.  That way, tools like pip can automatically detect what requirements
-    your package has, and deal with them accordingly.
+    there.  That way, tools like EasyInstall can automatically detect what
+    requirements your package has, and deal with them accordingly.
 
     Note that calling ``require('SomePackage')`` will not install
     ``SomePackage`` if it isn't already present.  If you need to do this, you
@@ -611,9 +611,9 @@ Requirements Parsing
     or activation of both Report-O-Rama and any libraries it needs in order to
     provide PDF support.  For example, you could use::
 
-        pip install Report-O-Rama[PDF]
+        easy_install.py Report-O-Rama[PDF]
 
-    To install the necessary packages using pip, or call
+    To install the necessary packages using the EasyInstall program, or call
     ``pkg_resources.require('Report-O-Rama[PDF]')`` to add the necessary
     distributions to sys.path at runtime.
 
@@ -1843,9 +1843,9 @@ History
      because it isn't necessarily a filesystem path (and hasn't been for some
      time now).  The ``location`` of ``Distribution`` objects in the filesystem
      should always be normalized using ``pkg_resources.normalize_path()``; all
-     of the setuptools' code that generates distributions from the filesystem
-     (including ``Distribution.from_filename()``) ensure this invariant, but if
-     you use a more generic API like ``Distribution()`` or
+     of the setuptools and EasyInstall code that generates distributions from
+     the filesystem (including ``Distribution.from_filename()``) ensure this
+     invariant, but if you use a more generic API like ``Distribution()`` or
      ``Distribution.from_location()`` you should take care that you don't
      create a distribution with an un-normalized filesystem path.
 
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index 344ea5bc3..2e7fe3bd9 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -8,10 +8,23 @@ distribute Python packages, especially ones that have dependencies on other
 packages.
 
 Packages built and distributed using ``setuptools`` look to the user like
-ordinary Python packages based on the ``distutils``.
+ordinary Python packages based on the ``distutils``.  Your users don't need to
+install or even know about setuptools in order to use them, and you don't
+have to include the entire setuptools package in your distributions.  By
+including just a single `bootstrap module`_ (a 12K .py file), your package will
+automatically download and install ``setuptools`` if the user is building your
+package from source and doesn't have a suitable version already installed.
+
+.. _bootstrap module: https://bootstrap.pypa.io/ez_setup.py
 
 Feature Highlights:
 
+* Automatically find/download/install/upgrade dependencies at build time using
+  the `EasyInstall tool <easy_install.html>`_,
+  which supports downloading via HTTP, FTP, Subversion, and SourceForge, and
+  automatically scans web pages linked from PyPI to find download links.  (It's
+  the closest thing to CPAN currently available for Python.)
+
 * Create `Python Eggs <http://peak.telecommunity.com/DevCenter/PythonEggs>`_ -
   a single-file importable distribution format
 
@@ -49,6 +62,8 @@ Feature Highlights:
 
 .. contents:: **Table of Contents**
 
+.. _ez_setup.py: `bootstrap module`_
+
 
 -----------------
 Developer's Guide
@@ -58,6 +73,10 @@ Developer's Guide
 Installing ``setuptools``
 =========================
 
+.. _EasyInstall Installation Instructions: easy_install.html
+
+.. _Custom Installation Locations: easy_install.html
+
 .. _Installing Packages: https://packaging.python.org/tutorials/installing-packages/
 
 To install the latest version of setuptools, use::
@@ -141,7 +160,7 @@ Specifying Your Project's Version
 
 Setuptools can work well with most versioning schemes; there are, however, a
 few special things to watch out for, in order to ensure that setuptools and
-other tools can always tell what version of your package is newer than another
+EasyInstall can always tell what version of your package is newer than another
 version.  Knowing these things will also help you correctly specify what
 versions of other projects your project depends on.
 
@@ -282,10 +301,11 @@ unless you need the associated ``setuptools`` feature.
 ``setup_requires``
     A string or list of strings specifying what other distributions need to
     be present in order for the *setup script* to run.  ``setuptools`` will
-    attempt to obtain these before processing the rest of the setup script or
-    commands.  This argument is needed if you are using distutils extensions as
-    part of your build process; for example, extensions that process setup()
-    arguments and turn them into EGG-INFO metadata files.
+    attempt to obtain these (even going so far as to download them using
+    ``EasyInstall``) before processing the rest of the setup script or commands.
+    This argument is needed if you are using distutils extensions as part of
+    your build process; for example, extensions that process setup() arguments
+    and turn them into EGG-INFO metadata files.
 
     (Note: projects listed in ``setup_requires`` will NOT be automatically
     installed on the system where the setup script is being run.  They are
@@ -298,7 +318,8 @@ unless you need the associated ``setuptools`` feature.
     A list of strings naming URLs to be searched when satisfying dependencies.
     These links will be used if needed to install packages specified by
     ``setup_requires`` or ``tests_require``.  They will also be written into
-    the egg's metadata for use during install by tools that support them.
+    the egg's metadata for use by tools like EasyInstall to use when installing
+    an ``.egg`` file.
 
 ``namespace_packages``
     A list of strings naming the project's "namespace packages".  A namespace
@@ -325,20 +346,17 @@ unless you need the associated ``setuptools`` feature.
     specified test suite, e.g. via ``setup.py test``.  See the section on the
     `test`_ command below for more details.
 
-    New in 41.5.0: Deprecated the test command.
-
 ``tests_require``
     If your project's tests need one or more additional packages besides those
     needed to install it, you can use this option to specify them.  It should
     be a string or list of strings specifying what other distributions need to
     be present for the package's tests to run.  When you run the ``test``
-    command, ``setuptools`` will  attempt to obtain these.  Note that these
+    command, ``setuptools`` will  attempt to obtain these (even going
+    so far as to download them using ``EasyInstall``).  Note that these
     required projects will *not* be installed on the system where the tests
     are run, but only downloaded to the project's setup directory if they're
     not already installed locally.
 
-    New in 41.5.0: Deprecated the test command.
-
 .. _test_loader:
 
 ``test_loader``
@@ -362,8 +380,6 @@ unless you need the associated ``setuptools`` feature.
     as long as you use the ``tests_require`` option to ensure that the package
     containing the loader class is available when the ``test`` command is run.
 
-    New in 41.5.0: Deprecated the test command.
-
 ``eager_resources``
     A list of strings naming resources that should be extracted together, if
     any of them is needed, or if any C extensions included in the project are
@@ -536,12 +552,11 @@ script called ``baz``, you might do something like this::
     )
 
 When this project is installed on non-Windows platforms (using "setup.py
-install", "setup.py develop", or with pip), a set of ``foo``, ``bar``,
-and ``baz`` scripts will be installed that import ``main_func`` and
-``some_func`` from the specified modules.  The functions you specify are
-called with no arguments, and their return value is passed to
-``sys.exit()``, so you can return an errorlevel or message to print to
-stderr.
+install", "setup.py develop", or by using EasyInstall), a set of ``foo``,
+``bar``, and ``baz`` scripts will be installed that import ``main_func`` and
+``some_func`` from the specified modules.  The functions you specify are called
+with no arguments, and their return value is passed to ``sys.exit()``, so you
+can return an errorlevel or message to print to stderr.
 
 On Windows, a set of ``foo.exe``, ``bar.exe``, and ``baz.exe`` launchers are
 created, alongside a set of ``foo.py``, ``bar.py``, and ``baz.pyw`` files.  The
@@ -581,6 +596,10 @@ Python must be available via the ``PATH`` environment variable, under its
 "long" name.  That is, if the egg is built for Python 2.3, there must be a
 ``python2.3`` executable present in a directory on ``PATH``.
 
+This feature is primarily intended to support ez_setup the installation of
+setuptools itself on non-Windows platforms, but may also be useful for other
+projects as well.
+
 IMPORTANT NOTE: Eggs with an "eggsecutable" header cannot be renamed, or
 invoked via symlinks.  They *must* be invoked using their original filename, in
 order to ensure that, once running, ``pkg_resources`` will know what project
@@ -594,7 +613,7 @@ Declaring Dependencies
 
 ``setuptools`` supports automatically installing dependencies when a package is
 installed, and including information about dependencies in Python Eggs (so that
-package management tools like pip can use the information).
+package management tools like EasyInstall can use the information).
 
 ``setuptools`` and ``pkg_resources`` use a common syntax for specifying a
 project's required dependencies.  This syntax consists of a project's PyPI
@@ -633,9 +652,10 @@ requirement in a string, each requirement must begin on a new line.
 
 This has three effects:
 
-1. When your project is installed, either by using pip, ``setup.py install``,
-   or ``setup.py develop``, all of the dependencies not already installed will
-   be located (via PyPI), downloaded, built (if necessary), and installed.
+1. When your project is installed, either by using EasyInstall, ``setup.py
+   install``, or ``setup.py develop``, all of the dependencies not already
+   installed will be located (via PyPI), downloaded, built (if necessary),
+   and installed.
 
 2. Any scripts in your project will be installed with wrappers that verify
    the availability of the specified dependencies at runtime, and ensure that
@@ -655,10 +675,6 @@ using ``setup.py develop``.)
 Dependencies that aren't in PyPI
 --------------------------------
 
-.. warning::
-    Dependency links support has been dropped by pip starting with version
-    19.0 (released 2019-01-22).
-
 If your project depends on packages that don't exist on PyPI, you may still be
 able to depend on them, as long as they are available for download as:
 
@@ -709,8 +725,9 @@ This will do a checkout (or a clone, in Git and Mercurial parlance) to a
 temporary folder and run ``setup.py bdist_egg``.
 
 The ``dependency_links`` option takes the form of a list of URL strings.  For
-example, this will cause a search of the specified page for eggs or source
-distributions, if the package's dependencies aren't already installed::
+example, the below will cause EasyInstall to search the specified page for
+eggs or source distributions, if the package's dependencies aren't already
+installed::
 
     setup(
         ...
@@ -750,7 +767,7 @@ names of "extra" features, to strings or lists of strings describing those
 features' requirements.  These requirements will *not* be automatically
 installed unless another package depends on them (directly or indirectly) by
 including the desired "extras" in square brackets after the associated project
-name.  (Or if the extras were listed in a requirement spec on the "pip install"
+name.  (Or if the extras were listed in a requirement spec on the EasyInstall
 command line.)
 
 Extras can be used by a project's `entry points`_ to specify dynamic
@@ -1165,12 +1182,13 @@ preferred way of working (as opposed to using a common independent staging area
 or the site-packages directory).
 
 To do this, use the ``setup.py develop`` command.  It works very similarly to
-``setup.py install``, except that it doesn't actually install anything.
-Instead, it creates a special ``.egg-link`` file in the deployment directory,
-that links to your project's source code.  And, if your deployment directory is
-Python's ``site-packages`` directory, it will also update the
-``easy-install.pth`` file to include your project's source code, thereby making
-it available on ``sys.path`` for all programs using that Python installation.
+``setup.py install`` or the EasyInstall tool, except that it doesn't actually
+install anything.  Instead, it creates a special ``.egg-link`` file in the
+deployment directory, that links to your project's source code.  And, if your
+deployment directory is Python's ``site-packages`` directory, it will also
+update the ``easy-install.pth`` file to include your project's source code,
+thereby making it available on ``sys.path`` for all programs using that Python
+installation.
 
 If you have enabled the ``use_2to3`` flag, then of course the ``.egg-link``
 will not link directly to your source code when run under Python 3, since
@@ -1241,6 +1259,20 @@ To install your newly uploaded package ``example_pkg``,  you can use pip::
 If you have issues at any point, please refer to `Packaging project tutorials`_
 for clarification.
 
+Distributing legacy ``setuptools`` projects using ez_setup.py
+-------------------------------------------------------------
+
+.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
+
+Distributing packages using the legacy ``ez_setup.py`` and ``easy_install`` is 
+deprecated in favor of PIP. Please consider migrating to using pip and twine based
+distribution.
+
+However, if you still have any ``ez_setup`` based packages, documentation for 
+ez_setup based distributions can be found at `ez_setup distribution guide`_.
+
+.. _ez_setup distribution guide: ez_setup.html
+
 Setting the ``zip_safe`` flag
 -----------------------------
 
@@ -1276,14 +1308,20 @@ you've checked over all the warnings it issued, and you are either satisfied it
 doesn't work, you can always change it to ``False``, which will force
 ``setuptools`` to install your project as a directory rather than as a zipfile.
 
+Of course, the end-user can still override either decision, if they are using
+EasyInstall to install your package.  And, if you want to override for testing
+purposes, you can just run ``setup.py easy_install --zip-ok .`` or ``setup.py
+easy_install --always-unzip .`` in your project directory. to install the
+package as a zipfile or directory, respectively.
+
 In the future, as we gain more experience with different packages and become
 more satisfied with the robustness of the ``pkg_resources`` runtime, the
 "zip safety" analysis may become less conservative.  However, we strongly
 recommend that you determine for yourself whether your project functions
 correctly when installed as a zipfile, correct any problems if you can, and
 then make an explicit declaration of ``True`` or ``False`` for the ``zip_safe``
-flag, so that it will not be necessary for ``bdist_egg`` to try to guess
-whether your project can work as a zipfile.
+flag, so that it will not be necessary for ``bdist_egg`` or ``EasyInstall`` to
+try to guess whether your project can work as a zipfile.
 
 .. _Namespace Packages:
 
@@ -1397,9 +1435,9 @@ to generate a daily build or snapshot for.  See the section below on the
 
 (Also, before you release your project, be sure to see the section above on
 `Specifying Your Project's Version`_ for more information about how pre- and
-post-release tags affect how version numbers are interpreted.  This is
-important in order to make sure that dependency processing tools will know
-which versions of your project are newer than others.)
+post-release tags affect how setuptools and EasyInstall interpret version
+numbers.  This is important in order to make sure that dependency processing
+tools will know which versions of your project are newer than others.)
 
 Finally, if you are creating builds frequently, and either building them in a
 downloadable location or are copying them to a distribution server, you should
@@ -1455,6 +1493,58 @@ all practical purposes, you'll probably use only the ``--formats`` option, if
 you use any option at all.
 
 
+Making your package available for EasyInstall
+---------------------------------------------
+
+There may be reasons why you don't want to upload distributions to
+PyPI, and just want your existing distributions (or perhaps a Subversion
+checkout) to be used instead.
+
+There are three ``setup()`` arguments that affect EasyInstall:
+
+``url`` and ``download_url``
+   These become links on your project's PyPI page.  EasyInstall will examine
+   them to see if they link to a package ("primary links"), or whether they are
+   HTML pages.  If they're HTML pages, EasyInstall scans all HREF's on the
+   page for primary links
+
+``long_description``
+   EasyInstall will check any URLs contained in this argument to see if they
+   are primary links.
+
+A URL is considered a "primary link" if it is a link to a .tar.gz, .tgz, .zip,
+.egg, .egg.zip, .tar.bz2, or .exe file, or if it has an ``#egg=project`` or
+``#egg=project-version`` fragment identifier attached to it.  EasyInstall
+attempts to determine a project name and optional version number from the text
+of a primary link *without* downloading it.  When it has found all the primary
+links, EasyInstall will select the best match based on requested version,
+platform compatibility, and other criteria.
+
+So, if your ``url`` or ``download_url`` point either directly to a downloadable
+source distribution, or to HTML page(s) that have direct links to such, then
+EasyInstall will be able to locate downloads automatically.  If you want to
+make Subversion checkouts available, then you should create links with either
+``#egg=project`` or ``#egg=project-version`` added to the URL.  You should
+replace ``project`` and ``version`` with the values they would have in an egg
+filename.  (Be sure to actually generate an egg and then use the initial part
+of the filename, rather than trying to guess what the escaped form of the
+project name and version number will be.)
+
+Note that Subversion checkout links are of lower precedence than other kinds
+of distributions, so EasyInstall will not select a Subversion checkout for
+downloading unless it has a version included in the ``#egg=`` suffix, and
+it's a higher version than EasyInstall has seen in any other links for your
+project.
+
+As a result, it's a common practice to use mark checkout URLs with a version of
+"dev" (i.e., ``#egg=projectname-dev``), so that users can do something like
+this::
+
+    easy_install --editable projectname==dev
+
+in order to check out the in-development version of ``projectname``.
+
+
 Making "Official" (Non-Snapshot) Releases
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -1599,9 +1689,6 @@ file locations.
 ``bdist_egg`` - Create a Python Egg for the project
 ===================================================
 
-.. warning::
-    **eggs** are deprecated in favor of wheels, and not supported by pip.
-
 This command generates a Python Egg (``.egg`` file) for the project.  Python
 Eggs are the preferred binary distribution format for EasyInstall, because they
 are cross-platform (for "pure" packages), directly importable, and contain
@@ -1709,9 +1796,9 @@ Here are some of the options that the ``develop`` command accepts.  Note that
 they affect the project's dependencies as well as the project itself, so if you
 have dependencies that need to be installed and you use ``--exclude-scripts``
 (for example), the dependencies' scripts will not be installed either!  For
-this reason, you may want to use pip to install the project's dependencies
-before using the ``develop`` command, if you need finer control over the
-installation options for dependencies.
+this reason, you may want to use EasyInstall to install the project's
+dependencies before using the ``develop`` command, if you need finer control
+over the installation options for dependencies.
 
 ``--uninstall, -u``
     Un-deploy the current project.  You may use the ``--install-dir`` or ``-d``
@@ -1721,10 +1808,10 @@ installation options for dependencies.
     staging area is Python's ``site-packages`` directory.
 
     Note that this option currently does *not* uninstall script wrappers!  You
-    must uninstall them yourself, or overwrite them by using pip to install a
-    different version of the package.  You can also avoid installing script
-    wrappers in the first place, if you use the ``--exclude-scripts`` (aka
-    ``-x``) option when you run ``develop`` to deploy the project.
+    must uninstall them yourself, or overwrite them by using EasyInstall to
+    activate a different version of the package.  You can also avoid installing
+    script wrappers in the first place, if you use the ``--exclude-scripts``
+    (aka ``-x``) option when you run ``develop`` to deploy the project.
 
 ``--multi-version, -m``
     "Multi-version" mode. Specifying this option prevents ``develop`` from
@@ -1733,8 +1820,8 @@ installation options for dependencies.
     removed upon successful deployment.  In multi-version mode, no specific
     version of the package is available for importing, unless you use
     ``pkg_resources.require()`` to put it on ``sys.path``, or you are running
-    a wrapper script generated by ``setuptools``.  (In which case the wrapper
-    script calls ``require()`` for you.)
+    a wrapper script generated by ``setuptools`` or EasyInstall.  (In which
+    case the wrapper script calls ``require()`` for you.)
 
     Note that if you install to a directory other than ``site-packages``,
     this option is automatically in effect, because ``.pth`` files can only be
@@ -1787,6 +1874,25 @@ files), the ``develop`` command will use them as defaults, unless you override
 them in a ``[develop]`` section or on the command line.
 
 
+``easy_install`` - Find and install packages
+============================================
+
+This command runs the `EasyInstall tool
+<easy_install.html>`_ for you.  It is exactly
+equivalent to running the ``easy_install`` command.  All command line arguments
+following this command are consumed and not processed further by the distutils,
+so this must be the last command listed on the command line.  Please see
+the EasyInstall documentation for the options reference and usage examples.
+Normally, there is no reason to use this command via the command line, as you
+can just use ``easy_install`` directly.  It's only listed here so that you know
+it's a distutils command, which means that you can:
+
+* create command aliases that use it,
+* create distutils extensions that invoke it as a subcommand, and
+* configure options for it in your ``setup.cfg`` or other distutils config
+  files.
+
+
 .. _egg_info:
 
 ``egg_info`` - Create egg metadata and set build tags
@@ -1845,9 +1951,9 @@ added in the following order:
 (Note: Because these options modify the version number used for source and
 binary distributions of your project, you should first make sure that you know
 how the resulting version numbers will be interpreted by automated tools
-like pip.  See the section above on `Specifying Your Project's Version`_ for an
-explanation of pre- and post-release tags, as well as tips on how to choose and
-verify a versioning scheme for your your project.)
+like EasyInstall.  See the section above on `Specifying Your Project's
+Version`_ for an explanation of pre- and post-release tags, as well as tips on
+how to choose and verify a versioning scheme for your your project.)
 
 For advanced uses, there is one other option that can be set, to change the
 location of the project's ``.egg-info`` directory.  Commands that need to find
@@ -2029,11 +2135,6 @@ distutils configuration file the option will be added to (or removed from).
 ``test`` - Build package and run a unittest suite
 =================================================
 
-.. warning::
-    ``test`` is deprecated and will be removed in a future version. Users
-    looking for a generic test entry point independent of test runner are
-    encouraged to use `tox <https://tox.readthedocs.io>`_.
-
 When doing test-driven development, or running automated builds that need
 testing before they are deployed for downloading or use, it's often useful
 to be able to run a project's unit tests without actually deploying the project
@@ -2079,8 +2180,6 @@ available:
     If you did not set a ``test_suite`` in your ``setup()`` call, and do not
     provide a ``--test-suite`` option, an error will occur.
 
-New in 41.5.0: Deprecated the test command.
-
 
 .. _upload:
 
@@ -2317,7 +2416,7 @@ tests_require            list-semi
 include_package_data     bool
 packages                 find:, find_namespace:, list-comma
 package_dir              dict
-package_data             section                                              (1)
+package_data             section
 exclude_package_data     section
 namespace_packages       list-comma
 py_modules               list-comma
@@ -2334,10 +2433,6 @@ data_files               dict                                 40.6.0
 
     **find_namespace directive** - The ``find_namespace:`` directive is supported since Python >=3.3.
 
-Notes:
-1. In the `package_data` section, a key named with a single asterisk (`*`)
-refers to all packages, in lieu of the empty string used in `setup.py`.
-
 
 Configuration API
 =================
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 51fb1192f..1f170cfda 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -333,7 +333,7 @@ class UnknownExtra(ResolutionError):
 
 _provider_factories = {}
 
-PY_MAJOR = '{}.{}'.format(*sys.version_info)
+PY_MAJOR = sys.version[:3]
 EGG_DIST = 3
 BINARY_DIST = 2
 SOURCE_DIST = 1
@@ -3109,7 +3109,6 @@ class Requirement(packaging.requirements.Requirement):
         self.extras = tuple(map(safe_extra, self.extras))
         self.hashCmp = (
             self.key,
-            self.url,
             self.specifier,
             frozenset(self.extras),
             str(self.marker) if self.marker else None,
diff --git a/setup.cfg b/setup.cfg
index 0e030cde8..96792dd36 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -19,7 +19,7 @@ universal = 1
 
 [metadata]
 name = setuptools
-version = 41.5.0
+version = 41.2.0
 description = Easily download, build, install, upgrade, and uninstall Python packages
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
@@ -42,7 +42,6 @@ classifiers =
     Programming Language :: Python :: 3.5
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
     Topic :: Software Development :: Libraries :: Python Modules
     Topic :: System :: Archiving :: Packaging
     Topic :: System :: Systems Administration
diff --git a/setup.py b/setup.py
index d97895fcc..f5030dd67 100755
--- a/setup.py
+++ b/setup.py
@@ -44,7 +44,7 @@ def _gen_console_scripts():
     if any(os.environ.get(var) not in (None, "", "0") for var in var_names):
         return
     tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main"
-    yield tmpl.format(shortver='{}.{}'.format(*sys.version_info))
+    yield tmpl.format(shortver=sys.version[:3])
 
 
 package_data = dict(
diff --git a/setuptools/_imp.py b/setuptools/_imp.py
new file mode 100644
index 000000000..a3cce9b28
--- /dev/null
+++ b/setuptools/_imp.py
@@ -0,0 +1,73 @@
+"""
+Re-implementation of find_module and get_frozen_object
+from the deprecated imp module.
+"""
+
+import os
+import importlib.util
+import importlib.machinery
+
+from .py34compat import module_from_spec
+
+
+PY_SOURCE = 1
+PY_COMPILED = 2
+C_EXTENSION = 3
+C_BUILTIN = 6
+PY_FROZEN = 7
+
+
+def find_module(module, paths=None):
+    """Just like 'imp.find_module()', but with package support"""
+    spec = importlib.util.find_spec(module, paths)
+    if spec is None:
+        raise ImportError("Can't find %s" % module)
+    if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
+        spec = importlib.util.spec_from_loader('__init__.py', spec.loader)
+
+    kind = -1
+    file = None
+    static = isinstance(spec.loader, type)
+    if spec.origin == 'frozen' or static and issubclass(
+            spec.loader, importlib.machinery.FrozenImporter):
+        kind = PY_FROZEN
+        path = None  # imp compabilty
+        suffix = mode = ''  # imp compability
+    elif spec.origin == 'built-in' or static and issubclass(
+            spec.loader, importlib.machinery.BuiltinImporter):
+        kind = C_BUILTIN
+        path = None  # imp compabilty
+        suffix = mode = ''  # imp compability
+    elif spec.has_location:
+        path = spec.origin
+        suffix = os.path.splitext(path)[1]
+        mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb'
+
+        if suffix in importlib.machinery.SOURCE_SUFFIXES:
+            kind = PY_SOURCE
+        elif suffix in importlib.machinery.BYTECODE_SUFFIXES:
+            kind = PY_COMPILED
+        elif suffix in importlib.machinery.EXTENSION_SUFFIXES:
+            kind = C_EXTENSION
+
+        if kind in {PY_SOURCE, PY_COMPILED}:
+            file = open(path, mode)
+    else:
+        path = None
+        suffix = mode = ''
+
+    return file, path, (suffix, mode, kind)
+
+
+def get_frozen_object(module, paths=None):
+    spec = importlib.util.find_spec(module, paths)
+    if not spec:
+        raise ImportError("Can't find %s" % module)
+    return spec.loader.get_code(module)
+
+
+def get_module(module, paths, info):
+    spec = importlib.util.find_spec(module, paths)
+    if not spec:
+        raise ImportError("Can't find %s" % module)
+    return module_from_spec(spec)
diff --git a/setuptools/_vendor/ordered_set.py b/setuptools/_vendor/ordered_set.py
deleted file mode 100644
index 14876000d..000000000
--- a/setuptools/_vendor/ordered_set.py
+++ /dev/null
@@ -1,488 +0,0 @@
-"""
-An OrderedSet is a custom MutableSet that remembers its order, so that every
-entry has an index that can be looked up.
-
-Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger,
-and released under the MIT license.
-"""
-import itertools as it
-from collections import deque
-
-try:
-    # Python 3
-    from collections.abc import MutableSet, Sequence
-except ImportError:
-    # Python 2.7
-    from collections import MutableSet, Sequence
-
-SLICE_ALL = slice(None)
-__version__ = "3.1"
-
-
-def is_iterable(obj):
-    """
-    Are we being asked to look up a list of things, instead of a single thing?
-    We check for the `__iter__` attribute so that this can cover types that
-    don't have to be known by this module, such as NumPy arrays.
-
-    Strings, however, should be considered as atomic values to look up, not
-    iterables. The same goes for tuples, since they are immutable and therefore
-    valid entries.
-
-    We don't need to check for the Python 2 `unicode` type, because it doesn't
-    have an `__iter__` attribute anyway.
-    """
-    return (
-        hasattr(obj, "__iter__")
-        and not isinstance(obj, str)
-        and not isinstance(obj, tuple)
-    )
-
-
-class OrderedSet(MutableSet, Sequence):
-    """
-    An OrderedSet is a custom MutableSet that remembers its order, so that
-    every entry has an index that can be looked up.
-
-    Example:
-        >>> OrderedSet([1, 1, 2, 3, 2])
-        OrderedSet([1, 2, 3])
-    """
-
-    def __init__(self, iterable=None):
-        self.items = []
-        self.map = {}
-        if iterable is not None:
-            self |= iterable
-
-    def __len__(self):
-        """
-        Returns the number of unique elements in the ordered set
-
-        Example:
-            >>> len(OrderedSet([]))
-            0
-            >>> len(OrderedSet([1, 2]))
-            2
-        """
-        return len(self.items)
-
-    def __getitem__(self, index):
-        """
-        Get the item at a given index.
-
-        If `index` is a slice, you will get back that slice of items, as a
-        new OrderedSet.
-
-        If `index` is a list or a similar iterable, you'll get a list of
-        items corresponding to those indices. This is similar to NumPy's
-        "fancy indexing". The result is not an OrderedSet because you may ask
-        for duplicate indices, and the number of elements returned should be
-        the number of elements asked for.
-
-        Example:
-            >>> oset = OrderedSet([1, 2, 3])
-            >>> oset[1]
-            2
-        """
-        if isinstance(index, slice) and index == SLICE_ALL:
-            return self.copy()
-        elif is_iterable(index):
-            return [self.items[i] for i in index]
-        elif hasattr(index, "__index__") or isinstance(index, slice):
-            result = self.items[index]
-            if isinstance(result, list):
-                return self.__class__(result)
-            else:
-                return result
-        else:
-            raise TypeError("Don't know how to index an OrderedSet by %r" % index)
-
-    def copy(self):
-        """
-        Return a shallow copy of this object.
-
-        Example:
-            >>> this = OrderedSet([1, 2, 3])
-            >>> other = this.copy()
-            >>> this == other
-            True
-            >>> this is other
-            False
-        """
-        return self.__class__(self)
-
-    def __getstate__(self):
-        if len(self) == 0:
-            # The state can't be an empty list.
-            # We need to return a truthy value, or else __setstate__ won't be run.
-            #
-            # This could have been done more gracefully by always putting the state
-            # in a tuple, but this way is backwards- and forwards- compatible with
-            # previous versions of OrderedSet.
-            return (None,)
-        else:
-            return list(self)
-
-    def __setstate__(self, state):
-        if state == (None,):
-            self.__init__([])
-        else:
-            self.__init__(state)
-
-    def __contains__(self, key):
-        """
-        Test if the item is in this ordered set
-
-        Example:
-            >>> 1 in OrderedSet([1, 3, 2])
-            True
-            >>> 5 in OrderedSet([1, 3, 2])
-            False
-        """
-        return key in self.map
-
-    def add(self, key):
-        """
-        Add `key` as an item to this OrderedSet, then return its index.
-
-        If `key` is already in the OrderedSet, return the index it already
-        had.
-
-        Example:
-            >>> oset = OrderedSet()
-            >>> oset.append(3)
-            0
-            >>> print(oset)
-            OrderedSet([3])
-        """
-        if key not in self.map:
-            self.map[key] = len(self.items)
-            self.items.append(key)
-        return self.map[key]
-
-    append = add
-
-    def update(self, sequence):
-        """
-        Update the set with the given iterable sequence, then return the index
-        of the last element inserted.
-
-        Example:
-            >>> oset = OrderedSet([1, 2, 3])
-            >>> oset.update([3, 1, 5, 1, 4])
-            4
-            >>> print(oset)
-            OrderedSet([1, 2, 3, 5, 4])
-        """
-        item_index = None
-        try:
-            for item in sequence:
-                item_index = self.add(item)
-        except TypeError:
-            raise ValueError(
-                "Argument needs to be an iterable, got %s" % type(sequence)
-            )
-        return item_index
-
-    def index(self, key):
-        """
-        Get the index of a given entry, raising an IndexError if it's not
-        present.
-
-        `key` can be an iterable of entries that is not a string, in which case
-        this returns a list of indices.
-
-        Example:
-            >>> oset = OrderedSet([1, 2, 3])
-            >>> oset.index(2)
-            1
-        """
-        if is_iterable(key):
-            return [self.index(subkey) for subkey in key]
-        return self.map[key]
-
-    # Provide some compatibility with pd.Index
-    get_loc = index
-    get_indexer = index
-
-    def pop(self):
-        """
-        Remove and return the last element from the set.
-
-        Raises KeyError if the set is empty.
-
-        Example:
-            >>> oset = OrderedSet([1, 2, 3])
-            >>> oset.pop()
-            3
-        """
-        if not self.items:
-            raise KeyError("Set is empty")
-
-        elem = self.items[-1]
-        del self.items[-1]
-        del self.map[elem]
-        return elem
-
-    def discard(self, key):
-        """
-        Remove an element.  Do not raise an exception if absent.
-
-        The MutableSet mixin uses this to implement the .remove() method, which
-        *does* raise an error when asked to remove a non-existent item.
-
-        Example:
-            >>> oset = OrderedSet([1, 2, 3])
-            >>> oset.discard(2)
-            >>> print(oset)
-            OrderedSet([1, 3])
-            >>> oset.discard(2)
-            >>> print(oset)
-            OrderedSet([1, 3])
-        """
-        if key in self:
-            i = self.map[key]
-            del self.items[i]
-            del self.map[key]
-            for k, v in self.map.items():
-                if v >= i:
-                    self.map[k] = v - 1
-
-    def clear(self):
-        """
-        Remove all items from this OrderedSet.
-        """
-        del self.items[:]
-        self.map.clear()
-
-    def __iter__(self):
-        """
-        Example:
-            >>> list(iter(OrderedSet([1, 2, 3])))
-            [1, 2, 3]
-        """
-        return iter(self.items)
-
-    def __reversed__(self):
-        """
-        Example:
-            >>> list(reversed(OrderedSet([1, 2, 3])))
-            [3, 2, 1]
-        """
-        return reversed(self.items)
-
-    def __repr__(self):
-        if not self:
-            return "%s()" % (self.__class__.__name__,)
-        return "%s(%r)" % (self.__class__.__name__, list(self))
-
-    def __eq__(self, other):
-        """
-        Returns true if the containers have the same items. If `other` is a
-        Sequence, then order is checked, otherwise it is ignored.
-
-        Example:
-            >>> oset = OrderedSet([1, 3, 2])
-            >>> oset == [1, 3, 2]
-            True
-            >>> oset == [1, 2, 3]
-            False
-            >>> oset == [2, 3]
-            False
-            >>> oset == OrderedSet([3, 2, 1])
-            False
-        """
-        # In Python 2 deque is not a Sequence, so treat it as one for
-        # consistent behavior with Python 3.
-        if isinstance(other, (Sequence, deque)):
-            # Check that this OrderedSet contains the same elements, in the
-            # same order, as the other object.
-            return list(self) == list(other)
-        try:
-            other_as_set = set(other)
-        except TypeError:
-            # If `other` can't be converted into a set, it's not equal.
-            return False
-        else:
-            return set(self) == other_as_set
-
-    def union(self, *sets):
-        """
-        Combines all unique items.
-        Each items order is defined by its first appearance.
-
-        Example:
-            >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0])
-            >>> print(oset)
-            OrderedSet([3, 1, 4, 5, 2, 0])
-            >>> oset.union([8, 9])
-            OrderedSet([3, 1, 4, 5, 2, 0, 8, 9])
-            >>> oset | {10}
-            OrderedSet([3, 1, 4, 5, 2, 0, 10])
-        """
-        cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
-        containers = map(list, it.chain([self], sets))
-        items = it.chain.from_iterable(containers)
-        return cls(items)
-
-    def __and__(self, other):
-        # the parent implementation of this is backwards
-        return self.intersection(other)
-
-    def intersection(self, *sets):
-        """
-        Returns elements in common between all sets. Order is defined only
-        by the first set.
-
-        Example:
-            >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3])
-            >>> print(oset)
-            OrderedSet([1, 2, 3])
-            >>> oset.intersection([2, 4, 5], [1, 2, 3, 4])
-            OrderedSet([2])
-            >>> oset.intersection()
-            OrderedSet([1, 2, 3])
-        """
-        cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
-        if sets:
-            common = set.intersection(*map(set, sets))
-            items = (item for item in self if item in common)
-        else:
-            items = self
-        return cls(items)
-
-    def difference(self, *sets):
-        """
-        Returns all elements that are in this set but not the others.
-
-        Example:
-            >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]))
-            OrderedSet([1, 3])
-            >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3]))
-            OrderedSet([1])
-            >>> OrderedSet([1, 2, 3]) - OrderedSet([2])
-            OrderedSet([1, 3])
-            >>> OrderedSet([1, 2, 3]).difference()
-            OrderedSet([1, 2, 3])
-        """
-        cls = self.__class__
-        if sets:
-            other = set.union(*map(set, sets))
-            items = (item for item in self if item not in other)
-        else:
-            items = self
-        return cls(items)
-
-    def issubset(self, other):
-        """
-        Report whether another set contains this set.
-
-        Example:
-            >>> OrderedSet([1, 2, 3]).issubset({1, 2})
-            False
-            >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4})
-            True
-            >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5})
-            False
-        """
-        if len(self) > len(other):  # Fast check for obvious cases
-            return False
-        return all(item in other for item in self)
-
-    def issuperset(self, other):
-        """
-        Report whether this set contains another set.
-
-        Example:
-            >>> OrderedSet([1, 2]).issuperset([1, 2, 3])
-            False
-            >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3})
-            True
-            >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3})
-            False
-        """
-        if len(self) < len(other):  # Fast check for obvious cases
-            return False
-        return all(item in self for item in other)
-
-    def symmetric_difference(self, other):
-        """
-        Return the symmetric difference of two OrderedSets as a new set.
-        That is, the new set will contain all elements that are in exactly
-        one of the sets.
-
-        Their order will be preserved, with elements from `self` preceding
-        elements from `other`.
-
-        Example:
-            >>> this = OrderedSet([1, 4, 3, 5, 7])
-            >>> other = OrderedSet([9, 7, 1, 3, 2])
-            >>> this.symmetric_difference(other)
-            OrderedSet([4, 5, 9, 2])
-        """
-        cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
-        diff1 = cls(self).difference(other)
-        diff2 = cls(other).difference(self)
-        return diff1.union(diff2)
-
-    def _update_items(self, items):
-        """
-        Replace the 'items' list of this OrderedSet with a new one, updating
-        self.map accordingly.
-        """
-        self.items = items
-        self.map = {item: idx for (idx, item) in enumerate(items)}
-
-    def difference_update(self, *sets):
-        """
-        Update this OrderedSet to remove items from one or more other sets.
-
-        Example:
-            >>> this = OrderedSet([1, 2, 3])
-            >>> this.difference_update(OrderedSet([2, 4]))
-            >>> print(this)
-            OrderedSet([1, 3])
-
-            >>> this = OrderedSet([1, 2, 3, 4, 5])
-            >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6]))
-            >>> print(this)
-            OrderedSet([3, 5])
-        """
-        items_to_remove = set()
-        for other in sets:
-            items_to_remove |= set(other)
-        self._update_items([item for item in self.items if item not in items_to_remove])
-
-    def intersection_update(self, other):
-        """
-        Update this OrderedSet to keep only items in another set, preserving
-        their order in this set.
-
-        Example:
-            >>> this = OrderedSet([1, 4, 3, 5, 7])
-            >>> other = OrderedSet([9, 7, 1, 3, 2])
-            >>> this.intersection_update(other)
-            >>> print(this)
-            OrderedSet([1, 3, 7])
-        """
-        other = set(other)
-        self._update_items([item for item in self.items if item in other])
-
-    def symmetric_difference_update(self, other):
-        """
-        Update this OrderedSet to remove items from another set, then
-        add items from the other set that were not present in this set.
-
-        Example:
-            >>> this = OrderedSet([1, 4, 3, 5, 7])
-            >>> other = OrderedSet([9, 7, 1, 3, 2])
-            >>> this.symmetric_difference_update(other)
-            >>> print(this)
-            OrderedSet([4, 5, 9, 2])
-        """
-        items_to_add = [item for item in other if item not in self]
-        items_to_remove = set(other)
-        self._update_items(
-            [item for item in self.items if item not in items_to_remove] + items_to_add
-        )
diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt
index 5731b4244..7d77863d5 100644
--- a/setuptools/_vendor/vendored.txt
+++ b/setuptools/_vendor/vendored.txt
@@ -1,4 +1,3 @@
 packaging==16.8
 pyparsing==2.2.1
 six==1.10.0
-ordered-set==3.1.1
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 98470f171..9f8df917e 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -284,7 +284,7 @@ class bdist_egg(Command):
                 "or refer to a module" % (ep,)
             )
 
-        pyver = '{}.{}'.format(*sys.version_info)
+        pyver = sys.version[:3]
         pkg = ep.module_name
         full = '.'.join(ep.attrs)
         base = ep.attrs[0]
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 545c3c442..06c98271b 100644
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -241,7 +241,7 @@ class easy_install(Command):
         """
         Render the Setuptools version and installation details, then exit.
         """
-        ver = '{}.{}'.format(*sys.version_info)
+        ver = sys.version[:3]
         dist = get_distribution('setuptools')
         tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
         print(tmpl.format(**locals()))
@@ -1180,7 +1180,8 @@ class easy_install(Command):
         # to the setup.cfg file.
         ei_opts = self.distribution.get_option_dict('easy_install').copy()
         fetch_directives = (
-            'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',
+            'find_links', 'site_dirs', 'index_url', 'optimize',
+            'site_dirs', 'allow_hosts',
         )
         fetch_options = {}
         for key, val in ei_opts.items():
@@ -1411,7 +1412,7 @@ def get_site_dirs():
                     os.path.join(
                         prefix,
                         "lib",
-                        "python{}.{}".format(*sys.version_info),
+                        "python" + sys.version[:3],
                         "site-packages",
                     ),
                     os.path.join(prefix, "lib", "site-python"),
@@ -1432,7 +1433,7 @@ def get_site_dirs():
                             home,
                             'Library',
                             'Python',
-                            '{}.{}'.format(*sys.version_info),
+                            sys.version[:3],
                             'site-packages',
                         )
                         sitedirs.append(home_sp)
diff --git a/setuptools/config.py b/setuptools/config.py
index 2d50e25e8..b6626043f 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -12,7 +12,6 @@ from importlib import import_module
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
 from setuptools.extern.packaging.version import LegacyVersion, parse
-from setuptools.extern.packaging.specifiers import SpecifierSet
 from setuptools.extern.six import string_types, PY3
 
 
@@ -555,7 +554,6 @@ class ConfigOptionsHandler(ConfigHandler):
             'packages': self._parse_packages,
             'entry_points': self._parse_file,
             'py_modules': parse_list,
-            'python_requires': SpecifierSet,
         }
 
     def _parse_packages(self, value):
diff --git a/setuptools/depends.py b/setuptools/depends.py
index 45e7052d8..a37675cbd 100644
--- a/setuptools/depends.py
+++ b/setuptools/depends.py
@@ -1,11 +1,13 @@
 import sys
-import imp
 import marshal
+import contextlib
 from distutils.version import StrictVersion
-from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
 
 from .py33compat import Bytecode
 
+from .py27compat import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
+from . import py27compat
+
 
 __all__ = [
     'Require', 'find_module', 'get_module_constant', 'extract_constant'
@@ -15,7 +17,8 @@ __all__ = [
 class Require:
     """A prerequisite to building or installing a distribution"""
 
-    def __init__(self, name, requested_version, module, homepage='',
+    def __init__(
+            self, name, requested_version, module, homepage='',
             attribute=None, format=None):
 
         if format is None and requested_version is not None:
@@ -79,23 +82,15 @@ class Require:
         return self.version_ok(version)
 
 
-def find_module(module, paths=None):
-    """Just like 'imp.find_module()', but with package support"""
-
-    parts = module.split('.')
-
-    while parts:
-        part = parts.pop(0)
-        f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
-
-        if kind == PKG_DIRECTORY:
-            parts = parts or ['__init__']
-            paths = [path]
-
-        elif parts:
-            raise ImportError("Can't find %r in %s" % (parts, module))
+def maybe_close(f):
+    @contextlib.contextmanager
+    def empty():
+        yield
+        return
+    if not f:
+        return empty()
 
-    return info
+    return contextlib.closing(f)
 
 
 def get_module_constant(module, symbol, default=-1, paths=None):
@@ -106,28 +101,23 @@ def get_module_constant(module, symbol, default=-1, paths=None):
     constant.  Otherwise, return 'default'."""
 
     try:
-        f, path, (suffix, mode, kind) = find_module(module, paths)
+        f, path, (suffix, mode, kind) = info = find_module(module, paths)
     except ImportError:
         # Module doesn't exist
         return None
 
-    try:
+    with maybe_close(f):
         if kind == PY_COMPILED:
             f.read(8)  # skip magic & date
             code = marshal.load(f)
         elif kind == PY_FROZEN:
-            code = imp.get_frozen_object(module)
+            code = py27compat.get_frozen_object(module, paths)
         elif kind == PY_SOURCE:
             code = compile(f.read(), path, 'exec')
         else:
             # Not something we can parse; we'll have to import it.  :(
-            if module not in sys.modules:
-                imp.load_module(module, f, path, (suffix, mode, kind))
-            return getattr(sys.modules[module], symbol, None)
-
-    finally:
-        if f:
-            f.close()
+            imported = py27compat.get_module(module, paths, info)
+            return getattr(imported, symbol, None)
 
     return extract_constant(code, symbol, default)
 
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 2e5ad4bd6..f0f030b55 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -27,7 +27,6 @@ from distutils.version import StrictVersion
 
 from setuptools.extern import six
 from setuptools.extern import packaging
-from setuptools.extern import ordered_set
 from setuptools.extern.six.moves import map, filter, filterfalse
 
 from . import SetuptoolsDeprecationWarning
@@ -408,7 +407,7 @@ class Distribution(_Distribution):
     _DISTUTILS_UNSUPPORTED_METADATA = {
         'long_description_content_type': None,
         'project_urls': dict,
-        'provides_extras': ordered_set.OrderedSet,
+        'provides_extras': set,
     }
 
     _patched_dist = None
diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py
index e8c616f91..cb2fa3292 100644
--- a/setuptools/extern/__init__.py
+++ b/setuptools/extern/__init__.py
@@ -69,5 +69,5 @@ class VendorImporter:
             sys.meta_path.append(self)
 
 
-names = 'six', 'packaging', 'pyparsing', 'ordered_set',
+names = 'six', 'packaging', 'pyparsing',
 VendorImporter(__name__, names, 'setuptools._vendor').install()
diff --git a/setuptools/msvc.py b/setuptools/msvc.py
index ffa7053b6..b9c472f14 100644
--- a/setuptools/msvc.py
+++ b/setuptools/msvc.py
@@ -11,17 +11,13 @@ Microsoft Visual C++ 9.0:
 Microsoft Visual C++ 10.0:
     Microsoft Windows SDK 7.1 (x86, x64, ia64)
 
-Microsoft Visual C++ 14.X:
+Microsoft Visual C++ 14.0:
     Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
+    Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
     Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
-    Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
-
-This may also support compilers shipped with compatible Visual Studio versions.
 """
 
-import json
-from os import listdir, pathsep
-from os.path import join, isfile, isdir, dirname
+import os
 import sys
 import platform
 import itertools
@@ -34,9 +30,12 @@ from .monkey import get_unpatched
 
 if platform.system() == 'Windows':
     from setuptools.extern.six.moves import winreg
-    from os import environ
+    safe_env = os.environ
 else:
-    # Mock winreg and environ so the module can be imported on this platform.
+    """
+    Mock winreg and environ so the module can be imported
+    on this platform.
+    """
 
     class winreg:
         HKEY_USERS = None
@@ -44,7 +43,7 @@ else:
         HKEY_LOCAL_MACHINE = None
         HKEY_CLASSES_ROOT = None
 
-    environ = dict()
+    safe_env = dict()
 
 _msvc9_suppress_errors = (
     # msvc9compiler isn't available on some platforms
@@ -64,14 +63,16 @@ except _msvc9_suppress_errors:
 def msvc9_find_vcvarsall(version):
     """
     Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
-    compiler build for Python
-    (VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
-
-    Fall back to original behavior when the standalone compiler is not
-    available.
+    compiler build for Python (VCForPython). Fall back to original behavior
+    when the standalone compiler is not available.
 
     Redirect the path of "vcvarsall.bat".
 
+    Known supported compilers
+    -------------------------
+    Microsoft Visual C++ 9.0:
+        Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
+
     Parameters
     ----------
     version: float
@@ -79,25 +80,24 @@ def msvc9_find_vcvarsall(version):
 
     Return
     ------
-    str
-        vcvarsall.bat path
+    vcvarsall.bat path: str
     """
-    vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
-    key = vc_base % ('', version)
+    VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
+    key = VC_BASE % ('', version)
     try:
         # Per-user installs register the compiler path here
         productdir = Reg.get_value(key, "installdir")
     except KeyError:
         try:
             # All-user installs on a 64-bit system register here
-            key = vc_base % ('Wow6432Node\\', version)
+            key = VC_BASE % ('Wow6432Node\\', version)
             productdir = Reg.get_value(key, "installdir")
         except KeyError:
             productdir = None
 
     if productdir:
-        vcvarsall = join(productdir, "vcvarsall.bat")
-        if isfile(vcvarsall):
+        vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat")
+        if os.path.isfile(vcvarsall):
             return vcvarsall
 
     return get_unpatched(msvc9_find_vcvarsall)(version)
@@ -106,10 +106,20 @@ def msvc9_find_vcvarsall(version):
 def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
     """
     Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
-    Microsoft Visual C++ 9.0 and 10.0 compilers.
+    compilers.
 
     Set environment without use of "vcvarsall.bat".
 
+    Known supported compilers
+    -------------------------
+    Microsoft Visual C++ 9.0:
+        Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
+        Microsoft Windows SDK 6.1 (x86, x64, ia64)
+        Microsoft Windows SDK 7.0 (x86, x64, ia64)
+
+    Microsoft Visual C++ 10.0:
+        Microsoft Windows SDK 7.1 (x86, x64, ia64)
+
     Parameters
     ----------
     ver: float
@@ -119,10 +129,9 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
 
     Return
     ------
-    dict
-        environment
+    environment: dict
     """
-    # Try to get environment from vcvarsall.bat (Classical way)
+    # Try to get environement from vcvarsall.bat (Classical way)
     try:
         orig = get_unpatched(msvc9_query_vcvarsall)
         return orig(ver, arch, *args, **kwargs)
@@ -144,10 +153,17 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
 def msvc14_get_vc_env(plat_spec):
     """
     Patched "distutils._msvccompiler._get_vc_env" for support extra
-    Microsoft Visual C++ 14.X compilers.
+    compilers.
 
     Set environment without use of "vcvarsall.bat".
 
+    Known supported compilers
+    -------------------------
+    Microsoft Visual C++ 14.0:
+        Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
+        Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
+        Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
+
     Parameters
     ----------
     plat_spec: str
@@ -155,8 +171,7 @@ def msvc14_get_vc_env(plat_spec):
 
     Return
     ------
-    dict
-        environment
+    environment: dict
     """
     # Try to get environment from vcvarsall.bat (Classical way)
     try:
@@ -202,9 +217,9 @@ def _augment_exception(exc, version, arch=''):
         if version == 9.0:
             if arch.lower().find('ia64') > -1:
                 # For VC++ 9.0, if IA64 support is needed, redirect user
-                # to Windows SDK 7.0.
-                # Note: No download link available from Microsoft.
-                message += ' Get it with "Microsoft Windows SDK 7.0"'
+                # to Windows SDK 7.0
+                message += ' Get it with "Microsoft Windows SDK 7.0": '
+                message += msdownload % 3138
             else:
                 # For VC++ 9.0 redirect user to Vc++ for Python 2.7 :
                 # This redirection link is maintained by Microsoft.
@@ -215,8 +230,8 @@ def _augment_exception(exc, version, arch=''):
             message += ' Get it with "Microsoft Windows SDK 7.1": '
             message += msdownload % 8279
         elif version >= 14.0:
-            # For VC++ 14.X Redirect user to latest Visual C++ Build Tools
-            message += (' Get it with "Build Tools for Visual Studio": '
+            # For VC++ 14.0 Redirect user to Visual C++ Build Tools
+            message += (' Get it with "Microsoft Visual C++ Build Tools": '
                         r'https://visualstudio.microsoft.com/downloads/')
 
     exc.args = (message, )
@@ -224,50 +239,26 @@ def _augment_exception(exc, version, arch=''):
 
 class PlatformInfo:
     """
-    Current and Target Architectures information.
+    Current and Target Architectures informations.
 
     Parameters
     ----------
     arch: str
         Target architecture.
     """
-    current_cpu = environ.get('processor_architecture', '').lower()
+    current_cpu = safe_env.get('processor_architecture', '').lower()
 
     def __init__(self, arch):
         self.arch = arch.lower().replace('x64', 'amd64')
 
     @property
     def target_cpu(self):
-        """
-        Return Target CPU architecture.
-
-        Return
-        ------
-        str
-            Target CPU
-        """
         return self.arch[self.arch.find('_') + 1:]
 
     def target_is_x86(self):
-        """
-        Return True if target CPU is x86 32 bits..
-
-        Return
-        ------
-        bool
-            CPU is x86 32 bits
-        """
         return self.target_cpu == 'x86'
 
     def current_is_x86(self):
-        """
-        Return True if current CPU is x86 32 bits..
-
-        Return
-        ------
-        bool
-            CPU is x86 32 bits
-        """
         return self.current_cpu == 'x86'
 
     def current_dir(self, hidex86=False, x64=False):
@@ -283,8 +274,8 @@ class PlatformInfo:
 
         Return
         ------
-        str
-            subfolder: '\target', or '' (see hidex86 parameter)
+        subfolder: str
+            '\target', or '' (see hidex86 parameter)
         """
         return (
             '' if (self.current_cpu == 'x86' and hidex86) else
@@ -305,8 +296,8 @@ class PlatformInfo:
 
         Return
         ------
-        str
-            subfolder: '\current', or '' (see hidex86 parameter)
+        subfolder: str
+            '\current', or '' (see hidex86 parameter)
         """
         return (
             '' if (self.target_cpu == 'x86' and hidex86) else
@@ -321,13 +312,13 @@ class PlatformInfo:
         Parameters
         ----------
         forcex86: bool
-            Use 'x86' as current architecture even if current architecture is
+            Use 'x86' as current architecture even if current acritecture is
             not x86.
 
         Return
         ------
-        str
-            subfolder: '' if target architecture is current architecture,
+        subfolder: str
+            '' if target architecture is current architecture,
             '\current_target' if not.
         """
         current = 'x86' if forcex86 else self.current_cpu
@@ -339,7 +330,7 @@ class PlatformInfo:
 
 class RegistryInfo:
     """
-    Microsoft Visual Studio related registry information.
+    Microsoft Visual Studio related registry informations.
 
     Parameters
     ----------
@@ -358,11 +349,6 @@ class RegistryInfo:
     def visualstudio(self):
         """
         Microsoft Visual Studio root registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
         return 'VisualStudio'
 
@@ -370,47 +356,27 @@ class RegistryInfo:
     def sxs(self):
         """
         Microsoft Visual Studio SxS registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
-        return join(self.visualstudio, 'SxS')
+        return os.path.join(self.visualstudio, 'SxS')
 
     @property
     def vc(self):
         """
         Microsoft Visual C++ VC7 registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
-        return join(self.sxs, 'VC7')
+        return os.path.join(self.sxs, 'VC7')
 
     @property
     def vs(self):
         """
         Microsoft Visual Studio VS7 registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
-        return join(self.sxs, 'VS7')
+        return os.path.join(self.sxs, 'VS7')
 
     @property
     def vc_for_python(self):
         """
         Microsoft Visual C++ for Python registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
         return r'DevDiv\VCForPython'
 
@@ -418,11 +384,6 @@ class RegistryInfo:
     def microsoft_sdk(self):
         """
         Microsoft SDK registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
         return 'Microsoft SDKs'
 
@@ -430,35 +391,20 @@ class RegistryInfo:
     def windows_sdk(self):
         """
         Microsoft Windows/Platform SDK registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
-        return join(self.microsoft_sdk, 'Windows')
+        return os.path.join(self.microsoft_sdk, 'Windows')
 
     @property
     def netfx_sdk(self):
         """
         Microsoft .NET Framework SDK registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
-        return join(self.microsoft_sdk, 'NETFXSDK')
+        return os.path.join(self.microsoft_sdk, 'NETFXSDK')
 
     @property
     def windows_kits_roots(self):
         """
         Microsoft Windows Kits Roots registry key.
-
-        Return
-        ------
-        str
-            Registry key
         """
         return r'Windows Kits\Installed Roots'
 
@@ -475,11 +421,10 @@ class RegistryInfo:
 
         Return
         ------
-        str
-            Registry key
+        str: value
         """
         node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
-        return join('Software', node64, 'Microsoft', key)
+        return os.path.join('Software', node64, 'Microsoft', key)
 
     def lookup(self, key, name):
         """
@@ -494,19 +439,18 @@ class RegistryInfo:
 
         Return
         ------
-        str
-            value
+        str: value
         """
-        key_read = winreg.KEY_READ
+        KEY_READ = winreg.KEY_READ
         openkey = winreg.OpenKey
         ms = self.microsoft
         for hkey in self.HKEYS:
             try:
-                bkey = openkey(hkey, ms(key), 0, key_read)
+                bkey = openkey(hkey, ms(key), 0, KEY_READ)
             except (OSError, IOError):
                 if not self.pi.current_is_x86():
                     try:
-                        bkey = openkey(hkey, ms(key, True), 0, key_read)
+                        bkey = openkey(hkey, ms(key, True), 0, KEY_READ)
                     except (OSError, IOError):
                         continue
                 else:
@@ -519,7 +463,7 @@ class RegistryInfo:
 
 class SystemInfo:
     """
-    Microsoft Windows and Visual Studio related system information.
+    Microsoft Windows and Visual Studio related system inormations.
 
     Parameters
     ----------
@@ -530,52 +474,30 @@ class SystemInfo:
     """
 
     # Variables and properties in this class use originals CamelCase variables
-    # names from Microsoft source files for more easy comparison.
-    WinDir = environ.get('WinDir', '')
-    ProgramFiles = environ.get('ProgramFiles', '')
-    ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
+    # names from Microsoft source files for more easy comparaison.
+    WinDir = safe_env.get('WinDir', '')
+    ProgramFiles = safe_env.get('ProgramFiles', '')
+    ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles)
 
     def __init__(self, registry_info, vc_ver=None):
         self.ri = registry_info
         self.pi = self.ri.pi
+        self.vc_ver = vc_ver or self._find_latest_available_vc_ver()
 
-        self.known_vs_paths = self.find_programdata_vs_vers()
-
-        # Except for VS15+, VC version is aligned with VS version
-        self.vs_ver = self.vc_ver = (
-                vc_ver or self._find_latest_available_vs_ver())
-
-    def _find_latest_available_vs_ver(self):
-        """
-        Find the latest VC version
-
-        Return
-        ------
-        float
-            version
-        """
-        reg_vc_vers = self.find_reg_vs_vers()
-
-        if not (reg_vc_vers or self.known_vs_paths):
-            raise distutils.errors.DistutilsPlatformError(
-                'No Microsoft Visual C++ version found')
-
-        vc_vers = set(reg_vc_vers)
-        vc_vers.update(self.known_vs_paths)
-        return sorted(vc_vers)[-1]
+    def _find_latest_available_vc_ver(self):
+        try:
+            return self.find_available_vc_vers()[-1]
+        except IndexError:
+            err = 'No Microsoft Visual C++ version found'
+            raise distutils.errors.DistutilsPlatformError(err)
 
-    def find_reg_vs_vers(self):
+    def find_available_vc_vers(self):
         """
-        Find Microsoft Visual Studio versions available in registry.
-
-        Return
-        ------
-        list of float
-            Versions
+        Find all available Microsoft Visual C++ versions.
         """
         ms = self.ri.microsoft
         vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
-        vs_vers = []
+        vc_vers = []
         for hkey in self.ri.HKEYS:
             for key in vckeys:
                 try:
@@ -586,108 +508,49 @@ class SystemInfo:
                 for i in range(values):
                     try:
                         ver = float(winreg.EnumValue(bkey, i)[0])
-                        if ver not in vs_vers:
-                            vs_vers.append(ver)
+                        if ver not in vc_vers:
+                            vc_vers.append(ver)
                     except ValueError:
                         pass
                 for i in range(subkeys):
                     try:
                         ver = float(winreg.EnumKey(bkey, i))
-                        if ver not in vs_vers:
-                            vs_vers.append(ver)
+                        if ver not in vc_vers:
+                            vc_vers.append(ver)
                     except ValueError:
                         pass
-        return sorted(vs_vers)
-
-    def find_programdata_vs_vers(self):
-        r"""
-        Find Visual studio 2017+ versions from information in
-        "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
-
-        Return
-        ------
-        dict
-            float version as key, path as value.
-        """
-        vs_versions = {}
-        instances_dir = \
-            r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
-
-        try:
-            hashed_names = listdir(instances_dir)
-
-        except (OSError, IOError):
-            # Directory not exists with all Visual Studio versions
-            return vs_versions
-
-        for name in hashed_names:
-            try:
-                # Get VS installation path from "state.json" file
-                state_path = join(instances_dir, name, 'state.json')
-                with open(state_path, 'rt', encoding='utf-8') as state_file:
-                    state = json.load(state_file)
-                vs_path = state['installationPath']
-
-                # Raises OSError if this VS installation does not contain VC
-                listdir(join(vs_path, r'VC\Tools\MSVC'))
-
-                # Store version and path
-                vs_versions[self._as_float_version(
-                    state['installationVersion'])] = vs_path
-
-            except (OSError, IOError, KeyError):
-                # Skip if "state.json" file is missing or bad format
-                continue
-
-        return vs_versions
-
-    @staticmethod
-    def _as_float_version(version):
-        """
-        Return a string version as a simplified float version (major.minor)
-
-        Parameters
-        ----------
-        version: str
-            Version.
-
-        Return
-        ------
-        float
-            version
-        """
-        return float('.'.join(version.split('.')[:2]))
+        return sorted(vc_vers)
 
     @property
     def VSInstallDir(self):
         """
         Microsoft Visual Studio directory.
-
-        Return
-        ------
-        str
-            path
         """
         # Default path
-        default = join(self.ProgramFilesx86,
-                       'Microsoft Visual Studio %0.1f' % self.vs_ver)
+        name = 'Microsoft Visual Studio %0.1f' % self.vc_ver
+        default = os.path.join(self.ProgramFilesx86, name)
 
         # Try to get path from registry, if fail use default path
-        return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default
+        return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default
 
     @property
     def VCInstallDir(self):
         """
         Microsoft Visual C++ directory.
-
-        Return
-        ------
-        str
-            path
         """
-        path = self._guess_vc() or self._guess_vc_legacy()
+        self.VSInstallDir
+
+        guess_vc = self._guess_vc() or self._guess_vc_legacy()
+
+        # Try to get "VC++ for Python" path from registry as default path
+        reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
+        python_vc = self.ri.lookup(reg_path, 'installdir')
+        default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc
 
-        if not isdir(path):
+        # Try to get path from registry, if fail use default path
+        path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc
+
+        if not os.path.isdir(path):
             msg = 'Microsoft Visual C++ directory not found'
             raise distutils.errors.DistutilsPlatformError(msg)
 
@@ -695,256 +558,186 @@ class SystemInfo:
 
     def _guess_vc(self):
         """
-        Locate Visual C++ for VS2017+.
-
-        Return
-        ------
-        str
-            path
+        Locate Visual C for 2017
         """
-        if self.vs_ver <= 14.0:
-            return ''
-
-        try:
-            # First search in known VS paths
-            vs_dir = self.known_vs_paths[self.vs_ver]
-        except KeyError:
-            # Else, search with path from registry
-            vs_dir = self.VSInstallDir
-
-        guess_vc = join(vs_dir, r'VC\Tools\MSVC')
+        if self.vc_ver <= 14.0:
+            return
 
+        default = r'VC\Tools\MSVC'
+        guess_vc = os.path.join(self.VSInstallDir, default)
         # Subdir with VC exact version as name
         try:
-            # Update the VC version with real one instead of VS version
-            vc_ver = listdir(guess_vc)[-1]
-            self.vc_ver = self._as_float_version(vc_ver)
-            return join(guess_vc, vc_ver)
+            vc_exact_ver = os.listdir(guess_vc)[-1]
+            return os.path.join(guess_vc, vc_exact_ver)
         except (OSError, IOError, IndexError):
-            return ''
+            pass
 
     def _guess_vc_legacy(self):
         """
-        Locate Visual C++ for versions prior to 2017.
-
-        Return
-        ------
-        str
-            path
+        Locate Visual C for versions prior to 2017
         """
-        default = join(self.ProgramFilesx86,
-                       r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver)
-
-        # Try to get "VC++ for Python" path from registry as default path
-        reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)
-        python_vc = self.ri.lookup(reg_path, 'installdir')
-        default_vc = join(python_vc, 'VC') if python_vc else default
-
-        # Try to get path from registry, if fail use default path
-        return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc
+        default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
+        return os.path.join(self.ProgramFilesx86, default)
 
     @property
     def WindowsSdkVersion(self):
         """
         Microsoft Windows SDK versions for specified MSVC++ version.
-
-        Return
-        ------
-        tuple of str
-            versions
-        """
-        if self.vs_ver <= 9.0:
-            return '7.0', '6.1', '6.0a'
-        elif self.vs_ver == 10.0:
-            return '7.1', '7.0a'
-        elif self.vs_ver == 11.0:
-            return '8.0', '8.0a'
-        elif self.vs_ver == 12.0:
-            return '8.1', '8.1a'
-        elif self.vs_ver >= 14.0:
-            return '10.0', '8.1'
+        """
+        if self.vc_ver <= 9.0:
+            return ('7.0', '6.1', '6.0a')
+        elif self.vc_ver == 10.0:
+            return ('7.1', '7.0a')
+        elif self.vc_ver == 11.0:
+            return ('8.0', '8.0a')
+        elif self.vc_ver == 12.0:
+            return ('8.1', '8.1a')
+        elif self.vc_ver >= 14.0:
+            return ('10.0', '8.1')
 
     @property
     def WindowsSdkLastVersion(self):
         """
-        Microsoft Windows SDK last version.
-
-        Return
-        ------
-        str
-            version
+        Microsoft Windows SDK last version
         """
-        return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
+        return self._use_last_dir_name(os.path.join(
+            self.WindowsSdkDir, 'lib'))
 
     @property
     def WindowsSdkDir(self):
         """
         Microsoft Windows SDK directory.
-
-        Return
-        ------
-        str
-            path
         """
         sdkdir = ''
         for ver in self.WindowsSdkVersion:
             # Try to get it from registry
-            loc = join(self.ri.windows_sdk, 'v%s' % ver)
+            loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver)
             sdkdir = self.ri.lookup(loc, 'installationfolder')
             if sdkdir:
                 break
-        if not sdkdir or not isdir(sdkdir):
+        if not sdkdir or not os.path.isdir(sdkdir):
             # Try to get "VC++ for Python" version from registry
-            path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
+            path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
             install_base = self.ri.lookup(path, 'installdir')
             if install_base:
-                sdkdir = join(install_base, 'WinSDK')
-        if not sdkdir or not isdir(sdkdir):
+                sdkdir = os.path.join(install_base, 'WinSDK')
+        if not sdkdir or not os.path.isdir(sdkdir):
             # If fail, use default new path
             for ver in self.WindowsSdkVersion:
                 intver = ver[:ver.rfind('.')]
-                path = r'Microsoft SDKs\Windows Kits\%s' % intver
-                d = join(self.ProgramFiles, path)
-                if isdir(d):
+                path = r'Microsoft SDKs\Windows Kits\%s' % (intver)
+                d = os.path.join(self.ProgramFiles, path)
+                if os.path.isdir(d):
                     sdkdir = d
-        if not sdkdir or not isdir(sdkdir):
+        if not sdkdir or not os.path.isdir(sdkdir):
             # If fail, use default old path
             for ver in self.WindowsSdkVersion:
                 path = r'Microsoft SDKs\Windows\v%s' % ver
-                d = join(self.ProgramFiles, path)
-                if isdir(d):
+                d = os.path.join(self.ProgramFiles, path)
+                if os.path.isdir(d):
                     sdkdir = d
         if not sdkdir:
             # If fail, use Platform SDK
-            sdkdir = join(self.VCInstallDir, 'PlatformSDK')
+            sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
         return sdkdir
 
     @property
     def WindowsSDKExecutablePath(self):
         """
         Microsoft Windows SDK executable directory.
-
-        Return
-        ------
-        str
-            path
         """
         # Find WinSDK NetFx Tools registry dir name
-        if self.vs_ver <= 11.0:
+        if self.vc_ver <= 11.0:
             netfxver = 35
             arch = ''
         else:
             netfxver = 40
-            hidex86 = True if self.vs_ver <= 12.0 else False
+            hidex86 = True if self.vc_ver <= 12.0 else False
             arch = self.pi.current_dir(x64=True, hidex86=hidex86)
         fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))
 
-        # list all possibles registry paths
+        # liste all possibles registry paths
         regpaths = []
-        if self.vs_ver >= 14.0:
+        if self.vc_ver >= 14.0:
             for ver in self.NetFxSdkVersion:
-                regpaths += [join(self.ri.netfx_sdk, ver, fx)]
+                regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
 
         for ver in self.WindowsSdkVersion:
-            regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
+            regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
 
         # Return installation folder from the more recent path
         for path in regpaths:
             execpath = self.ri.lookup(path, 'installationfolder')
             if execpath:
-                return execpath
+                break
+        return execpath
 
     @property
     def FSharpInstallDir(self):
         """
         Microsoft Visual F# directory.
-
-        Return
-        ------
-        str
-            path
         """
-        path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)
+        path = r'%0.1f\Setup\F#' % self.vc_ver
+        path = os.path.join(self.ri.visualstudio, path)
         return self.ri.lookup(path, 'productdir') or ''
 
     @property
     def UniversalCRTSdkDir(self):
         """
         Microsoft Universal CRT SDK directory.
-
-        Return
-        ------
-        str
-            path
         """
         # Set Kit Roots versions for specified MSVC++ version
-        vers = ('10', '81') if self.vs_ver >= 14.0 else ()
+        if self.vc_ver >= 14.0:
+            vers = ('10', '81')
+        else:
+            vers = ()
 
         # Find path of the more recent Kit
         for ver in vers:
             sdkdir = self.ri.lookup(self.ri.windows_kits_roots,
                                     'kitsroot%s' % ver)
             if sdkdir:
-                return sdkdir or ''
+                break
+        return sdkdir or ''
 
     @property
     def UniversalCRTSdkLastVersion(self):
         """
-        Microsoft Universal C Runtime SDK last version.
-
-        Return
-        ------
-        str
-            version
+        Microsoft Universal C Runtime SDK last version
         """
-        return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))
+        return self._use_last_dir_name(os.path.join(
+            self.UniversalCRTSdkDir, 'lib'))
 
     @property
     def NetFxSdkVersion(self):
         """
         Microsoft .NET Framework SDK versions.
-
-        Return
-        ------
-        tuple of str
-            versions
         """
-        # Set FxSdk versions for specified VS version
-        return (('4.7.2', '4.7.1', '4.7',
-                 '4.6.2', '4.6.1', '4.6',
-                 '4.5.2', '4.5.1', '4.5')
-                if self.vs_ver >= 14.0 else ())
+        # Set FxSdk versions for specified MSVC++ version
+        if self.vc_ver >= 14.0:
+            return ('4.6.1', '4.6')
+        else:
+            return ()
 
     @property
     def NetFxSdkDir(self):
         """
         Microsoft .NET Framework SDK directory.
-
-        Return
-        ------
-        str
-            path
         """
-        sdkdir = ''
         for ver in self.NetFxSdkVersion:
-            loc = join(self.ri.netfx_sdk, ver)
+            loc = os.path.join(self.ri.netfx_sdk, ver)
             sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
             if sdkdir:
                 break
-        return sdkdir
+        return sdkdir or ''
 
     @property
     def FrameworkDir32(self):
         """
         Microsoft .NET Framework 32bit directory.
-
-        Return
-        ------
-        str
-            path
         """
         # Default path
-        guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')
+        guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
 
         # Try to get path from registry, if fail use default path
         return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
@@ -953,14 +746,9 @@ class SystemInfo:
     def FrameworkDir64(self):
         """
         Microsoft .NET Framework 64bit directory.
-
-        Return
-        ------
-        str
-            path
         """
         # Default path
-        guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')
+        guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
 
         # Try to get path from registry, if fail use default path
         return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
@@ -969,11 +757,6 @@ class SystemInfo:
     def FrameworkVersion32(self):
         """
         Microsoft .NET Framework 32bit versions.
-
-        Return
-        ------
-        tuple of str
-            versions
         """
         return self._find_dot_net_versions(32)
 
@@ -981,11 +764,6 @@ class SystemInfo:
     def FrameworkVersion64(self):
         """
         Microsoft .NET Framework 64bit versions.
-
-        Return
-        ------
-        tuple of str
-            versions
         """
         return self._find_dot_net_versions(64)
 
@@ -997,11 +775,6 @@ class SystemInfo:
         ----------
         bits: int
             Platform number of bits: 32 or 64.
-
-        Return
-        ------
-        tuple of str
-            versions
         """
         # Find actual .NET version in registry
         reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)
@@ -1009,17 +782,18 @@ class SystemInfo:
         ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
 
         # Set .NET versions for specified MSVC++ version
-        if self.vs_ver >= 12.0:
-            return ver, 'v4.0'
-        elif self.vs_ver >= 10.0:
-            return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
-        elif self.vs_ver == 9.0:
-            return 'v3.5', 'v2.0.50727'
-        elif self.vs_ver == 8.0:
-            return 'v3.0', 'v2.0.50727'
-
-    @staticmethod
-    def _use_last_dir_name(path, prefix=''):
+        if self.vc_ver >= 12.0:
+            frameworkver = (ver, 'v4.0')
+        elif self.vc_ver >= 10.0:
+            frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver,
+                            'v3.5')
+        elif self.vc_ver == 9.0:
+            frameworkver = ('v3.5', 'v2.0.50727')
+        if self.vc_ver == 8.0:
+            frameworkver = ('v3.0', 'v2.0.50727')
+        return frameworkver
+
+    def _use_last_dir_name(self, path, prefix=''):
         """
         Return name of the last dir in path or '' if no dir found.
 
@@ -1028,17 +802,12 @@ class SystemInfo:
         path: str
             Use dirs in this path
         prefix: str
-            Use only dirs starting by this prefix
-
-        Return
-        ------
-        str
-            name
+            Use only dirs startings by this prefix
         """
         matching_dirs = (
             dir_name
-            for dir_name in reversed(listdir(path))
-            if isdir(join(path, dir_name)) and
+            for dir_name in reversed(os.listdir(path))
+            if os.path.isdir(os.path.join(path, dir_name)) and
             dir_name.startswith(prefix)
         )
         return next(matching_dirs, None) or ''
@@ -1049,7 +818,7 @@ class EnvironmentInfo:
     Return environment variables for specified Microsoft Visual C++ version
     and platform : Lib, Include, Path and libpath.
 
-    This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
+    This function is compatible with Microsoft Visual C++ 9.0 to 14.0.
 
     Script created by analysing Microsoft environment configuration files like
     "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
@@ -1066,7 +835,7 @@ class EnvironmentInfo:
     """
 
     # Variables and properties in this class use originals CamelCase variables
-    # names from Microsoft source files for more easy comparison.
+    # names from Microsoft source files for more easy comparaison.
 
     def __init__(self, arch, vc_ver=None, vc_min_ver=0):
         self.pi = PlatformInfo(arch)
@@ -1077,255 +846,205 @@ class EnvironmentInfo:
             err = 'No suitable Microsoft Visual C++ version found'
             raise distutils.errors.DistutilsPlatformError(err)
 
-    @property
-    def vs_ver(self):
-        """
-        Microsoft Visual Studio.
-
-        Return
-        ------
-        float
-            version
-        """
-        return self.si.vs_ver
-
     @property
     def vc_ver(self):
         """
         Microsoft Visual C++ version.
-
-        Return
-        ------
-        float
-            version
         """
         return self.si.vc_ver
 
     @property
     def VSTools(self):
         """
-        Microsoft Visual Studio Tools.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual Studio Tools
         """
         paths = [r'Common7\IDE', r'Common7\Tools']
 
-        if self.vs_ver >= 14.0:
+        if self.vc_ver >= 14.0:
             arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
             paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
             paths += [r'Team Tools\Performance Tools']
             paths += [r'Team Tools\Performance Tools%s' % arch_subdir]
 
-        return [join(self.si.VSInstallDir, path) for path in paths]
+        return [os.path.join(self.si.VSInstallDir, path) for path in paths]
 
     @property
     def VCIncludes(self):
         """
-        Microsoft Visual C++ & Microsoft Foundation Class Includes.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual C++ & Microsoft Foundation Class Includes
         """
-        return [join(self.si.VCInstallDir, 'Include'),
-                join(self.si.VCInstallDir, r'ATLMFC\Include')]
+        return [os.path.join(self.si.VCInstallDir, 'Include'),
+                os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')]
 
     @property
     def VCLibraries(self):
         """
-        Microsoft Visual C++ & Microsoft Foundation Class Libraries.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual C++ & Microsoft Foundation Class Libraries
         """
-        if self.vs_ver >= 15.0:
+        if self.vc_ver >= 15.0:
             arch_subdir = self.pi.target_dir(x64=True)
         else:
             arch_subdir = self.pi.target_dir(hidex86=True)
         paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir]
 
-        if self.vs_ver >= 14.0:
+        if self.vc_ver >= 14.0:
             paths += [r'Lib\store%s' % arch_subdir]
 
-        return [join(self.si.VCInstallDir, path) for path in paths]
+        return [os.path.join(self.si.VCInstallDir, path) for path in paths]
 
     @property
     def VCStoreRefs(self):
         """
-        Microsoft Visual C++ store references Libraries.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual C++ store references Libraries
         """
-        if self.vs_ver < 14.0:
+        if self.vc_ver < 14.0:
             return []
-        return [join(self.si.VCInstallDir, r'Lib\store\references')]
+        return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
 
     @property
     def VCTools(self):
         """
-        Microsoft Visual C++ Tools.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual C++ Tools
         """
         si = self.si
-        tools = [join(si.VCInstallDir, 'VCPackages')]
+        tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
 
-        forcex86 = True if self.vs_ver <= 10.0 else False
+        forcex86 = True if self.vc_ver <= 10.0 else False
         arch_subdir = self.pi.cross_dir(forcex86)
         if arch_subdir:
-            tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
+            tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
 
-        if self.vs_ver == 14.0:
+        if self.vc_ver == 14.0:
             path = 'Bin%s' % self.pi.current_dir(hidex86=True)
-            tools += [join(si.VCInstallDir, path)]
+            tools += [os.path.join(si.VCInstallDir, path)]
 
-        elif self.vs_ver >= 15.0:
+        elif self.vc_ver >= 15.0:
             host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
                         r'bin\HostX64%s')
-            tools += [join(
+            tools += [os.path.join(
                 si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
 
             if self.pi.current_cpu != self.pi.target_cpu:
-                tools += [join(
+                tools += [os.path.join(
                     si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
 
         else:
-            tools += [join(si.VCInstallDir, 'Bin')]
+            tools += [os.path.join(si.VCInstallDir, 'Bin')]
 
         return tools
 
     @property
     def OSLibraries(self):
         """
-        Microsoft Windows SDK Libraries.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Windows SDK Libraries
         """
-        if self.vs_ver <= 10.0:
+        if self.vc_ver <= 10.0:
             arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
-            return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
+            return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
 
         else:
             arch_subdir = self.pi.target_dir(x64=True)
-            lib = join(self.si.WindowsSdkDir, 'lib')
+            lib = os.path.join(self.si.WindowsSdkDir, 'lib')
             libver = self._sdk_subdir
-            return [join(lib, '%sum%s' % (libver , arch_subdir))]
+            return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))]
 
     @property
     def OSIncludes(self):
         """
-        Microsoft Windows SDK Include.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Windows SDK Include
         """
-        include = join(self.si.WindowsSdkDir, 'include')
+        include = os.path.join(self.si.WindowsSdkDir, 'include')
 
-        if self.vs_ver <= 10.0:
-            return [include, join(include, 'gl')]
+        if self.vc_ver <= 10.0:
+            return [include, os.path.join(include, 'gl')]
 
         else:
-            if self.vs_ver >= 14.0:
+            if self.vc_ver >= 14.0:
                 sdkver = self._sdk_subdir
             else:
                 sdkver = ''
-            return [join(include, '%sshared' % sdkver),
-                    join(include, '%sum' % sdkver),
-                    join(include, '%swinrt' % sdkver)]
+            return [os.path.join(include, '%sshared' % sdkver),
+                    os.path.join(include, '%sum' % sdkver),
+                    os.path.join(include, '%swinrt' % sdkver)]
 
     @property
     def OSLibpath(self):
         """
-        Microsoft Windows SDK Libraries Paths.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Windows SDK Libraries Paths
         """
-        ref = join(self.si.WindowsSdkDir, 'References')
+        ref = os.path.join(self.si.WindowsSdkDir, 'References')
         libpath = []
 
-        if self.vs_ver <= 9.0:
+        if self.vc_ver <= 9.0:
             libpath += self.OSLibraries
 
-        if self.vs_ver >= 11.0:
-            libpath += [join(ref, r'CommonConfiguration\Neutral')]
+        if self.vc_ver >= 11.0:
+            libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
 
-        if self.vs_ver >= 14.0:
+        if self.vc_ver >= 14.0:
             libpath += [
                 ref,
-                join(self.si.WindowsSdkDir, 'UnionMetadata'),
-                join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
-                join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
-                join(ref,'Windows.Networking.Connectivity.WwanContract',
-                     '1.0.0.0'),
-                join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
-                     '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
-                     'neutral'),
+                os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
+                os.path.join(
+                    ref,
+                    'Windows.Foundation.UniversalApiContract',
+                    '1.0.0.0',
+                ),
+                os.path.join(
+                    ref,
+                    'Windows.Foundation.FoundationContract',
+                    '1.0.0.0',
+                ),
+                os.path.join(
+                    ref,
+                    'Windows.Networking.Connectivity.WwanContract',
+                    '1.0.0.0',
+                ),
+                os.path.join(
+                    self.si.WindowsSdkDir,
+                    'ExtensionSDKs',
+                    'Microsoft.VCLibs',
+                    '%0.1f' % self.vc_ver,
+                    'References',
+                    'CommonConfiguration',
+                    'neutral',
+                ),
             ]
         return libpath
 
     @property
     def SdkTools(self):
         """
-        Microsoft Windows SDK Tools.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Windows SDK Tools
         """
         return list(self._sdk_tools())
 
     def _sdk_tools(self):
         """
-        Microsoft Windows SDK Tools paths generator.
-
-        Return
-        ------
-        generator of str
-            paths
+        Microsoft Windows SDK Tools paths generator
         """
-        if self.vs_ver < 15.0:
-            bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
-            yield join(self.si.WindowsSdkDir, bin_dir)
+        if self.vc_ver < 15.0:
+            bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86'
+            yield os.path.join(self.si.WindowsSdkDir, bin_dir)
 
         if not self.pi.current_is_x86():
             arch_subdir = self.pi.current_dir(x64=True)
             path = 'Bin%s' % arch_subdir
-            yield join(self.si.WindowsSdkDir, path)
+            yield os.path.join(self.si.WindowsSdkDir, path)
 
-        if self.vs_ver in (10.0, 11.0):
+        if self.vc_ver == 10.0 or self.vc_ver == 11.0:
             if self.pi.target_is_x86():
                 arch_subdir = ''
             else:
                 arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
             path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
-            yield join(self.si.WindowsSdkDir, path)
+            yield os.path.join(self.si.WindowsSdkDir, path)
 
-        elif self.vs_ver >= 15.0:
-            path = join(self.si.WindowsSdkDir, 'Bin')
+        elif self.vc_ver >= 15.0:
+            path = os.path.join(self.si.WindowsSdkDir, 'Bin')
             arch_subdir = self.pi.current_dir(x64=True)
             sdkver = self.si.WindowsSdkLastVersion
-            yield join(path, '%s%s' % (sdkver, arch_subdir))
+            yield os.path.join(path, '%s%s' % (sdkver, arch_subdir))
 
         if self.si.WindowsSDKExecutablePath:
             yield self.si.WindowsSDKExecutablePath
@@ -1333,12 +1052,7 @@ class EnvironmentInfo:
     @property
     def _sdk_subdir(self):
         """
-        Microsoft Windows SDK version subdir.
-
-        Return
-        ------
-        str
-            subdir
+        Microsoft Windows SDK version subdir
         """
         ucrtver = self.si.WindowsSdkLastVersion
         return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1346,32 +1060,22 @@ class EnvironmentInfo:
     @property
     def SdkSetup(self):
         """
-        Microsoft Windows SDK Setup.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Windows SDK Setup
         """
-        if self.vs_ver > 9.0:
+        if self.vc_ver > 9.0:
             return []
 
-        return [join(self.si.WindowsSdkDir, 'Setup')]
+        return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
 
     @property
     def FxTools(self):
         """
-        Microsoft .NET Framework Tools.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft .NET Framework Tools
         """
         pi = self.pi
         si = self.si
 
-        if self.vs_ver <= 10.0:
+        if self.vc_ver <= 10.0:
             include32 = True
             include64 = not pi.target_is_x86() and not pi.current_is_x86()
         else:
@@ -1380,142 +1084,102 @@ class EnvironmentInfo:
 
         tools = []
         if include32:
-            tools += [join(si.FrameworkDir32, ver)
+            tools += [os.path.join(si.FrameworkDir32, ver)
                       for ver in si.FrameworkVersion32]
         if include64:
-            tools += [join(si.FrameworkDir64, ver)
+            tools += [os.path.join(si.FrameworkDir64, ver)
                       for ver in si.FrameworkVersion64]
         return tools
 
     @property
     def NetFxSDKLibraries(self):
         """
-        Microsoft .Net Framework SDK Libraries.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft .Net Framework SDK Libraries
         """
-        if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
+        if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
             return []
 
         arch_subdir = self.pi.target_dir(x64=True)
-        return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
+        return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
 
     @property
     def NetFxSDKIncludes(self):
         """
-        Microsoft .Net Framework SDK Includes.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft .Net Framework SDK Includes
         """
-        if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
+        if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
             return []
 
-        return [join(self.si.NetFxSdkDir, r'include\um')]
+        return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
 
     @property
     def VsTDb(self):
         """
-        Microsoft Visual Studio Team System Database.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual Studio Team System Database
         """
-        return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
+        return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
 
     @property
     def MSBuild(self):
         """
-        Microsoft Build Engine.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Build Engine
         """
-        if self.vs_ver < 12.0:
+        if self.vc_ver < 12.0:
             return []
-        elif self.vs_ver < 15.0:
+        elif self.vc_ver < 15.0:
             base_path = self.si.ProgramFilesx86
             arch_subdir = self.pi.current_dir(hidex86=True)
         else:
             base_path = self.si.VSInstallDir
             arch_subdir = ''
 
-        path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir)
-        build = [join(base_path, path)]
+        path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir)
+        build = [os.path.join(base_path, path)]
 
-        if self.vs_ver >= 15.0:
+        if self.vc_ver >= 15.0:
             # Add Roslyn C# & Visual Basic Compiler
-            build += [join(base_path, path, 'Roslyn')]
+            build += [os.path.join(base_path, path, 'Roslyn')]
 
         return build
 
     @property
     def HTMLHelpWorkshop(self):
         """
-        Microsoft HTML Help Workshop.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft HTML Help Workshop
         """
-        if self.vs_ver < 11.0:
+        if self.vc_ver < 11.0:
             return []
 
-        return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
+        return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
 
     @property
     def UCRTLibraries(self):
         """
-        Microsoft Universal C Runtime SDK Libraries.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Universal C Runtime SDK Libraries
         """
-        if self.vs_ver < 14.0:
+        if self.vc_ver < 14.0:
             return []
 
         arch_subdir = self.pi.target_dir(x64=True)
-        lib = join(self.si.UniversalCRTSdkDir, 'lib')
+        lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
         ucrtver = self._ucrt_subdir
-        return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
+        return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
 
     @property
     def UCRTIncludes(self):
         """
-        Microsoft Universal C Runtime SDK Include.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Universal C Runtime SDK Include
         """
-        if self.vs_ver < 14.0:
+        if self.vc_ver < 14.0:
             return []
 
-        include = join(self.si.UniversalCRTSdkDir, 'include')
-        return [join(include, '%sucrt' % self._ucrt_subdir)]
+        include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
+        return [os.path.join(include, '%sucrt' % self._ucrt_subdir)]
 
     @property
     def _ucrt_subdir(self):
         """
-        Microsoft Universal C Runtime SDK version subdir.
-
-        Return
-        ------
-        str
-            subdir
+        Microsoft Universal C Runtime SDK version subdir
         """
         ucrtver = self.si.UniversalCRTSdkLastVersion
         return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1523,52 +1187,31 @@ class EnvironmentInfo:
     @property
     def FSharp(self):
         """
-        Microsoft Visual F#.
-
-        Return
-        ------
-        list of str
-            paths
+        Microsoft Visual F#
         """
-        if 11.0 > self.vs_ver > 12.0:
+        if self.vc_ver < 11.0 and self.vc_ver > 12.0:
             return []
 
-        return [self.si.FSharpInstallDir]
+        return self.si.FSharpInstallDir
 
     @property
     def VCRuntimeRedist(self):
         """
-        Microsoft Visual C++ runtime redistributable dll.
+        Microsoft Visual C++ runtime redistribuable dll
+        """
+        arch_subdir = self.pi.target_dir(x64=True)
+        if self.vc_ver < 15:
+            redist_path = self.si.VCInstallDir
+            vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
+        else:
+            redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist')
+            vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
 
-        Return
-        ------
-        str
-            path
-        """
-        vcruntime = 'vcruntime%d0.dll' % self.vc_ver
-        arch_subdir = self.pi.target_dir(x64=True).strip('\\')
-
-        # Installation prefixes candidates
-        prefixes = []
-        tools_path = self.si.VCInstallDir
-        redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist'))
-        if isdir(redist_path):
-            # Redist version may not be exactly the same as tools
-            redist_path = join(redist_path, listdir(redist_path)[-1])
-            prefixes += [redist_path, join(redist_path, 'onecore')]
-
-        prefixes += [join(tools_path, 'redist')]  # VS14 legacy path
-
-        # CRT directory
-        crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10),
-                    # Sometime store in directory with VS version instead of VC
-                    'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10))
-
-        # vcruntime path
-        for prefix, crt_dir in itertools.product(prefixes, crt_dirs):
-            path = join(prefix, arch_subdir, crt_dir, vcruntime)
-            if isfile(path):
-                return path
+        # Visual Studio 2017  is still Visual C++ 14.0
+        dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver
+
+        vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver)
+        return os.path.join(redist_path, vcruntime)
 
     def return_env(self, exists=True):
         """
@@ -1578,11 +1221,6 @@ class EnvironmentInfo:
         ----------
         exists: bool
             It True, only return existing paths.
-
-        Return
-        ------
-        dict
-            environment
         """
         env = dict(
             include=self._build_paths('include',
@@ -1616,7 +1254,7 @@ class EnvironmentInfo:
                                     self.FSharp],
                                    exists),
         )
-        if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist):
+        if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist):
             env['py_vcruntime_redist'] = self.VCRuntimeRedist
         return env
 
@@ -1627,35 +1265,20 @@ class EnvironmentInfo:
         unique, extant, directories from those paths and from
         the environment variable. Raise an error if no paths
         are resolved.
-
-        Parameters
-        ----------
-        name: str
-            Environment variable name
-        spec_path_lists: list of str
-            Paths
-        exists: bool
-            It True, only return existing paths.
-
-        Return
-        ------
-        str
-            Pathsep-separated paths
         """
         # flatten spec_path_lists
         spec_paths = itertools.chain.from_iterable(spec_path_lists)
-        env_paths = environ.get(name, '').split(pathsep)
+        env_paths = safe_env.get(name, '').split(os.pathsep)
         paths = itertools.chain(spec_paths, env_paths)
-        extant_paths = list(filter(isdir, paths)) if exists else paths
+        extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
         if not extant_paths:
             msg = "%s environment variable is empty" % name.upper()
             raise distutils.errors.DistutilsPlatformError(msg)
         unique_paths = self._unique_everseen(extant_paths)
-        return pathsep.join(unique_paths)
+        return os.pathsep.join(unique_paths)
 
     # from Python docs
-    @staticmethod
-    def _unique_everseen(iterable, key=None):
+    def _unique_everseen(self, iterable, key=None):
         """
         List unique elements, preserving order.
         Remember all elements ever seen.
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index f419d4716..6b06f2ca2 100644
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -46,7 +46,7 @@ __all__ = [
 _SOCKET_TIMEOUT = 15
 
 _tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}"
-user_agent = _tmpl.format(py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools)
+user_agent = _tmpl.format(py_major=sys.version[:3], setuptools=setuptools)
 
 
 def parse_requirement_arg(spec):
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
index 2985011b9..1d57360f4 100644
--- a/setuptools/py27compat.py
+++ b/setuptools/py27compat.py
@@ -2,6 +2,7 @@
 Compatibility Support for Python 2.7 and earlier
 """
 
+import sys
 import platform
 
 from setuptools.extern import six
@@ -26,3 +27,34 @@ linux_py2_ascii = (
 
 rmtree_safe = str if linux_py2_ascii else lambda x: x
 """Workaround for http://bugs.python.org/issue24672"""
+
+
+try:
+    from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
+    from ._imp import get_frozen_object, get_module
+except ImportError:
+    import imp
+    from imp import PY_COMPILED, PY_FROZEN, PY_SOURCE  # noqa
+
+    def find_module(module, paths=None):
+        """Just like 'imp.find_module()', but with package support"""
+        parts = module.split('.')
+        while parts:
+            part = parts.pop(0)
+            f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
+
+            if kind == imp.PKG_DIRECTORY:
+                parts = parts or ['__init__']
+                paths = [path]
+
+            elif parts:
+                raise ImportError("Can't find %r in %s" % (parts, module))
+
+        return info
+
+    def get_frozen_object(module, paths):
+        return imp.get_frozen_object(module)
+
+    def get_module(module, paths, info):
+        imp.load_module(module, *info)
+        return sys.modules[module]
diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py
new file mode 100644
index 000000000..3ad917222
--- /dev/null
+++ b/setuptools/py34compat.py
@@ -0,0 +1,13 @@
+import importlib
+
+try:
+    import importlib.util
+except ImportError:
+    pass
+
+
+try:
+    module_from_spec = importlib.util.module_from_spec
+except AttributeError:
+    def module_from_spec(spec):
+        return spec.loader.load_module(spec.name)


Test patch:
diff --git a/conftest.py b/conftest.py
index 0d7b274c0..1746bfb58 100644
--- a/conftest.py
+++ b/conftest.py
@@ -19,6 +19,7 @@ collect_ignore = [
 
 if sys.version_info < (3,):
     collect_ignore.append('setuptools/lib2to3_ex.py')
+    collect_ignore.append('setuptools/_imp.py')
 
 
 if sys.version_info < (3, 6):
diff --git a/pkg_resources/api_tests.txt b/pkg_resources/api_tests.txt
index 7ae5a038d..0a75170e4 100644
--- a/pkg_resources/api_tests.txt
+++ b/pkg_resources/api_tests.txt
@@ -36,7 +36,7 @@ Distributions have various introspectable attributes::
     >>> dist.version
     '0.9'
 
-    >>> dist.py_version == '{}.{}'.format(*sys.version_info)
+    >>> dist.py_version == sys.version[:3]
     True
 
     >>> print(dist.platform)
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index 93fa71143..86afcf741 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -116,7 +116,7 @@ class TestDistro:
         self.checkFooPkg(d)
 
         d = Distribution("/some/path")
-        assert d.py_version == '{}.{}'.format(*sys.version_info)
+        assert d.py_version == sys.version[:3]
         assert d.platform is None
 
     def testDistroParse(self):
@@ -520,11 +520,6 @@ class TestRequirements:
         assert r1 == r2
         assert str(r1) == str(r2)
         assert str(r2) == "Twisted==1.2c1,>=1.2"
-        assert (
-            Requirement("Twisted")
-            !=
-            Requirement("Twisted @ https://localhost/twisted.zip")
-        )
 
     def testBasicContains(self):
         r = Requirement("Twisted>=1.2")
@@ -551,23 +546,11 @@ class TestRequirements:
             ==
             hash((
                 "twisted",
-                None,
                 packaging.specifiers.SpecifierSet(">=1.2"),
                 frozenset(["foo", "bar"]),
                 None
             ))
         )
-        assert (
-            hash(Requirement.parse("Twisted @ https://localhost/twisted.zip"))
-            ==
-            hash((
-                "twisted",
-                "https://localhost/twisted.zip",
-                packaging.specifiers.SpecifierSet(),
-                frozenset(),
-                None
-            ))
-        )
 
     def testVersionEquality(self):
         r1 = Requirement.parse("foo==0.3a2")
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index c148b38d1..973e4eb21 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -74,7 +74,7 @@ class NonDataProperty:
 class test(Command):
     """Command to run unit tests after in-place build"""
 
-    description = "run unit tests after in-place build (deprecated)"
+    description = "run unit tests after in-place build"
 
     user_options = [
         ('test-module=', 'm', "Run 'test_suite' in specified module"),
@@ -214,14 +214,6 @@ class test(Command):
         return itertools.chain(ir_d, tr_d, er_d)
 
     def run(self):
-        self.announce(
-            "WARNING: Testing via this command is deprecated and will be "
-            "removed in a future version. Users looking for a generic test "
-            "entry point independent of test runner are encouraged to use "
-            "tox.",
-            log.WARN,
-        )
-
         installed_dists = self.install_dists(self.distribution)
 
         cmd = ' '.join(self._argv)
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
index fb5b90b1a..54742aa64 100644
--- a/setuptools/tests/test_bdist_egg.py
+++ b/setuptools/tests/test_bdist_egg.py
@@ -42,7 +42,7 @@ class Test:
 
         # let's see if we got our egg link at the right place
         [content] = os.listdir('dist')
-        assert re.match(r'foo-0.0.0-py[23].\d+.egg$', content)
+        assert re.match(r'foo-0.0.0-py[23].\d.egg$', content)
 
     @pytest.mark.xfail(
         os.environ.get('PYTHONDONTWRITEBYTECODE'),
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 69d8d00db..bc97664d4 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: UTF-8 -*-
 from __future__ import unicode_literals
 
 import contextlib
@@ -819,40 +819,6 @@ class TestOptions:
             ]
             assert sorted(dist.data_files) == sorted(expected)
 
-    def test_python_requires_simple(self, tmpdir):
-        fake_env(
-            tmpdir,
-            DALS("""
-            [options]
-            python_requires=>=2.7
-            """),
-        )
-        with get_dist(tmpdir) as dist:
-            dist.parse_config_files()
-
-    def test_python_requires_compound(self, tmpdir):
-        fake_env(
-            tmpdir,
-            DALS("""
-            [options]
-            python_requires=>=2.7,!=3.0.*
-            """),
-        )
-        with get_dist(tmpdir) as dist:
-            dist.parse_config_files()
-
-    def test_python_requires_invalid(self, tmpdir):
-        fake_env(
-            tmpdir,
-            DALS("""
-            [options]
-            python_requires=invalid
-            """),
-        )
-        with pytest.raises(Exception):
-            with get_dist(tmpdir) as dist:
-                dist.parse_config_files()
-
 
 saved_dist_init = _Distribution.__init__
 
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index 36237f24a..c771a19a1 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -3,7 +3,6 @@
 from __future__ import unicode_literals
 
 import io
-import collections
 import re
 from distutils.errors import DistutilsSetupError
 from setuptools.dist import (
@@ -272,19 +271,6 @@ def test_maintainer_author(name, attrs, tmpdir):
             assert line in pkg_lines_set
 
 
-def test_provides_extras_deterministic_order():
-    extras = collections.OrderedDict()
-    extras['a'] = ['foo']
-    extras['b'] = ['bar']
-    attrs = dict(extras_require=extras)
-    dist = Distribution(attrs)
-    assert dist.metadata.provides_extras == ['a', 'b']
-    attrs['extras_require'] = collections.OrderedDict(
-        reversed(list(attrs['extras_require'].items())))
-    dist = Distribution(attrs)
-    assert dist.metadata.provides_extras == ['b', 'a']
-
-   
 CHECK_PACKAGE_DATA_TESTS = (
     # Valid.
     ({
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 1c0b2b18b..1e1321889 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -143,7 +143,6 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
             'tests_require',
             'python_requires',
             'install_requires',
-            'long_description_content_type',
         ]
         assert not match or match.group(1).strip('"\'') in allowed_unknowns
 
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
index 6242a018c..faaa6ba90 100644
--- a/setuptools/tests/test_test.py
+++ b/setuptools/tests/test_test.py
@@ -1,8 +1,7 @@
-# -*- coding: utf-8 -*-
+# -*- coding: UTF-8 -*-
 
 from __future__ import unicode_literals
 
-import mock
 from distutils import log
 import os
 
@@ -86,7 +85,9 @@ def test_test(capfd):
     dist.script_name = 'setup.py'
     cmd = test(dist)
     cmd.ensure_finalized()
-    cmd.run()
+    # The test runner calls sys.exit
+    with contexts.suppress_exceptions(SystemExit):
+        cmd.run()
     out, err = capfd.readouterr()
     assert out == 'Foo\n'
 
@@ -118,55 +119,8 @@ def test_tests_are_run_once(capfd):
     dist.script_name = 'setup.py'
     cmd = test(dist)
     cmd.ensure_finalized()
-    cmd.run()
+    # The test runner calls sys.exit
+    with contexts.suppress_exceptions(SystemExit):
+        cmd.run()
     out, err = capfd.readouterr()
     assert out == 'Foo\n'
-
-
-@pytest.mark.usefixtures('sample_test')
-def test_warns_deprecation(capfd):
-    params = dict(
-        name='foo',
-        packages=['name', 'name.space', 'name.space.tests'],
-        namespace_packages=['name'],
-        test_suite='name.space.tests.test_suite',
-        use_2to3=True
-    )
-    dist = Distribution(params)
-    dist.script_name = 'setup.py'
-    cmd = test(dist)
-    cmd.ensure_finalized()
-    cmd.announce = mock.Mock()
-    cmd.run()
-    capfd.readouterr()
-    msg = (
-        "WARNING: Testing via this command is deprecated and will be "
-        "removed in a future version. Users looking for a generic test "
-        "entry point independent of test runner are encouraged to use "
-        "tox."
-    )
-    cmd.announce.assert_any_call(msg, log.WARN)
-
-
-@pytest.mark.usefixtures('sample_test')
-def test_deprecation_stderr(capfd):
-    params = dict(
-        name='foo',
-        packages=['name', 'name.space', 'name.space.tests'],
-        namespace_packages=['name'],
-        test_suite='name.space.tests.test_suite',
-        use_2to3=True
-    )
-    dist = Distribution(params)
-    dist.script_name = 'setup.py'
-    cmd = test(dist)
-    cmd.ensure_finalized()
-    cmd.run()
-    out, err = capfd.readouterr()
-    msg = (
-        "WARNING: Testing via this command is deprecated and will be "
-        "removed in a future version. Users looking for a generic test "
-        "entry point independent of test runner are encouraged to use "
-        "tox.\n"
-    )
-    assert msg in err
diff --git a/tests/manual_test.py b/tests/manual_test.py
new file mode 100644
index 000000000..99db4b014
--- /dev/null
+++ b/tests/manual_test.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import shutil
+import tempfile
+import subprocess
+from distutils.command.install import INSTALL_SCHEMES
+from string import Template
+
+from setuptools.extern.six.moves import urllib
+
+
+def _system_call(*args):
+    assert subprocess.call(args) == 0
+
+
+def tempdir(func):
+    def _tempdir(*args, **kwargs):
+        test_dir = tempfile.mkdtemp()
+        old_dir = os.getcwd()
+        os.chdir(test_dir)
+        try:
+            return func(*args, **kwargs)
+        finally:
+            os.chdir(old_dir)
+            shutil.rmtree(test_dir)
+
+    return _tempdir
+
+
+SIMPLE_BUILDOUT = """\
+[buildout]
+
+parts = eggs
+
+[eggs]
+recipe = zc.recipe.egg
+
+eggs =
+    extensions
+"""
+
+BOOTSTRAP = 'http://downloads.buildout.org/1/bootstrap.py'
+PYVER = sys.version.split()[0][:3]
+
+_VARS = {'base': '.',
+         'py_version_short': PYVER}
+
+scheme = 'nt' if sys.platform == 'win32' else 'unix_prefix'
+PURELIB = INSTALL_SCHEMES[scheme]['purelib']
+
+
+@tempdir
+def test_virtualenv():
+    """virtualenv with setuptools"""
+    purelib = os.path.abspath(Template(PURELIB).substitute(**_VARS))
+    _system_call('virtualenv', '--no-site-packages', '.')
+    _system_call('bin/easy_install', 'setuptools==dev')
+    # linux specific
+    site_pkg = os.listdir(purelib)
+    site_pkg.sort()
+    assert 'setuptools' in site_pkg[0]
+    easy_install = os.path.join(purelib, 'easy-install.pth')
+    with open(easy_install) as f:
+        res = f.read()
+    assert 'setuptools' in res
+
+
+@tempdir
+def test_full():
+    """virtualenv + pip + buildout"""
+    _system_call('virtualenv', '--no-site-packages', '.')
+    _system_call('bin/easy_install', '-q', 'setuptools==dev')
+    _system_call('bin/easy_install', '-qU', 'setuptools==dev')
+    _system_call('bin/easy_install', '-q', 'pip')
+    _system_call('bin/pip', 'install', '-q', 'zc.buildout')
+
+    with open('buildout.cfg', 'w') as f:
+        f.write(SIMPLE_BUILDOUT)
+
+    with open('bootstrap.py', 'w') as f:
+        f.write(urllib.request.urlopen(BOOTSTRAP).read())
+
+    _system_call('bin/python', 'bootstrap.py')
+    _system_call('bin/buildout', '-q')
+    eggs = os.listdir('eggs')
+    eggs.sort()
+    assert len(eggs) == 3
+    assert eggs[1].startswith('setuptools')
+    del eggs[1]
+    assert eggs == [
+        'extensions-0.3-py2.6.egg',
+        'zc.recipe.egg-1.2.2-py2.6.egg',
+    ]
+
+
+if __name__ == '__main__':
+    test_virtualenv()
+    test_full()