Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-flit-core for 
openSUSE:Factory checked in at 2021-04-18 21:44:20
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-flit-core (Old)
 and      /work/SRC/openSUSE:Factory/.python-flit-core.new.12324 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-flit-core"

Sun Apr 18 21:44:20 2021 rev:6 rq:885474 version:3.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-flit-core/python-flit-core.changes        
2020-10-27 19:01:21.978848945 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-flit-core.new.12324/python-flit-core.changes 
    2021-04-18 21:44:28.096656680 +0200
@@ -1,0 +2,12 @@
+Wed Apr 14 19:29:23 UTC 2021 - Matthias Bach <[email protected]>
+
+- Update to version 3.2
+  * Experimental support for specifying metadata in a [project] in
+    pyproject.toml table as specified by PEP-621.
+  * Fix writing METADATA file with multi-line information in
+    certain fields such as Author.
+  * Fix building wheel when a directory such as LICENSES appears
+    in the project root directory.
+  * Switch from the deprecated pytoml package to toml.
+
+-------------------------------------------------------------------

Old:
----
  flit_core-3.0.0.tar.gz

New:
----
  flit_core-3.2.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-flit-core.spec ++++++
--- /var/tmp/diff_new_pack.FgdRZk/_old  2021-04-18 21:44:28.484657336 +0200
+++ /var/tmp/diff_new_pack.FgdRZk/_new  2021-04-18 21:44:28.488657342 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-flit-core
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,10 +16,9 @@
 #
 
 
-%{?!python_module:%define python_module() python3-%{**}}
 %define skip_python2 1
 Name:           python-flit-core
-Version:        3.0.0
+Version:        3.2.0
 Release:        0
 Summary:        Distribution-building parts of Flit
 License:        BSD-3-Clause
@@ -27,11 +26,11 @@
 Source:         
https://files.pythonhosted.org/packages/source/f/flit-core/flit_core-%{version}.tar.gz
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest}
-BuildRequires:  %{python_module pytoml}
 BuildRequires:  %{python_module testpath}
+BuildRequires:  %{python_module toml}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-pytoml
+Requires:       python-toml
 BuildArch:      noarch
 %python_subpackages
 

++++++ flit_core-3.0.0.tar.gz -> flit_core-3.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/PKG-INFO new/flit_core-3.2.0/PKG-INFO
--- old/flit_core-3.0.0/PKG-INFO        1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/PKG-INFO        1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: flit_core
-Version: 3.0.0
+Version: 3.2.0
 Summary: Distribution-building parts of Flit. See flit package for more 
information
 Home-page: https://github.com/takluyver/flit
 Author: Thomas Kluyver & contributors
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/__init__.py 
new/flit_core-3.2.0/flit_core/__init__.py
--- old/flit_core-3.0.0/flit_core/__init__.py   2020-09-06 12:29:28.066011700 
+0200
+++ new/flit_core-3.2.0/flit_core/__init__.py   2021-03-15 23:45:58.347866000 
+0100
@@ -4,4 +4,4 @@
 All the convenient development features live in the main 'flit' package.
 """
 
-__version__ = '3.0.0'
+__version__ = '3.2.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/build_thyself.py 
new/flit_core-3.2.0/flit_core/build_thyself.py
--- old/flit_core-3.0.0/flit_core/build_thyself.py      2020-05-23 
20:40:28.575257500 +0200
+++ new/flit_core-3.2.0/flit_core/build_thyself.py      2021-01-23 
13:24:58.122672800 +0100
@@ -25,7 +25,7 @@
     'summary': ('Distribution-building parts of Flit. '
                     'See flit package for more information'),
     'requires_dist': [
-        'pytoml',
+        'toml',
     ],
     'requires_python': '>=3.4',
     'classifiers': [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/buildapi.py 
new/flit_core-3.2.0/flit_core/buildapi.py
--- old/flit_core-3.0.0/flit_core/buildapi.py   2020-08-25 19:07:58.613150000 
+0200
+++ new/flit_core-3.2.0/flit_core/buildapi.py   2021-03-15 23:45:58.348866200 
+0100
@@ -21,12 +21,17 @@
 def get_requires_for_build_wheel(config_settings=None):
     """Returns a list of requirements for building, as strings"""
     info = read_flit_config(pyproj_toml)
-    # If we can get the module info from the AST, we don't need any extra
+    # If we can get version & description from pyproject.toml (PEP 621), or
+    # by parsing the module (_via_ast), we don't need any extra
     # dependencies. If not, we'll need to try importing it, so report any
     # runtime dependencies as build dependencies.
+    want_summary = 'description' in info.dynamic_metadata
+    want_version = 'version' in info.dynamic_metadata
+
     module = Module(info.module, Path.cwd())
     docstring, version = get_docstring_and_version_via_ast(module)
-    if (docstring is None) or (version is None):
+
+    if (want_summary and not docstring) or (want_version and not version):
         return info.metadata.get('requires_dist', [])
     else:
         return []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/common.py 
new/flit_core-3.2.0/flit_core/common.py
--- old/flit_core-3.0.0/flit_core/common.py     2020-07-14 22:41:21.759879400 
+0200
+++ new/flit_core-3.2.0/flit_core/common.py     2021-03-21 13:02:58.410992100 
+0100
@@ -143,15 +143,24 @@
     return ast.get_docstring(node), version
 
 
+# To ensure we're actually loading the specified file, give it a unique name to
+# avoid any cached import. In normal use we'll only load one module per 
process,
+# so it should only matter for the tests, but we'll do it anyway.
+_import_i = 0
+
+
 def get_docstring_and_version_via_import(target):
     """
     Return a tuple like (docstring, version) for the given module,
     extracted by importing the module and pulling __doc__ & __version__
     from it.
     """
+    global _import_i
+    _import_i += 1
+
     log.debug("Loading module %s", target.file)
     from importlib.machinery import SourceFileLoader
-    sl = SourceFileLoader(target.name, str(target.file))
+    sl = SourceFileLoader('flit_core.dummy.import%d' % _import_i, 
str(target.file))
     with _module_load_ctx():
         m = sl.load_module()
     docstring = m.__dict__.get('__doc__', None)
@@ -159,9 +168,16 @@
     return docstring, version
 
 
-def get_info_from_module(target):
+def get_info_from_module(target, for_fields=('version', 'description')):
     """Load the module/package, get its docstring and __version__
     """
+    if not for_fields:
+        return {}
+
+    # What core metadata calls Summary, PEP 621 calls description
+    want_summary = 'description' in for_fields
+    want_version = 'version' in for_fields
+
     log.debug("Loading module %s", target.file)
 
     # Attempt to extract our docstring & version by parsing our target's
@@ -169,19 +185,23 @@
     # build without necessarily requiring that our built package's
     # requirements are installed.
     docstring, version = get_docstring_and_version_via_ast(target)
-    if not (docstring and version):
+    if (want_summary and not docstring) or (want_version and not version):
         docstring, version = get_docstring_and_version_via_import(target)
 
-    if (not docstring) or not docstring.strip():
-        raise NoDocstringError('Flit cannot package module without docstring, '
-                'or empty docstring. Please add a docstring to your module '
-                '({}).'.format(target.file))
-
-    version = check_version(version)
-
-    docstring_lines = docstring.lstrip().splitlines()
-    return {'summary': docstring_lines[0],
-            'version': version}
+    res = {}
+
+    if want_summary:
+        if (not docstring) or not docstring.strip():
+            raise NoDocstringError(
+                'Flit cannot package module without docstring, or empty 
docstring. '
+                'Please add a docstring to your module 
({}).'.format(target.file)
+            )
+        res['summary'] = docstring.lstrip().splitlines()[0]
+
+    if want_version:
+        res['version'] = check_version(version)
+
+    return res
 
 def check_version(version):
     """
