Repository: pypa/setuptools (python)

Issue:
build_meta does not include setup.py
When someone invokes the build operation via the ``build_meta`` (via https://github.com/pypa/setuptools/blob/master/setuptools/build_meta.py#L170 - https://www.python.org/dev/peps/pep-0517/#build-sdist) the ``setup.py`` is no longer automatically packaged.

In case of ``python setup.py sdist`` what ensured that this will happen is https://github.com/python/cpython/blob/master/Lib/distutils/command/sdist.py#L250. Here ``distribution.script_name`` is set to ``setup.py`` when called via ``python setup.py sdist``. This is not set though when calling it directly via ``build_meta`` so packaging ``setup.py`` is safely ignored. 

Note the files to package is determined while running ``egg_info``, and this is re-used across runs (by reading ``SOURCES.txt``). Therefore this bug is hidden when:
- anyone beforehand calls ``python setup.py sdist``,
- when there's a distutils extension that automatically pulls in setup.py (such as setuptools-scm), 
- when some config value pulls in (one in ``MANIFEST.in``, ``setup.cfg``, ``setup.py``).

Pull request description:
Egg include setup py
## Summary of changes

While creating sdists, we want the source dist .tar.gz to contain `setup.py` because this is what pip would use to install the source distribution. `python setup.py sdist` ensured setup.py was included because distutils by default adds the [script_name](https://github.com/python/cpython/blob/master/Lib/distutils/command/sdist.py#L250) that was executing the build process. However, for someone calling `build_sdist('.')` programatically, the script name is the script callind build_sdist function and not `setup.py`.

The problem is that sdist command uses the `egg-info` command to extract file list to be tar-ziped, which did not include setup.py as default (unless it was called via 'setup.py' as the script name). This fix adds an explicit check in the `egg-info` command for setup.py.

An alternative solution is solving this one level deeper at distutils [add_defaults](https://github.com/python/cpython/blob/master/Lib/distutils/command/sdist.py#L210) method which `egg-info` command [uses](https://github.com/pypa/setuptools/blob/master/setuptools/command/egg_info.py#L570).

Closes #1506 

Will update changelog once we agree on the approach

### 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


Patch:
diff --git a/changelog.d/1554.change.rst b/changelog.d/1554.change.rst
new file mode 100644
index 000000000..d55c042ce
--- /dev/null
+++ b/changelog.d/1554.change.rst
@@ -0,0 +1,1 @@
+``build_meta.build_sdist`` now includes ``setup.py`` in source distributions by default
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index e1022d31f..d9fe3da31 100644
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -575,6 +575,12 @@ class manifest_maker(sdist):
             self.filelist.extend(rcfiles)
         elif os.path.exists(self.manifest):
             self.read_manifest()
+
+        if os.path.exists("setup.py"):
+            # setup.py should be included by default, even if it's not
+            # the script called to create the sdist
+            self.filelist.append("setup.py")
+
         ei_cmd = self.get_finalized_command('egg_info')
         self.filelist.graft(ei_cmd.egg_info)
 


Test patch:
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
index c5f4dcaa9..940780cf5 100644
--- a/setuptools/tests/test_build_meta.py
+++ b/setuptools/tests/test_build_meta.py
@@ -2,9 +2,11 @@ from __future__ import unicode_literals
 
 import os
 import shutil
+import tarfile
 
 import pytest
 
+from setuptools.build_meta import build_sdist
 from .files import build_files
 from .textwrap import DALS
 from . import py2_only
@@ -181,3 +183,34 @@ def test_build_sdist_version_change(build_backend):
 
     sdist_name = build_backend.build_sdist("out_sdist")
     assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name))
+
+
+def test_build_sdist_setup_py_exists(tmpdir_cwd):
+    # If build_sdist is called from a script other than setup.py,
+    # ensure setup.py is include
+    build_files(defns[0])
+    targz_path = build_sdist("temp")
+    with tarfile.open(os.path.join("temp", targz_path)) as tar:
+        assert any('setup.py' in name for name in tar.getnames())
+
+
+def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
+    # Ensure that MANIFEST.in can exclude setup.py
+    files = {
+        'setup.py': DALS("""
+    __import__('setuptools').setup(
+        name='foo',
+        version='0.0.0',
+        py_modules=['hello']
+    )"""),
+        'hello.py': '',
+        'MANIFEST.in': DALS("""
+    exclude setup.py
+    """)
+    }
+
+    build_files(files)
+    targz_path = build_sdist("temp")
+    with tarfile.open(os.path.join("temp", targz_path)) as tar:
+        assert not any('setup.py' in name for name in tar.getnames())
+
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 46fb884f2..f97b3f1d8 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -569,6 +569,20 @@ class TestEggInfo:
         for msg in fixtures:
             assert manifest_maker._should_suppress_warning(msg)
 
+    def test_egg_info_includes_setup_py(self, tmpdir_cwd):
+        self._create_project()
+        dist = Distribution({"name": "foo", "version": "0.0.1"})
+        dist.script_name = "non_setup.py"
+        egg_info_instance = egg_info(dist)
+        egg_info_instance.finalize_options()
+        egg_info_instance.run()
+
+        assert 'setup.py' in egg_info_instance.filelist.files
+
+        with open(egg_info_instance.egg_info + "/SOURCES.txt") as f:
+            sources = f.read().split('\n')
+            assert 'setup.py' in sources
+
     def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
         environ = os.environ.copy().update(
             HOME=env.paths['home'],
@@ -605,4 +619,4 @@ class TestEggInfo:
         assert 'Version: 0.0.0.dev0' in pkg_info_lines
 
     def test_get_pkg_info_revision_deprecated(self):
-        pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
\ No newline at end of file
+        pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index 3a203890c..d2c4e0cf9 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -92,9 +92,8 @@ fail_on_latin1_encoded_filenames = pytest.mark.xfail(
 class TestSdistTest:
     def setup_method(self, method):
         self.temp_dir = tempfile.mkdtemp()
-        f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
-        f.write(SETUP_PY)
-        f.close()
+        with open(os.path.join(self.temp_dir, 'setup.py'), 'w') as f:
+            f.write(SETUP_PY)
 
         # Set up the rest of the test package
         test_pkg = os.path.join(self.temp_dir, 'sdist_test')
@@ -135,6 +134,47 @@ class TestSdistTest:
         assert os.path.join('sdist_test', 'c.rst') not in manifest
         assert os.path.join('d', 'e.dat') in manifest
 
+    def test_setup_py_exists(self):
+        dist = Distribution(SETUP_ATTRS)
+        dist.script_name = 'foo.py'
+        cmd = sdist(dist)
+        cmd.ensure_finalized()
+
+        with quiet():
+            cmd.run()
+
+        manifest = cmd.filelist.files
+        assert 'setup.py' in manifest
+
+    def test_setup_py_missing(self):
+        dist = Distribution(SETUP_ATTRS)
+        dist.script_name = 'foo.py'
+        cmd = sdist(dist)
+        cmd.ensure_finalized()
+
+        if os.path.exists("setup.py"):
+            os.remove("setup.py")
+        with quiet():
+            cmd.run()
+
+        manifest = cmd.filelist.files
+        assert 'setup.py' not in manifest
+
+    def test_setup_py_excluded(self):
+        with open("MANIFEST.in", "w") as manifest_file:
+            manifest_file.write("exclude setup.py")
+
+        dist = Distribution(SETUP_ATTRS)
+        dist.script_name = 'foo.py'
+        cmd = sdist(dist)
+        cmd.ensure_finalized()
+
+        with quiet():
+            cmd.run()
+
+        manifest = cmd.filelist.files
+        assert 'setup.py' not in manifest
+
     def test_defaults_case_sensitivity(self):
         """
         Make sure default files (README.*, etc.) are added in a case-sensitive