From c15cd314bd3b0e383b40fa8d38fa5c84f05a2d0a Mon Sep 17 00:00:00 2001 From: messense Date: Wed, 14 Jul 2021 22:33:13 +0800 Subject: [PATCH 1/2] Fold long header fields in Python metadata --- Cargo.lock | 29 ++++++++++++++++- Cargo.toml | 1 + Changelog.md | 1 + Readme.md | 1 + src/metadata.rs | 48 ++++++++++++++++++++++++---- test-crates/pyo3-pure/LICENSE | 25 +++++++++++++++ test-crates/pyo3-pure/pyproject.toml | 1 + 7 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 test-crates/pyo3-pure/LICENSE diff --git a/Cargo.lock b/Cargo.lock index 00937aad7..9cc1b1ccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ dependencies = [ "atty", "bitflags", "strsim", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -872,6 +872,7 @@ dependencies = [ "tar", "target-lexicon", "tempfile", + "textwrap 0.14.2", "thiserror", "toml", "walkdir", @@ -1569,6 +1570,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "socket2" version = "0.4.0" @@ -1681,6 +1688,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.26" @@ -1832,6 +1850,15 @@ dependencies = [ "matches", ] +[[package]] +name = "unicode-linebreak" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a31f45d18a3213b918019f78fe6a73a14ab896807f0aaf5622aa0684749455" +dependencies = [ + "regex", +] + [[package]] name = "unicode-normalization" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index 791653c1d..e76d6f0f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ scroll = "0.10.2" target-lexicon = "0.12.0" pyproject-toml = "0.1.0" python-pkginfo = "0.4.0" +textwrap = "0.14.2" [dev-dependencies] indoc = "1.0.3" diff --git a/Changelog.md b/Changelog.md index add5a12a1..6605e0da1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Use UTF-8 encoding when reading `pyproject.toml` by domdfcoding in [#588](https://github.com/PyO3/maturin/pull/588) * Use Cargo's `repository` field as `Source Code` in project URL in [#590](https://github.com/PyO3/maturin/pull/590) +* Fold long header fields in Python metadata in [#594](https://github.com/PyO3/maturin/pull/594) ## [0.11.1] - 2021-07-10 diff --git a/Readme.md b/Readme.md index 0430c42b9..66a908885 100644 --- a/Readme.md +++ b/Readme.md @@ -124,6 +124,7 @@ my-project ## Python metadata maturin supports [PEP 621](https://www.python.org/dev/peps/pep-0621/), you can specify python package metadata in `pyproject.toml`. +maturin merges metadata from `Cargo.toml` and `pyproject.toml`, `pyproject.toml` take precedence over `Cargo.toml`. To specify python dependencies, add a list `dependencies` in a `[project]` section in the `pyproject.toml`. This list is equivalent to `install_requires` in setuptools: diff --git a/src/metadata.rs b/src/metadata.rs index 9b3da9e00..6efa83317 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -171,8 +171,12 @@ impl Metadata21 { (None, None) => {} } } - self.author = Some(names.join(", ")); - self.author_email = Some(emails.join(", ")); + if !names.is_empty() { + self.author = Some(names.join(", ")); + } + if !emails.is_empty() { + self.author_email = Some(emails.join(", ")); + } } if let Some(maintainers) = &project.maintainers { @@ -192,8 +196,12 @@ impl Metadata21 { (None, None) => {} } } - self.maintainer = Some(names.join(", ")); - self.maintainer_email = Some(emails.join(", ")); + if !names.is_empty() { + self.maintainer = Some(names.join(", ")); + } + if !emails.is_empty() { + self.maintainer_email = Some(emails.join(", ")); + } } if let Some(keywords) = &project.keywords { @@ -383,7 +391,7 @@ impl Metadata21 { } }; - add_option("Summary", &self.summary); + add_option("Summary", &self.summary.as_deref().map(fold_header)); add_option("Keywords", &self.keywords); add_option("Home-Page", &self.home_page); add_option("Download-URL", &self.download_url); @@ -391,7 +399,7 @@ impl Metadata21 { add_option("Author-email", &self.author_email); add_option("Maintainer", &self.maintainer); add_option("Maintainer-email", &self.maintainer_email); - add_option("License", &self.license); + add_option("License", &self.license.as_deref().map(fold_header)); add_option("Requires-Python", &self.requires_python); add_option("Description-Content-Type", &self.description_content_type); // Project-URL is special @@ -463,6 +471,28 @@ impl Metadata21 { } } +/// Fold long header field according to RFC 5322 section 2.2.3 +/// https://datatracker.ietf.org/doc/html/rfc5322#section-2.2.3 +fn fold_header(text: &str) -> String { + let mut result = String::with_capacity(text.len()); + + let options = textwrap::Options::new(78) + .initial_indent("") + .subsequent_indent("\t"); + for (i, line) in textwrap::wrap(text, options).iter().enumerate() { + if i > 0 { + result.push_str("\r\n"); + } + if line.is_empty() { + result.push('\t'); + } else { + result.push_str(&line); + } + } + + result +} + #[cfg(test)] mod test { use super::*; @@ -740,6 +770,10 @@ mod test { "attrs; extra == 'test'", "boltons; (sys_platform == 'win32') and extra == 'test'" ] - ) + ); + + let content = metadata.to_file_contents(); + let pkginfo: Result = content.parse(); + assert!(pkginfo.is_ok()); } } diff --git a/test-crates/pyo3-pure/LICENSE b/test-crates/pyo3-pure/LICENSE new file mode 100644 index 000000000..fa566452c --- /dev/null +++ b/test-crates/pyo3-pure/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2018-present konstin + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/test-crates/pyo3-pure/pyproject.toml b/test-crates/pyo3-pure/pyproject.toml index bf2c333fe..9b830af52 100644 --- a/test-crates/pyo3-pure/pyproject.toml +++ b/test-crates/pyo3-pure/pyproject.toml @@ -12,6 +12,7 @@ readme = "Readme.md" maintainers = [ {name = "messense", email = "messense@icloud.com"} ] +license = { file = "LICENSE" } [project.optional-dependencies] test = [ From ac632f8e448c1167d3969070fd3565266ec30ebf Mon Sep 17 00:00:00 2001 From: messense Date: Wed, 14 Jul 2021 22:56:20 +0800 Subject: [PATCH 2/2] Try Python 3.9.5 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da1048758..86434bb45 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: python-version: 3.8 - uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: 3.9.5 - name: Install cffi and virtualenv run: pip install cffi virtualenv - uses: actions-rs/toolchain@v1