@@ -269,6 +289,7 @@
 
 class Metadata(object):
 
+    summary = None
     home_page = None
     author = None
     author_email = None
@@ -297,9 +318,9 @@
     metadata_version = "2.1"
 
     def __init__(self, data):
+        data = data.copy()
         self.name = data.pop('name')
         self.version = data.pop('version')
-        self.summary = data.pop('summary')
 
         for k, v in data.items():
             assert hasattr(self, k), "data does not have attribute 
'{}'".format(k)
@@ -314,11 +335,11 @@
             'Metadata-Version',
             'Name',
             'Version',
+        ]
+        optional_fields = [
             'Summary',
             'Home-page',
             'License',
-        ]
-        optional_fields = [
             'Keywords',
             'Author',
             'Author-email',
@@ -330,11 +351,16 @@
 
         for field in fields:
             value = getattr(self, self._normalise_name(field))
-            fp.write(u"{}: {}\n".format(field, value or 'UNKNOWN'))
+            fp.write(u"{}: {}\n".format(field, value))
 
         for field in optional_fields:
             value = getattr(self, self._normalise_name(field))
             if value is not None:
+                # TODO: verify which fields can be multiline
+                # The spec has multiline examples for Author, Maintainer &
+                # License (& Description, but we put that in the body)
+                # Indent following lines with 8 spaces:
+                value = '\n        '.join(value.splitlines())
                 fp.write(u"{}: {}\n".format(field, value))
 
         for clsfr in self.classifiers:
@@ -363,20 +389,26 @@
 
 def make_metadata(module, ini_info):
     md_dict = {'name': module.name, 'provides': [module.name]}
-    md_dict.update(get_info_from_module(module))
+    md_dict.update(get_info_from_module(module, ini_info.dynamic_metadata))
     md_dict.update(ini_info.metadata)
     return Metadata(md_dict)
 
-def metadata_and_module_from_ini_path(ini_path):
-    from .config import read_flit_config
-    ini_path = str(ini_path)
-    ini_info = read_flit_config(ini_path)
-    module = Module(ini_info.module, osp.dirname(ini_path))
-    metadata = make_metadata(module, ini_info)
-    return metadata,module
+
+
+def normalize_dist_name(name: str, version: str) -> str:
+    """Normalizes a name and a PEP 440 version
+
+    The resulting string is valid as dist-info folder name
+    and as first part of a wheel filename
+
+    See 
https://packaging.python.org/specifications/binary-distribution-format/#escaping-and-unicode
+    """
+    normalized_name = re.sub(r'[-_.]+', '_', name, flags=re.UNICODE)
+    assert check_version(version) == version
+    assert '-' not in version, 'Normalized versions can???t have dashes'
+    return '{}-{}'.format(normalized_name, version)
+
 
 def dist_info_name(distribution, version):
     """Get the correct name of the .dist-info folder"""
-    escaped_name = re.sub(r"[^\w\d.]+", "_", distribution, flags=re.UNICODE)
-    escaped_version = re.sub(r"[^\w\d.]+", "_", version, flags=re.UNICODE)
-    return u'{}-{}.dist-info'.format(escaped_name, escaped_version)
+    return normalize_dist_name(distribution, version) + '.dist-info'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/config.py 
new/flit_core-3.2.0/flit_core/config.py
--- old/flit_core-3.0.0/flit_core/config.py     2020-05-23 20:40:28.576257700 
+0200
+++ new/flit_core-3.2.0/flit_core/config.py     2021-03-21 22:16:35.799428000 
+0100
@@ -1,11 +1,15 @@
 import difflib
+from email.headerregistry import Address
 import errno
 import logging
 import os
 import os.path as osp
-import pytoml as toml
+from pathlib import Path
+import toml
 import re
 
+from .versionno import normalise_version
+
 log = logging.getLogger(__name__)
 
 
@@ -38,6 +42,26 @@
     'author',
 }
 
+pep621_allowed_fields = {
+    'name',
+    'version',
+    'description',
+    'readme',
+    'requires-python',
+    'license',
+    'authors',
+    'maintainers',
+    'keywords',
+    'classifiers',
+    'urls',
+    'scripts',
+    'gui-scripts',
+    'entry-points',
+    'dependencies',
+    'optional-dependencies',
+    'dynamic',
+}
+
 
 def read_flit_config(path):
     """Read and check the `pyproject.toml` file with data about the package.
@@ -57,39 +81,66 @@
     
     Returns a LoadedConfig object.
     """
