diff --git a/CHANGES.rst b/CHANGES.rst
index c68615b..1f50f51 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,7 +5,10 @@ Changelog
 0.41 (unreleased)
 -----------------
 
-- Nothing changed yet.
+- Support `PEP 517`_, i.e. packages using pyproject.toml instead of a setup.py
+  (`#105 <https://github.com/mgedmin/check-manifest/issue/105>`_).
+
+.. _PEP 517: https://www.python.org/dev/peps/pep-0517/
 
 
 0.40 (2019-10-15)
diff --git a/check_manifest.py b/check_manifest.py
index 0038476..46203a7 100755
--- a/check_manifest.py
+++ b/check_manifest.py
@@ -827,9 +827,12 @@ def is_package(source_tree='.'):
     """Is the directory the root of a Python package?
 
     Note: the term "package" here refers to a collection of files
-    with a setup.py, not to a directory with an __init__.py.
+    with a setup.py/pyproject.toml, not to a directory with an __init__.py.
     """
-    return os.path.exists(os.path.join(source_tree, 'setup.py'))
+    return (
+        os.path.exists(os.path.join(source_tree, 'setup.py'))
+        or os.path.exists(os.path.join(source_tree, 'pyproject.toml'))
+    )
 
 
 def extract_version_from_filename(filename):
@@ -840,6 +843,23 @@ def extract_version_from_filename(filename):
     return filename.partition('-')[2]
 
 
