diff --git a/.gitignore b/.gitignore
index 379b2ff8586404f0236c18be564ecdbd915d7f2c..ccf1add084b2ec33cc5291d3da9be6976fee2e39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
 *~
-MANIFEST
 *.pyc
 *.pyo
 *.so
@@ -15,3 +14,5 @@ MANIFEST
 /doc/source/images/.*_flag
 /doc/source/images/[a-zA-Z]*.py
 /build.conf
+/kwant.egg-info/
+/MANIFEST.in
diff --git a/setup.py b/setup.py
index 631b28ed82e734927b2c5754c20b33c3c1e006d4..0f019f81d2a1bd9d192ae6fd347507dee65348b7 100755
--- a/setup.py
+++ b/setup.py
@@ -17,17 +17,19 @@ import glob
 import imp
 import subprocess
 import ConfigParser
-from distutils.core import setup, Extension, Command
-from distutils.util import get_platform
+from setuptools import setup, Extension, Command
+from sysconfig import get_platform
 from distutils.errors import DistutilsError, DistutilsModuleError, \
     CCompilerError
 from distutils.command.build import build
-from distutils.command.sdist import sdist
+from setuptools.command.sdist import sdist
+from setuptools.command.build_ext import build_ext
 
 import numpy
 
 CONFIG_FILE = 'build.conf'
 README_FILE = 'README.rst'
+MANIFEST_IN_FILE = 'MANIFEST.in'
 README_END_BEFORE = 'See also in this directory:'
 STATIC_VERSION_PATH = ('kwant', '_kwant_version.py')
 REQUIRED_CYTHON_VERSION = (0, 22)
@@ -72,11 +74,6 @@ if cythonize:
             cython_version[-1] -= 1
         cython_version = tuple(cython_version)
 
-if cythonize and cython_version:
-    from Cython.Distutils import build_ext
-else:
-    from distutils.command.build_ext import build_ext
-
 distr_root = os.path.dirname(os.path.abspath(__file__))
 
 def banner(title=''):
@@ -95,6 +92,7 @@ Build configuration was:
 """
 error_msg = error_msg.format(header=banner(' Error '), sep=banner())
 
+
 class kwant_build_ext(build_ext):
     def run(self):
         if not config_file_present:
@@ -150,30 +148,6 @@ class kwant_build(build):
         write_version(os.path.join(self.build_lib, *STATIC_VERSION_PATH))
 
 
-class kwant_test(Command):
-    description = "build, then run the unit tests"
-    user_options = []
-
-    def initialize_options(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        try:
-            from nose.core import run
-        except ImportError:
-            raise DistutilsModuleError('nose <http://nose.readthedocs.org/> '
-                                       'is needed to run the tests')
-        self.run_command('build')
-        major, minor = sys.version_info[:2]
-        lib_dir = "build/lib.{0}-{1}.{2}".format(get_platform(), major, minor)
-        print()
-        if not run(argv=[__file__, '-v', lib_dir]):
-            raise DistutilsError('at least one of the tests failed')
-
-
 def git_lsfiles():
     if not version_is_from_git:
         return
@@ -197,45 +171,42 @@ class kwant_sdist(sdist):
     sub_commands = [('build', None)] + sdist.sub_commands
 
     def run(self):
+        """
+        Create MANIFEST.in from git if possible, otherwise check that MANIFEST.in
+        is present.
+
+        Right now (2015) generating MANIFEST.in seems to be the only way to
+        include files in the source distribution that setuptools does not think
+        should be there.  Setting include_package_data to True makes setuptools
+        include *.pyx and other source files in the binary distribution.
+        """
+        manifest = os.path.join(distr_root, MANIFEST_IN_FILE)
         names = git_lsfiles()
-        trustworthy = True
         if names is None:
-            # Check that MANIFEST exists and has not been generated by
-            # distutils.
-            try:
-                with open(distr_root + '/MANIFEST', 'r') as f:
-                    line = f.read()
-            except IOError:
-                print("Error: MANIFEST file is missing and Git is not"
-                      " available to regenerate it.", file=sys.stderr)
+            if not (os.path.isfile(manifest) and os.access(manifest, os.R_OK)):
+                print("Error:", MANIFEST_IN_FILE,
+                      "file is missing and Git is not available"
+                      " to regenerate it.", file=sys.stderr)
                 exit(1)
-            trustworthy = not line.strip().startswith('#')
         else:
-            # Generate MANIFEST file.
-            with open(distr_root + '/MANIFEST', 'w') as f:
+            with open(manifest, 'w') as f:
                 for name in names:
                     a, sep, b = name.rpartition('/')
                     if b == '.gitignore':
                         continue
                     stem, dot, extension = b.rpartition('.')
+                    f.write('include {}'.format(name))
                     if extension == 'pyx':
-                        f.write(''.join([a, sep, stem, dot, 'c', '\n']))
-                    f.write(name + '\n')
-                f.write('MANIFEST\n')
+                        f.write(''.join([' ', a, sep, stem, dot, 'c']))
+                    f.write('\n')
 
         sdist.run(self)
 
         if names is None:
             print(banner(' Warning '),
-"""Git was not available for re-generating the MANIFEST file (the list of file
-names to be included in the source distribution).  The old MANIFEST was used.""",
-                  banner(),
-                  sep='\n', file=sys.stderr)
 
-        if not trustworthy:
-            print(banner(' Warning '),
-"""The existing MANIFEST file seems to have been generated by distutils (it begins
-with a comment).  It may well be incomplete.""",
+        """Git was not available to generate the list of files to be included in the
+source distribution.  The old MANIFEST.in was used.""",
                   banner(),
                   sep='\n', file=sys.stderr)
 
@@ -301,9 +272,7 @@ def search_mumps():
 
 
 def extensions():
-    """Return a list of tuples (args, kwrds) to be passed to
-    Extension. possibly after replacing ".pyx" with ".c" if Cython is not to be
-    used."""
+    """Return a list of tuples (args, kwrds) to be passed to Extension."""
 
     global build_summary, config_file_present
     build_summary = []
@@ -476,13 +445,16 @@ def main():
           url="http://kwant-project.org/",
           license="BSD",
           packages=packages(),
+          test_suite = 'nose.collector',
           cmdclass={'build': kwant_build,
                     'sdist': kwant_sdist,
                     'build_ext': kwant_build_ext,
-                    'build_tut': kwant_build_tut,
-                    'test': kwant_test},
+                    'build_tut': kwant_build_tut},
           ext_modules=ext_modules(extensions()),
-          include_dirs=[numpy.get_include()])
+          include_dirs=[numpy.get_include()],
+          setup_requires=['numpy > 1.6.1', 'nose >= 1.0'],
+          install_requires=['numpy > 1.6.1', 'scipy >= 0.9', 'tinyarray'],
+          extras_require={'plotting': 'matplotlib >= 1.2'})
 
 if __name__ == '__main__':
     main()