-    if ('tool' not in d) or ('flit' not in d['tool']) \
-            or (not isinstance(d['tool']['flit'], dict)):
-        raise ConfigError("TOML file missing [tool.flit] table.")
+    dtool = d.get('tool', {}).get('flit', {})
 
-    d = d['tool']['flit']
-    unknown_sections = set(d) - {'metadata', 'scripts', 'entrypoints', 'sdist'}
-    unknown_sections = [s for s in unknown_sections if not 
s.lower().startswith('x-')]
-    if unknown_sections:
-        raise ConfigError('Unknown sections: ' + ', '.join(unknown_sections))
+    if 'project' in d:
+        # Metadata in [project] table (PEP 621)
+        if 'metadata' in dtool:
+            raise ConfigError(
+                "Use [project] table for metadata or [tool.flit.metadata], not 
both."
+            )
+        if ('scripts' in dtool) or ('entrypoints' in dtool):
+            raise ConfigError(
+                "Don't mix [project] metadata with [tool.flit.scripts] or "
+                "[tool.flit.entrypoints]. Use [project.scripts],"
+                "[project.gui-scripts] or [project.entry-points] as 
replacements."
+            )
+        loaded_cfg = read_pep621_metadata(d['project'], path)
 
-    if 'metadata' not in d:
-        raise ConfigError('[tool.flit.metadata] section is required')
+        module_tbl = dtool.get('module', {})
+        if 'name' in module_tbl:
+            loaded_cfg.module = module_tbl['name']
+    elif 'metadata' in dtool:
+        # Metadata in [tool.flit.metadata] (pre PEP 621 format)
+        if 'module' in dtool:
+            raise ConfigError(
+                "Use [tool.flit.module] table with new-style [project] 
metadata, "
+                "not [tool.flit.metadata]"
+            )
+        loaded_cfg = _prep_metadata(dtool['metadata'], path)
+        loaded_cfg.dynamic_metadata = ['version', 'description']
 
-    loaded_cfg = _prep_metadata(d['metadata'], path)
+        if 'entrypoints' in dtool:
+            loaded_cfg.entrypoints = flatten_entrypoints(dtool['entrypoints'])
 
-    if 'entrypoints' in d:
-        loaded_cfg.entrypoints = flatten_entrypoints(d['entrypoints'])
+        if 'scripts' in dtool:
+            loaded_cfg.add_scripts(dict(dtool['scripts']))
+    else:
+        raise ConfigError(
+            "Neither [project] nor [tool.flit.metadata] found in 
pyproject.toml"
+        )
 
-    if 'scripts' in d:
-        loaded_cfg.add_scripts(dict(d['scripts']))
+    unknown_sections = set(dtool) - {
+        'metadata', 'module', 'scripts', 'entrypoints', 'sdist'
+    }
+    unknown_sections = [s for s in unknown_sections if not 
s.lower().startswith('x-')]
+    if unknown_sections:
+        raise ConfigError('Unexpected tables in pyproject.toml: ' + ', '.join(
+            '[tool.flit.{}]'.format(s) for s in unknown_sections
+        ))
 
-    if 'sdist' in d:
-        unknown_keys = set(d['sdist']) - {'include', 'exclude'}
+    if 'sdist' in dtool:
+        unknown_keys = set(dtool['sdist']) - {'include', 'exclude'}
         if unknown_keys:
             raise ConfigError(
                 "Unknown keys in [tool.flit.sdist]:" + ", ".join(unknown_keys)
             )
 
         loaded_cfg.sdist_include_patterns = _check_glob_patterns(
-            d['sdist'].get('include', []), 'include'
+            dtool['sdist'].get('include', []), 'include'
         )
         loaded_cfg.sdist_exclude_patterns = _check_glob_patterns(
-            d['sdist'].get('exclude', []), 'exclude'
+            dtool['sdist'].get('exclude', []), 'exclude'
         )
 
     return loaded_cfg
@@ -176,6 +227,7 @@
         self.referenced_files = []
         self.sdist_include_patterns = []
         self.sdist_exclude_patterns = []
+        self.dynamic_metadata = []
 
     def add_scripts(self, scripts_dict):
         if scripts_dict:
@@ -191,6 +243,36 @@
 }
 
 