+def build_sdist(tempdir, python=sys.executable):
+    """Build a source distribution in a temporary directory.
+
+    Should be run with the current working directory inside the Python package
+    you want to build.
+    """
+    if os.path.exists('pyproject.toml'):
+        # I could do this in-process with
+        #   import pep517.envbuild
+        #   pep517.envbuild.build_sdist('.', tempdir)
+        # but then it would print a bunch of things to stdout and I'd have to
+        # worry about exceptions
+        run([python, '-m', 'pep517.build', '--source', '-o', tempdir, '.'])
+    else:
+        run([python, 'setup.py', 'sdist', '-d', tempdir])
+
+
 def check_manifest(source_tree='.', create=False, update=False,
                    python=sys.executable):
     """Compare a generated source distribution with list of files in a VCS.
@@ -851,7 +871,8 @@ def check_manifest(source_tree='.', create=False, update=False,
         python = os.path.abspath(python)
     with cd(source_tree):
         if not is_package():
-            raise Failure('This is not a Python project (no setup.py).')
+            raise Failure(
+                'This is not a Python project (no setup.py/pyproject.toml).')
         read_config()
         read_manifest()
         info_begin("listing source files under version control")
@@ -862,7 +883,7 @@ def check_manifest(source_tree='.', create=False, update=False,
             raise Failure('There are no files added to version control!')
         info_begin("building an sdist")
         with mkdtemp('-sdist') as tempdir:
-            run([python, 'setup.py', 'sdist', '-d', tempdir])
+            build_sdist(tempdir, python=python)
             sdist_filename = get_one_file_in(tempdir)
             info_continue(": %s" % os.path.basename(sdist_filename))
             sdist_files = sorted(normalize_names(strip_sdist_extras(
@@ -877,24 +898,19 @@ def check_manifest(source_tree='.', create=False, update=False,
         info_begin("copying source files to a temporary directory")
         with mkdtemp('-sources') as tempsourcedir:
             copy_files(existing_source_files, tempsourcedir)
-            if os.path.exists('MANIFEST.in') and 'MANIFEST.in' not in source_files:
-                # See https://github.com/mgedmin/check-manifest/issues/7
-                # if do this, we will emit a warning about MANIFEST.in not
-                # being in source control, if we don't do this, the user
-                # gets confused about their new manifest rules being
-                # ignored.
-                copy_files(['MANIFEST.in'], tempsourcedir)
-            if 'setup.py' not in source_files:
-                # See https://github.com/mgedmin/check-manifest/issues/46
-                # if do this, we will emit a warning about setup.py not
-                # being in source control, if we don't do this, the user
-                # gets a scary error
-                copy_files(['setup.py'], tempsourcedir)
+            for filename in 'MANIFEST.in', 'setup.py', 'pyproject.toml':
+                if filename not in source_files and os.path.exists(filename):
+                    # See https://github.com/mgedmin/check-manifest/issues/7
+                    # and https://github.com/mgedmin/check-manifest/issues/46:
+                    # if we do this, the user gets a warning about files
+                    # missing from source control; if we don't do this,
+                    # things get very confusing for the user!
+                    copy_files([filename], tempsourcedir)
             info_begin("building a clean sdist")
             with cd(tempsourcedir):
                 with mkdtemp('-sdist') as tempdir:
                     os.environ['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
-                    run([python, 'setup.py', 'sdist', '-d', tempdir])
+                    build_sdist(tempdir, python=python)
                     sdist_filename = get_one_file_in(tempdir)
                     info_continue(": %s" % os.path.basename(sdist_filename))
                     clean_sdist_files = sorted(normalize_names(strip_sdist_extras(
diff --git a/setup.py b/setup.py
index ac1af10..cfefae9 100755
--- a/setup.py
+++ b/setup.py
@@ -58,7 +58,7 @@ setup(
     zip_safe=False,
     test_suite='tests.test_suite',
     python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
-    install_requires=['toml'],
+    install_requires=['toml', 'pep517'],
     extras_require={
         'test': ['mock'],
     },

Does not work with setup.py-less packaging?
I use setuptools with declarative config (i.e. defined in [`setup.cfg`](https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files)) and build system in `pyproject.toml`:

```
[build-system]
requires = [
    "setuptools >= 40.6.0",
    "wheel",
]
build-backend = "setuptools.build_meta"
```

This works fine to build wheels and sdist using the PEP 517 hooks ([here](https://github.com/pypa/setuptools/blob/v41.5.0/setuptools/build_meta.py#L252-L253)).

But check-manifest doesn't recognize this as valid Python project.

```
$ check-manifest
This is not a Python project (no setup.py).
```
diff --git a/tests.py b/tests.py
index 8832ce8..b760687 100644
--- a/tests.py
+++ b/tests.py
@@ -452,9 +452,11 @@ class Tests(unittest.TestCase):
     def test_is_package(self):
         from check_manifest import is_package
         j = os.path.join
-        with mock.patch('os.path.exists', lambda fn: fn == j('a', 'setup.py')):
+        exists = {j('a', 'setup.py'), j('c', 'pyproject.toml')}
+        with mock.patch('os.path.exists', lambda fn: fn in exists):
             self.assertTrue(is_package('a'))
             self.assertFalse(is_package('b'))
+            self.assertTrue(is_package('c'))
 
     def test_extract_version_from_filename(self):
         from check_manifest import extract_version_from_filename as e
@@ -593,6 +595,24 @@ class Tests(unittest.TestCase):
             "%s, line 2: continuation line immediately precedes end-of-file" % filename,
         ])
 
+    def test_build_sdist(self):
+        from check_manifest import build_sdist, cd, get_one_file_in
+        src_dir = self.make_temp_dir()
+        filename = os.path.join(src_dir, 'pyproject.toml')
+        self.create_file(filename, textwrap.dedent('''
+            [build-system]
+            requires = [
+                "setuptools >= 40.6.0",
+                "wheel",
+            ]
+            build-backend = "setuptools.build_meta"
+        '''))
+        out_dir = self.make_temp_dir()
+        python = os.path.abspath(sys.executable)
+        with cd(src_dir):
+            build_sdist(out_dir, python=python)
+        self.assertTrue(get_one_file_in(out_dir))
+
 
 class TestConfiguration(unittest.TestCase):
 
@@ -1386,8 +1406,9 @@ class TestCheckManifest(unittest.TestCase):
         from check_manifest import check_manifest, Failure
         with self.assertRaises(Failure) as cm:
             check_manifest()
-        self.assertEqual(str(cm.exception),
-                         "This is not a Python project (no setup.py).")
+        self.assertEqual(
+            str(cm.exception),
+            "This is not a Python project (no setup.py/pyproject.toml).")
 
     def test_forgot_to_git_add_anything(self):
         from check_manifest import check_manifest, Failure

pip install -e .[test]
parse_log_pytest
toml
nose coverage mock pytest
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi @ file:///croot/certifi_1671487769961/work/certifi
-e git+https://github.com/mgedmin/check-manifest.git@4695989f6646f4bd241324b4d838b2aee7e42f2d#egg=check_manifest
coverage==7.2.7
exceptiongroup==1.2.2
importlib-metadata==6.7.0
iniconfig==2.0.0
mock==5.2.0
nose==1.3.7
packaging==24.0
pluggy==1.2.0
pytest==7.4.4
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
tomli==2.0.1
typing_extensions==4.7.1
zipp==3.15.0

name: check-manifest
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.2.7
      - exceptiongroup==1.2.2
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - mock==5.2.0
      - nose==1.3.7
      - packaging==24.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - zipp==3.15.0
prefix: /opt/conda/envs/check-manifest

tests.py::Tests::test_is_package tests.py::TestCheckManifest::test_not_python_project
tests.py::Tests::test_build_sdist tests.py::Tests::test_rmtree_unreadable_directories tests.py::TestGit::test_get_versioned_files_with_git_submodules
tests.py::Tests::test_add_directories tests.py::Tests::test_copy_files tests.py::Tests::test_detect_vcs_no_vcs tests.py::Tests::test_extract_version_from_filename tests.py::Tests::test_file_matches tests.py::Tests::test_find_bad_ideas tests.py::Tests::test_find_suggestions tests.py::Tests::test_find_suggestions_generic_fallback_rules tests.py::Tests::test_find_suggestions_ignores_directories tests.py::Tests::test_format_list tests.py::Tests::test_format_missing tests.py::Tests::test_get_archive_file_list_tar tests.py::Tests::test_get_archive_file_list_tar_nonascii tests.py::Tests::test_get_archive_file_list_unrecognized_archive tests.py::Tests::test_get_archive_file_list_zip tests.py::Tests::test_get_archive_file_list_zip_nonascii tests.py::Tests::test_get_ignore_from_manifest tests.py::Tests::test_get_ignore_from_manifest_lines tests.py::Tests::test_get_ignore_from_manifest_warnings tests.py::Tests::test_get_one_file_in tests.py::Tests::test_get_one_file_in_empty_directory tests.py::Tests::test_get_one_file_in_too_many tests.py::Tests::test_glob_to_regexp tests.py::Tests::test_mkdtemp_readonly_files tests.py::Tests::test_normalize_names tests.py::Tests::test_rmtree_readonly_directories tests.py::Tests::test_rmtree_readonly_directories_and_files tests.py::Tests::test_run_failure tests.py::Tests::test_run_no_such_program tests.py::Tests::test_run_success tests.py::Tests::test_strip_sdist_extras tests.py::Tests::test_strip_sdist_extras_with_manifest tests.py::Tests::test_strip_toplevel_name_empty_list tests.py::Tests::test_strip_toplevel_name_no_common_prefix tests.py::Tests::test_unicodify tests.py::TestConfiguration::test_read_config_no_config tests.py::TestConfiguration::test_read_manifest tests.py::TestConfiguration::test_read_manifest_no_manifest tests.py::TestConfiguration::test_read_pyproject_config_extra_ignores tests.py::TestConfiguration::test_read_pyproject_config_ignore_bad_ideas tests.py::TestConfiguration::test_read_pyproject_config_no_option tests.py::TestConfiguration::test_read_pyproject_config_no_section tests.py::TestConfiguration::test_read_pyproject_config_override_ignores tests.py::TestConfiguration::test_read_setup_config_extra_ignores tests.py::TestConfiguration::test_read_setup_config_ignore_bad_ideas tests.py::TestConfiguration::test_read_setup_config_no_option tests.py::TestConfiguration::test_read_setup_config_no_section tests.py::TestConfiguration::test_read_setup_config_override_ignores tests.py::TestMain::test tests.py::TestMain::test_exit_code_1_on_error tests.py::TestMain::test_exit_code_2_on_failure tests.py::TestMain::test_extra_ignore_args tests.py::TestMain::test_ignore_bad_ideas_args tests.py::TestZestIntegration::test_zest_releaser_check_all_okay tests.py::TestZestIntegration::test_zest_releaser_check_error_user_aborts tests.py::TestZestIntegration::test_zest_releaser_check_error_user_plods_on tests.py::TestZestIntegration::test_zest_releaser_check_failure_user_aborts tests.py::TestZestIntegration::test_zest_releaser_check_failure_user_plods_on tests.py::TestZestIntegration::test_zest_releaser_check_not_a_package tests.py::TestZestIntegration::test_zest_releaser_check_user_disagrees tests.py::TestGit::test_detect_git_submodule tests.py::TestGit::test_get_vcs_files tests.py::TestGit::test_get_vcs_files_added_but_uncommitted tests.py::TestGit::test_get_vcs_files_deleted_but_not_removed tests.py::TestGit::test_get_vcs_files_empty tests.py::TestGit::test_get_vcs_files_in_a_subdir tests.py::TestGit::test_get_vcs_files_nonascii_filenames tests.py::TestBzrTerminalCharsetDetectionOnOldPythons::test_terminal_encoding_cp0 tests.py::TestBzrTerminalCharsetDetectionOnOldPythons::test_terminal_encoding_not_known tests.py::TestBzrTerminalCharsetDetectionOnOldPythons::test_terminal_encoding_stdin_known tests.py::TestBzrTerminalCharsetDetectionOnOldPythons::test_terminal_encoding_stdout_known tests.py::TestHg::test_get_vcs_files tests.py::TestHg::test_get_vcs_files_added_but_uncommitted tests.py::TestHg::test_get_vcs_files_deleted_but_not_removed tests.py::TestHg::test_get_vcs_files_empty tests.py::TestHg::test_get_vcs_files_in_a_subdir tests.py::TestHg::test_get_vcs_files_nonascii_filenames tests.py::TestSvn::test_get_vcs_files tests.py::TestSvn::test_get_vcs_files_added_but_uncommitted tests.py::TestSvn::test_get_vcs_files_deleted_but_not_removed tests.py::TestSvn::test_get_vcs_files_empty tests.py::TestSvn::test_get_vcs_files_in_a_subdir tests.py::TestSvn::test_get_vcs_files_nonascii_filenames tests.py::TestSvn::test_svn_externals tests.py::TestSvnExtraErrors::test_svn_xml_parsing_warning tests.py::TestUserInterface::test_error tests.py::TestUserInterface::test_error_verbose tests.py::TestUserInterface::test_info tests.py::TestUserInterface::test_info_begin_continue_end tests.py::TestUserInterface::test_info_begin_continue_end_verbose tests.py::TestUserInterface::test_info_emits_newline_when_needed tests.py::TestUserInterface::test_info_emits_newline_when_needed_verbose tests.py::TestUserInterface::test_info_verbose tests.py::TestUserInterface::test_warning tests.py::TestUserInterface::test_warning_verbose tests.py::TestCheckManifest::test_MANIFEST_in_does_not_need_to_be_added_to_be_considered tests.py::TestCheckManifest::test_all_is_well tests.py::TestCheckManifest::test_bad_ideas tests.py::TestCheckManifest::test_forgot_to_git_add_anything tests.py::TestCheckManifest::test_ignore_bad_ideas tests.py::TestCheckManifest::test_missing_source_files tests.py::TestCheckManifest::test_python_from_path tests.py::TestCheckManifest::test_relative_pathname tests.py::TestCheckManifest::test_relative_python tests.py::TestCheckManifest::test_setup_py_does_not_need_to_be_added_to_be_considered tests.py::TestCheckManifest::test_suggestions tests.py::TestCheckManifest::test_suggestions_all_unknown_patterns tests.py::TestCheckManifest::test_suggestions_create tests.py::TestCheckManifest::test_suggestions_some_unknown_patterns tests.py::TestCheckManifest::test_suggestions_update tests.py::test_suite
setup.py check_manifest.py CHANGES.rst
setup.py check_manifest.py CHANGES.rst