+def description_from_file(rel_path: str, proj_dir: Path, guess_mimetype=True):
+    if osp.isabs(rel_path):
+        raise ConfigError("Readme path must be relative")
+
+    desc_path = proj_dir / rel_path
+    try:
+        with desc_path.open('r', encoding='utf-8') as f:
+            raw_desc = f.read()
+    except IOError as e:
+        if e.errno == errno.ENOENT:
+            raise ConfigError(
+                "Description file {} does not exist".format(desc_path)
+            )
+        raise
+
+    if guess_mimetype:
+        ext = desc_path.suffix.lower()
+        try:
+            mimetype = readme_ext_to_content_type[ext]
+        except KeyError:
+            log.warning("Unknown extension %r for description file.", ext)
+            log.warning("  Recognised extensions: %s",
+                        " ".join(readme_ext_to_content_type))
+            mimetype = None
+    else:
+        mimetype = None
+
+    return raw_desc, mimetype
+
+
 def _prep_metadata(md_sect, path):
     """Process & verify the metadata from a config file
     
@@ -215,26 +297,8 @@
     if 'description-file' in md_sect:
         desc_path = md_sect.get('description-file')
         res.referenced_files.append(desc_path)
-        description_file = path.parent / desc_path
-        try:
-            with description_file.open('r', encoding='utf-8') as f:
-                raw_desc = f.read()
-        except IOError as e:
-            if e.errno == errno.ENOENT:
-                raise ConfigError(
-                    "Description file {} does not 
exist".format(description_file)
-                )
-            raise
-        ext = description_file.suffix
-        try:
-            mimetype = readme_ext_to_content_type[ext]
-        except KeyError:
-            log.warning("Unknown extension %r for description file.", ext)
-            log.warning("  Recognised extensions: %s",
-                        " ".join(readme_ext_to_content_type))
-            mimetype = None
-
-        md_dict['description'] =  raw_desc
+        desc_content, mimetype = description_from_file(desc_path, path.parent)
+        md_dict['description'] =  desc_content
         md_dict['description_content_type'] = mimetype
 
     if 'urls' in md_sect:
@@ -318,3 +382,249 @@
                 yield '{} ; extra == "{}" and ({})'.format(name, extra, 
envmark)
             else:
                 yield '{} ; extra == "{}"'.format(req, extra)
+
+
+def _check_type(d, field_name, cls):
+    if not isinstance(d[field_name], cls):
+        raise ConfigError(
+            "{} field should be {}, not {}".format(field_name, cls, 
type(d[field_name]))
+        )
+
+def _check_list_of_str(d, field_name):
+    if not isinstance(d[field_name], list) or not all(
+        isinstance(e, str) for e in d[field_name]
+    ):
+        raise ConfigError(
+            "{} field should be a list of strings".format(field_name)
+        )
+
+def read_pep621_metadata(proj, path) -> LoadedConfig:
+    lc = LoadedConfig()
+    md_dict = lc.metadata
+
+    if 'name' not in proj:
+        raise ConfigError('name must be specified in [project] table')
+    _check_type(proj, 'name', str)
+    lc.module = md_dict['name'] = proj['name']
+
+    unexpected_keys = proj.keys() - pep621_allowed_fields
+    if unexpected_keys:
+        log.warning("Unexpected names under [project]: %s", ', 
'.join(unexpected_keys))
+
+    if 'version' in proj:
+        _check_type(proj, 'version', str)
+        md_dict['version'] = normalise_version(proj['version'])
+    if 'description' in proj:
+        _check_type(proj, 'description', str)
+        md_dict['summary'] = proj['description']
+    if 'readme' in proj:
+        readme = proj['readme']
+        if isinstance(readme, str):
+            lc.referenced_files.append(readme)
+            desc_content, mimetype = description_from_file(readme, path.parent)
+
+        elif isinstance(readme, dict):
+            unrec_keys = set(readme.keys()) - {'text', 'file', 'content-type'}
+            if unrec_keys:
+                raise ConfigError(
+                    "Unrecognised keys in [project.readme]: 
{}".format(unrec_keys)
+                )
+            if 'content-type' in readme:
+                mimetype = readme['content-type']
+                mtype_base = mimetype.split(';')[0].strip()  # e.g. text/x-rst
+                if mtype_base not in readme_ext_to_content_type.values():
+                    raise ConfigError(
+                        "Unrecognised readme content-type: 
{!r}".format(mtype_base)
+                    )
+                # TODO: validate content-type parameters (charset, md variant)?
+            else:
+                raise ConfigError(
+                    "content-type field required in [project.readme] table"
+                )
+            if 'file' in readme:
+                if 'text' in readme:
+                    raise ConfigError(
+                        "[project.readme] should specify file or text, not 
both"
+                    )
+                lc.referenced_files.append(readme['file'])
+                desc_content, _ = description_from_file(
+                    readme['file'], path.parent, guess_mimetype=False
+                )
+            elif 'text' in readme:
+                desc_content = readme['text']
+            else:
+                raise ConfigError(
+                    "file or text field required in [project.readme] table"
+                )
+        else:
+            raise ConfigError(
+                "project.readme should be a string or a table"
+            )
+
+        md_dict['description'] = desc_content
+        md_dict['description_content_type'] = mimetype
+
+    if 'requires-python' in proj:
+        md_dict['requires_python'] = proj['requires-python']
+
+    if 'license' in proj:
+        _check_type(proj, 'license', dict)
+        license_tbl = proj['license']
+        unrec_keys = set(license_tbl.keys()) - {'text', 'file'}
+        if unrec_keys:
+            raise ConfigError(
+                "Unrecognised keys in [project.license]: {}".format(unrec_keys)
+            )
+
+        # TODO: Do something with license info.
+        # The 'License' field in packaging metadata is a brief description of
+        # a license, not the full text or a file path. PEP 639 will improve on
+        # how licenses are recorded.
+        if 'file' in license_tbl:
+            if 'text' in license_tbl:
+                raise ConfigError(
+                    "[project.license] should specify file or text, not both"
+                )
+            lc.referenced_files.append(license_tbl['file'])
+        elif 'text' in license_tbl:
+            pass
+        else:
+            raise ConfigError(
+                "file or text field required in [project.license] table"
+            )
+
+    if 'authors' in proj:
+        _check_type(proj, 'authors', list)
+        md_dict.update(pep621_people(proj['authors']))
+
+    if 'maintainers' in proj:
+        _check_type(proj, 'maintainers', list)
+        md_dict.update(pep621_people(proj['maintainers'], 
group_name='maintainer'))
+
+    if 'keywords' in proj:
+        _check_list_of_str(proj, 'keywords')
+        md_dict['keywords'] = ",".join(proj['keywords'])
+
+    if 'classifiers' in proj:
+        _check_list_of_str(proj, 'classifiers')
+        md_dict['classifiers'] = proj['classifiers']
+
+    if 'urls' in proj:
+        _check_type(proj, 'urls', dict)
+        project_urls = md_dict['project_urls'] = []
+        for label, url in sorted(proj['urls'].items()):
+            project_urls.append("{}, {}".format(label, url))
+
+    if 'entry-points' in proj:
+        _check_type(proj, 'entry-points', dict)
+        for grp in proj['entry-points'].values():
+            if not isinstance(grp, dict):
+                raise ConfigError(
+                    "projects.entry-points should only contain sub-tables"
+                )
+            if not all(isinstance(k, str) for k in grp.values()):
+                raise ConfigError(
+                    "[projects.entry-points.*] tables should have string 
values"
+                )
+        if set(proj['entry-points'].keys()) & {'console_scripts', 
'gui_scripts'}:
+            raise ConfigError(
+                "Scripts should be specified in [project.scripts] or "
+                "[project.gui-scripts], not under [project.entry-points]"
+            )
+        lc.entrypoints = proj['entry-points']
+
+    if 'scripts' in proj:
+        _check_type(proj, 'scripts', dict)
+        if not all(isinstance(k, str) for k in proj['scripts'].values()):
+            raise ConfigError(
+                "[projects.scripts] table should have string values"
+            )
+        lc.entrypoints['console_scripts'] = proj['scripts']
+
+    if 'gui-scripts' in proj:
+        _check_type(proj, 'gui-scripts', dict)
+        if not all(isinstance(k, str) for k in proj['gui-scripts'].values()):
+            raise ConfigError(
+                "[projects.gui-scripts] table should have string values"
+            )
+        lc.entrypoints['gui_scripts'] = proj['gui-scripts']
+
+    if 'dependencies' in proj:
+        _check_list_of_str(proj, 'dependencies')
+        md_dict['requires_dist'] = proj['dependencies']
+
+    if 'optional-dependencies' in proj:
+        _check_type(proj, 'optional-dependencies', dict)
+        optdeps = proj['optional-dependencies']
+        if not all(isinstance(e, list) for e in optdeps.values()):
+            raise ConfigError(
+                'Expected a dict of lists in optional-dependencies field'
+            )
+        for e, reqs in optdeps.items():
+            if not all(isinstance(a, str) for a in reqs):
+                raise ConfigError(
+                    'Expected a string list for optional-dependencies 
({})'.format(e)
+                )
+
+        reqs_noextra = md_dict.pop('requires_dist', [])
+        lc.reqs_by_extra = optdeps.copy()
+
+        # Add optional-dependencies into requires_dist
+        md_dict['requires_dist'] = \
+            reqs_noextra + list(_expand_requires_extra(lc.reqs_by_extra))
+
+        md_dict['provides_extra'] = sorted(lc.reqs_by_extra.keys())
+
+        # For internal use, record the main requirements as a '.none' extra.
+        lc.reqs_by_extra['.none'] = reqs_noextra
+
+    if 'dynamic' in proj:
+        _check_list_of_str(proj, 'dynamic')
+        dynamic = set(proj['dynamic'])
+        unrec_dynamic = dynamic - {'version', 'description'}
+        if unrec_dynamic:
+            raise ConfigError(
+                "flit only supports dynamic metadata for 'version' & 
'description'"
+            )
+        if dynamic.intersection(proj):
+            raise ConfigError(
+                "keys listed in project.dynamic must not be in [project] table"
+            )
+        lc.dynamic_metadata = dynamic
+
+    if ('version' not in proj) and ('version' not in lc.dynamic_metadata):
+        raise ConfigError(
+            "version must be specified under [project] or listed as a dynamic 
field"
+        )
+    if ('description' not in proj) and ('description' not in 
lc.dynamic_metadata):
+        raise ConfigError(
+            "description must be specified under [project] or listed as a 
dynamic field"
+        )
+
+    return lc
+
+def pep621_people(people, group_name='author') -> dict:
+    """Convert authors/maintainers from PEP 621 to core metadata fields"""
+    names, emails = [], []
+    for person in people:
+        if not isinstance(person, dict):
+            raise ConfigError("{} info must be list of 
dicts".format(group_name))
+        unrec_keys = set(person.keys()) - {'name', 'email'}
+        if unrec_keys:
+            raise ConfigError(
+                "Unrecognised keys in {} info: {}".format(group_name, 
unrec_keys)
+            )
+        if 'email' in person:
+            email = person['email']
+            if 'name' in person:
+                email = str(Address(person['name'], addr_spec=email))
+            emails.append(email)
+        elif 'name' in person:
+            names.append(person['name'])
+
+    res = {}
+    if names:
+        res[group_name] = ", ".join(names)
+    if emails:
+        res[group_name + '_email'] = ", ".join(emails)
+    return res
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/sdist.py 
new/flit_core-3.2.0/flit_core/sdist.py
--- old/flit_core-3.0.0/flit_core/sdist.py      2020-05-23 20:40:28.576257700 
+0200
+++ new/flit_core-3.2.0/flit_core/sdist.py      2021-01-23 13:24:58.122672800 
+0100
@@ -95,7 +95,7 @@
 
     @classmethod
     def from_ini_path(cls, ini_path: Path):
-        # Local import so bootstrapping doesn't try to load pytoml
+        # Local import so bootstrapping doesn't try to load toml
         from .config import read_flit_config
         ini_info = read_flit_config(ini_path)
         srcdir = ini_path.parent
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/inclusion/LICENSES/README 
new/flit_core-3.2.0/flit_core/tests/samples/inclusion/LICENSES/README
--- old/flit_core-3.0.0/flit_core/tests/samples/inclusion/LICENSES/README       
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/inclusion/LICENSES/README       
2021-03-21 11:25:46.340291500 +0100
@@ -0,0 +1,2 @@
+This directory will match the LICENSE* glob which Flit uses to add license
+files to wheel metadata.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep517/pyproject.toml 
new/flit_core-3.2.0/flit_core/tests/samples/pep517/pyproject.toml
--- old/flit_core-3.0.0/flit_core/tests/samples/pep517/pyproject.toml   
2020-01-26 23:11:13.888440000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep517/pyproject.toml   
2021-03-15 23:45:58.351866200 +0100
@@ -1,6 +1,6 @@
 [build-system]
-requires = ["flit"]
-build-backend = "flit.buildapi"
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
 
 [tool.flit.metadata]
 module = "module1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621/LICENSE 
new/flit_core-3.2.0/flit_core/tests/samples/pep621/LICENSE
--- old/flit_core-3.0.0/flit_core/tests/samples/pep621/LICENSE  1970-01-01 
01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep621/LICENSE  2021-03-15 
23:45:58.352866200 +0100
@@ -0,0 +1 @@
+This file should be added to wheels
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621/README.rst 
new/flit_core-3.2.0/flit_core/tests/samples/pep621/README.rst
--- old/flit_core-3.0.0/flit_core/tests/samples/pep621/README.rst       
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep621/README.rst       
2021-03-15 23:45:58.352866200 +0100
@@ -0,0 +1 @@
+This contains a n??n-ascii character
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621/module1a.py 
new/flit_core-3.2.0/flit_core/tests/samples/pep621/module1a.py
--- old/flit_core-3.0.0/flit_core/tests/samples/pep621/module1a.py      
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep621/module1a.py      
2021-03-15 23:45:58.352866200 +0100
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621/pyproject.toml 
new/flit_core-3.2.0/flit_core/tests/samples/pep621/pyproject.toml
--- old/flit_core-3.0.0/flit_core/tests/samples/pep621/pyproject.toml   
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep621/pyproject.toml   
2021-03-15 23:45:58.353866000 +0100
@@ -0,0 +1,39 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "module1"
+authors = [
+    {name = "Sir R??bin", email = "[email protected]"}
+]
+maintainers = [
+    {name = "Sir Galahad"}
+]
+readme = "README.rst"
+license = {file = "LICENSE"}
+requires-python = ">=3.7"
+dependencies = [
+    "requests >= 2.18",
+    "docutils",
+]
+keywords = ["example", "test"]
+dynamic = [
+    "version",
+    "description",
+]
+
+[project.optional-dependencies]
+test = [
+  "pytest",
+  "mock; python_version<'3.6'"
+]
+
+[project.urls]
+homepage = "http://github.com/sirrobin/module1";
+
+[project.entry-points.flit_test_example]
+foo = "module1:main"
+
+[tool.flit.module]
+name = "module1a"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/.gitignore
 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/.gitignore
--- 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/.gitignore
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/.gitignore
       2021-02-27 14:14:47.680864800 +0100
@@ -0,0 +1,2 @@
+# Created by pytest automatically.
+*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/CACHEDIR.TAG
 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/CACHEDIR.TAG
--- 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/CACHEDIR.TAG
     1970-01-01 01:00:00.000000000 +0100
+++ 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/CACHEDIR.TAG
     2021-02-27 14:14:47.680864800 +0100
@@ -0,0 +1,4 @@
+Signature: 8a477f597d28d172789f06886806bc55
+# This file is a cache directory tag created by pytest.
+# For information about cache directory tags, see:
+#      http://www.bford.info/cachedir/spec.html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/README.md
 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/README.md
--- 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/README.md
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/README.md
        2021-02-27 14:14:47.680864800 +0100
@@ -0,0 +1,8 @@
+# pytest cache directory #
+
+This directory contains data from the pytest's cache plugin,
+which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
+
+**Do not** commit this to version control.
+
+See [the docs](https://docs.pytest.org/en/stable/cache.html) for more 
information.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/nodeids
 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/nodeids
--- 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/nodeids
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/nodeids
  2021-02-27 14:14:52.008859600 +0100
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/stepwise
 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/stepwise
--- 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/stepwise
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/.pytest_cache/v/cache/stepwise
 2021-02-27 14:14:52.007859700 +0100
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/README.rst 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/README.rst
--- old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/README.rst     
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/README.rst     
2021-03-15 23:45:58.354866300 +0100
@@ -0,0 +1 @@
+This contains a n??n-ascii character
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/pyproject.toml 
new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/pyproject.toml
--- old/flit_core-3.0.0/flit_core/tests/samples/pep621_nodynamic/pyproject.toml 
1970-01-01 01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/samples/pep621_nodynamic/pyproject.toml 
2021-03-15 23:45:58.354866300 +0100
@@ -0,0 +1,28 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "module1"
+version = "0.03"
+description = "Statically specified description"
+authors = [
+    {name = "Sir Robin", email = "[email protected]"}
+]
+readme = {file = "README.rst", content-type = "text/x-rst"}
+classifiers = [
+    "Topic :: Internet :: WWW/HTTP",
+]
+dependencies = [
+    "requests >= 2.18",
+    "docutils",
+]
+
+[project.urls]
+homepage = "http://github.com/sirrobin/module1";
+
+[project.scripts]
+foo = "module1:main"
+
+[project.gui-scripts]
+foo-gui = "module1:main"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/tests/test_buildapi.py 
new/flit_core-3.2.0/flit_core/tests/test_buildapi.py
--- old/flit_core-3.0.0/flit_core/tests/test_buildapi.py        2020-08-25 
19:07:58.614150000 +0200
+++ new/flit_core-3.2.0/flit_core/tests/test_buildapi.py        2021-03-15 
23:45:58.355866200 +0100
@@ -26,6 +26,13 @@
         assert buildapi.get_requires_for_build_wheel() == []
         assert buildapi.get_requires_for_build_sdist() == []
 
+def test_get_build_requires_pep621_nodynamic():
+    # This module isn't inspected because version & description are specified
+    # as static metadata in pyproject.toml, so there are no build dependencies
+    with cwd(osp.join(samples_dir, 'pep621_nodynamic')):
+        assert buildapi.get_requires_for_build_wheel() == []
+        assert buildapi.get_requires_for_build_sdist() == []
+
 def test_get_build_requires_import():
     # This one has to be imported, so its runtime dependencies are also
     # build dependencies.
@@ -39,6 +46,13 @@
         filename = buildapi.build_wheel(td)
         assert filename.endswith('.whl'), filename
         assert_isfile(osp.join(td, filename))
+        assert zipfile.is_zipfile(osp.join(td, filename))
+
+def test_build_wheel_pep621():
+    with TemporaryDirectory() as td, cwd(osp.join(samples_dir, 'pep621')):
+        filename = buildapi.build_wheel(td)
+        assert filename.endswith('.whl'), filename
+        assert_isfile(osp.join(td, filename))
         assert zipfile.is_zipfile(osp.join(td, filename))
 
 def test_build_sdist():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/tests/test_common.py 
new/flit_core-3.2.0/flit_core/tests/test_common.py
--- old/flit_core-3.0.0/flit_core/tests/test_common.py  2020-08-25 
19:07:58.614150000 +0200
+++ new/flit_core-3.2.0/flit_core/tests/test_common.py  2021-03-21 
13:02:58.410992100 +0100
@@ -1,10 +1,14 @@
+import email.parser
+import email.policy
+from io import StringIO
 from pathlib import Path
 import pytest
 from unittest import TestCase
 
+from flit_core import config
 from flit_core.common import (
     Module, get_info_from_module, InvalidVersion, NoVersionError, 
check_version,
-    normalize_file_permissions, Metadata
+    normalize_file_permissions, Metadata, make_metadata,
 )
 
 samples_dir = Path(__file__).parent / 'samples'
@@ -40,10 +44,17 @@
                                 'version': '7.0'}
                          )
 
-        info = get_info_from_module(Module('package1', samples_dir))
+        pkg1 = Module('package1', samples_dir)
+        info = get_info_from_module(pkg1)
         self.assertEqual(info, {'summary': 'A sample package',
                                 'version': '0.1'}
                          )
+        info = get_info_from_module(pkg1, for_fields=['version'])
+        self.assertEqual(info, {'version': '0.1'})
+        info = get_info_from_module(pkg1, for_fields=['description'])
+        self.assertEqual(info, {'summary': 'A sample package'})
+        info = get_info_from_module(pkg1, for_fields=[])
+        self.assertEqual(info, {})
 
         info = get_info_from_module(Module('moduleunimportable', samples_dir))
         self.assertEqual(info, {'summary': 'A sample unimportable module',
@@ -95,3 +106,31 @@
     metadata.requires_python = requires_python
     result = metadata.supports_py2
     assert result == expected_result
+
+def test_make_metadata():
+    project_dir = samples_dir / 'pep621_nodynamic'
+    ini_info = config.read_flit_config(project_dir / 'pyproject.toml')
+    module = Module(ini_info.module, project_dir)
+    print(module.file)
+    md = make_metadata(module, ini_info)
+    assert md.version == '0.3'
+    assert md.summary == "Statically specified description"
+
+def test_metadata_multiline(tmp_path):
+    d = {
+        'name': 'foo',
+        'version': '1.0',
+        # Example from: 
https://packaging.python.org/specifications/core-metadata/#author
+        'author': ('C. Schultz, Universal Features Syndicate\n'
+                   'Los Angeles, CA <[email protected]>'),
+    }
+    md = Metadata(d)
+    sio = StringIO()
+    md.write_metadata_file(sio)
+    sio.seek(0)
+
+    msg = email.parser.Parser(policy=email.policy.compat32).parse(sio)
+    assert msg['Name'] == d['name']
+    assert msg['Version'] == d['version']
+    assert [l.lstrip() for l in msg['Author'].splitlines()] == 
d['author'].splitlines()
+    assert not msg.defects
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/tests/test_config.py 
new/flit_core-3.2.0/flit_core/tests/test_config.py
--- old/flit_core-3.0.0/flit_core/tests/test_config.py  2020-05-23 
20:40:28.577257600 +0200
+++ new/flit_core-3.2.0/flit_core/tests/test_config.py  2021-03-21 
22:14:40.833671800 +0100
@@ -15,6 +15,30 @@
     assert inf.module == 'module1'
     assert inf.metadata['home_page'] == 'http://github.com/sirrobin/module1'
 
+def test_load_pep621():
+    inf = config.read_flit_config(samples_dir / 'pep621' / 'pyproject.toml')
+    assert inf.module == 'module1a'
+    assert inf.metadata['name'] == 'module1'
+    assert inf.metadata['description_content_type'] == 'text/x-rst'
+    # Remove all whitespace from requirements so we don't check exact format:
+    assert {r.replace(' ', '') for r in inf.metadata['requires_dist']} == {
+        'docutils',
+        'requests>=2.18',
+        'pytest;extra=="test"',  # from [project.optional-dependencies]
+        'mock;extra=="test"and(python_version<\'3.6\')',
+    }
+    assert inf.metadata['author_email'] == "Sir R??bin <[email protected]>"
+    assert inf.entrypoints['flit_test_example']['foo'] == 'module1:main'
+    assert set(inf.dynamic_metadata) == {'version', 'description'}
+
+def test_load_pep621_nodynamic():
+    inf = config.read_flit_config(samples_dir / 'pep621_nodynamic' / 
'pyproject.toml')
+    assert inf.module == 'module1'
+    assert inf.metadata['name'] == 'module1'
+    assert inf.metadata['version'] == '0.3'
+    assert inf.metadata['summary'] == 'Statically specified description'
+    assert set(inf.dynamic_metadata) == set()
+
 def test_misspelled_key():
     with pytest.raises(config.ConfigError) as e_info:
         config.read_flit_config(samples_dir / 'misspelled-key.toml')
@@ -85,3 +109,44 @@
 
     with pytest.raises(config.ConfigError, match=err_match):
         config.prep_toml_config(toml_cfg, None)
+
[email protected](('proj_bad', 'err_match'), [
+    ({'version': 1}, r'\bstr\b'),
+    ({'license': {'fromage': 2}}, '[Uu]nrecognised'),
+    ({'license': {'file': 'LICENSE', 'text': 'xyz'}}, 'both'),
+    ({'license': {}}, 'required'),
+    ({'keywords': 'foo'}, 'list'),
+    ({'keywords': ['foo', 7]}, 'strings'),
+    ({'entry-points': {'foo': 'module1:main'}}, 'entry-point.*tables'),
+    ({'entry-points': {'group': {'foo': 7}}}, 'entry-point.*string'),
+    ({'entry-points': {'gui_scripts': {'foo': 'a:b'}}}, 
r'\[project\.gui-scripts\]'),
+    ({'scripts': {'foo': 7}}, 'scripts.*string'),
+    ({'gui-scripts': {'foo': 7}}, 'gui-scripts.*string'),
+    ({'optional-dependencies': {'test': 'requests'}}, 'list.*optional-dep'),
+    ({'optional-dependencies': {'test': [7]}}, 'string.*optional-dep'),
+    ({'dynamic': ['classifiers']}, 'dynamic'),
+    ({'dynamic': ['version']}, r'dynamic.*\[project\]'),
+    ({'authors': ['thomas']}, r'author.*\bdict'),
+    ({'maintainers': [{'title': 'Dr'}]}, r'maintainer.*title'),
+])
+def test_bad_pep621_info(proj_bad, err_match):
+    proj = {'name': 'module1', 'version': '1.0', 'description': 'x'}
+    proj.update(proj_bad)
+    with pytest.raises(config.ConfigError, match=err_match):
+        config.read_pep621_metadata(proj, samples_dir / 'pep621')
+
[email protected](('readme', 'err_match'), [
+    ({'file': 'README.rst'}, 'required'),
+    ({'file': 'README.rst', 'content-type': 'text/x-python'}, 'content-type'),
+    ('/opt/README.rst', 'relative'),
+    ({'file': 'README.rst', 'text': '', 'content-type': 'text/x-rst'}, 'both'),
+    ({'content-type': 'text/x-rst'}, 'required'),
+    ({'file': 'README.rst', 'content-type': 'text/x-rst', 'a': 'b'}, 
'[Uu]nrecognised'),
+    (5, r'readme.*string'),
+])
+def test_bad_pep621_readme(readme, err_match):
+    proj = {
+        'name': 'module1', 'version': '1.0', 'description': 'x', 'readme': 
readme
+    }
+    with pytest.raises(config.ConfigError, match=err_match):
+        config.read_pep621_metadata(proj, samples_dir / 'pep621')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/tests/test_sdist.py 
new/flit_core-3.2.0/flit_core/tests/test_sdist.py
--- old/flit_core-3.0.0/flit_core/tests/test_sdist.py   2020-05-23 
20:40:28.577257600 +0200
+++ new/flit_core-3.2.0/flit_core/tests/test_sdist.py   2021-03-15 
23:45:58.357866300 +0100
@@ -15,6 +15,22 @@
     assert_isfile(tmp_path / 'package1-0.1.tar.gz')
 
 
+def test_make_sdist_pep621(tmp_path):
+    builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'pep621' / 
'pyproject.toml')
+    path = builder.build(tmp_path)
+    assert path == tmp_path / 'module1-0.1.tar.gz'
+    assert_isfile(path)
+
+
+def test_make_sdist_pep621_nodynamic(tmp_path):
+    builder = sdist.SdistBuilder.from_ini_path(
+        samples_dir / 'pep621_nodynamic' / 'pyproject.toml'
+    )
+    path = builder.build(tmp_path)
+    assert path == tmp_path / 'module1-0.3.tar.gz'
+    assert_isfile(path)
+
+
 def test_clean_tarinfo():
     with tarfile.open(mode='w', fileobj=BytesIO()) as tf:
         ti = tf.gettarinfo(str(samples_dir / 'module1.py'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/tests/test_wheel.py 
new/flit_core-3.2.0/flit_core/tests/test_wheel.py
--- old/flit_core-3.0.0/flit_core/tests/test_wheel.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/flit_core-3.2.0/flit_core/tests/test_wheel.py   2021-03-21 
11:25:46.340291500 +0100
@@ -0,0 +1,12 @@
+from pathlib import Path
+
+from testpath import assert_isfile
+
+from flit_core.wheel import make_wheel_in
+
+samples_dir = Path(__file__).parent / 'samples'
+
+def test_licenses_dir(tmp_path):
+    # Smoketest for https://github.com/takluyver/flit/issues/399
+    info = make_wheel_in(samples_dir / 'inclusion' / 'pyproject.toml', 
tmp_path)
+    assert_isfile(info.file)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/flit_core/wheel.py 
new/flit_core-3.2.0/flit_core/wheel.py
--- old/flit_core-3.0.0/flit_core/wheel.py      2020-08-25 19:07:58.614150000 
+0200
+++ new/flit_core-3.2.0/flit_core/wheel.py      2021-03-21 11:25:46.340291500 
+0100
@@ -7,7 +7,6 @@
 import logging
 import os
 import os.path as osp
-import re
 import stat
 import sys
 import tempfile
@@ -83,7 +82,7 @@
 
     @classmethod
     def from_ini_path(cls, ini_path, target_fp):
-        # Local import so bootstrapping doesn't try to load pytoml
+        # Local import so bootstrapping doesn't try to load toml
         from .config import read_flit_config
         directory = ini_path.parent
         ini_info = read_flit_config(ini_path)
@@ -98,11 +97,9 @@
 
     @property
     def wheel_filename(self):
+        dist_name = common.normalize_dist_name(self.metadata.name, 
self.metadata.version)
         tag = ('py2.' if self.metadata.supports_py2 else '') + 'py3-none-any'
-        return '{}-{}-{}.whl'.format(
-                re.sub(r"[^\w\d.]+", "_", self.metadata.name, 
flags=re.UNICODE),
-                re.sub(r"[^\w\d.]+", "_", self.metadata.version, 
flags=re.UNICODE),
-                tag)
+        return '{}-{}.whl'.format(dist_name, tag)
 
     def _add_file_old(self, full_path, rel_path):
         log.debug("Adding %s to zip file", full_path)
@@ -200,8 +197,9 @@
                 common.write_entry_points(self.entrypoints, f)
 
         for base in ('COPYING', 'LICENSE'):
-            for path in sorted(glob(str(self.directory / (base + '*')))):
-                self._add_file(path, '%s/%s' % (self.dist_info, 
osp.basename(path)))
+            for path in sorted(self.directory.glob(base + '*')):
+                if path.is_file():
+                    self._add_file(path, '%s/%s' % (self.dist_info, path.name))
 
         with self._write_to_zip(self.dist_info + '/WHEEL') as f:
             _write_wheel_file(f, supports_py2=self.metadata.supports_py2)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/flit_core-3.0.0/pyproject.toml 
new/flit_core-3.2.0/pyproject.toml
--- old/flit_core-3.0.0/pyproject.toml  2020-03-01 14:13:02.429710900 +0100
+++ new/flit_core-3.2.0/pyproject.toml  2020-11-11 09:56:38.525969700 +0100
@@ -1,4 +1,4 @@
 [build-system]
 requires = []
 build-backend = "flit_core.build_thyself"
-backend-path = "."
+backend-path = ["."]

Reply via email to