From solipsis at pitrou.net Sat Dec 1 04:10:32 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Sat, 01 Dec 2018 09:10:32 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=6 Message-ID: <20181201091032.1.3B7A03DFDB880DD1@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_functools leaked [0, 3, 1] memory blocks, sum=4 test_logging leaked [26, -26, 1] memory blocks, sum=1 test_multiprocessing_spawn leaked [2, -1, 0] memory blocks, sum=1 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflog5I4EeV', '--timeout', '7200'] From webhook-mailer at python.org Sat Dec 1 05:04:07 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Sat, 01 Dec 2018 10:04:07 -0000 Subject: [Python-checkins] bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) Message-ID: https://github.com/python/cpython/commit/edeca92c84a3b08902ecdfe987cde00c7e617887 commit: edeca92c84a3b08902ecdfe987cde00c7e617887 branch: master author: Xtreak committer: Victor Stinner date: 2018-12-01T11:03:54+01:00 summary: bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) files: A Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testmock.py diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index a9c82dcb5d3e..9547b1a1fada 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -542,7 +542,7 @@ def reset_mock(self, visited=None,*, return_value=False, side_effect=False): self._mock_side_effect = None for child in self._mock_children.values(): - if isinstance(child, _SpecState): + if isinstance(child, _SpecState) or child is _deleted: continue child.reset_mock(visited) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 8cd284a6522d..ac6eea3720b8 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1596,6 +1596,16 @@ def test_attribute_deletion(self): self.assertRaises(AttributeError, getattr, mock, 'f') + def test_reset_mock_does_not_raise_on_attr_deletion(self): + # bpo-31177: reset_mock should not raise AttributeError when attributes + # were deleted in a mock instance + mock = Mock() + mock.child = True + del mock.child + mock.reset_mock() + self.assertFalse(hasattr(mock, 'child')) + + def test_class_assignable(self): for mock in Mock(), MagicMock(): self.assertNotIsInstance(mock, int) diff --git a/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst b/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst new file mode 100644 index 000000000000..f385571e99cc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst @@ -0,0 +1,2 @@ +Fix bug that prevented using :meth:`reset_mock ` +on mock instances with deleted attributes From webhook-mailer at python.org Sat Dec 1 05:16:30 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Sat, 01 Dec 2018 10:16:30 -0000 Subject: [Python-checkins] bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) Message-ID: https://github.com/python/cpython/commit/c0566e0ff6c2dd1a8b814ecd65649605c090527b commit: c0566e0ff6c2dd1a8b814ecd65649605c090527b branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-01T02:16:27-08:00 summary: bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) (cherry picked from commit edeca92c84a3b08902ecdfe987cde00c7e617887) Co-authored-by: Xtreak files: A Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testmock.py diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 31b198516161..4bb0c32c745b 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -543,7 +543,7 @@ def reset_mock(self, visited=None,*, return_value=False, side_effect=False): self._mock_side_effect = None for child in self._mock_children.values(): - if isinstance(child, _SpecState): + if isinstance(child, _SpecState) or child is _deleted: continue child.reset_mock(visited) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index b64c8663d212..4601136eff9c 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1556,6 +1556,16 @@ def test_attribute_deletion(self): self.assertRaises(AttributeError, getattr, mock, 'f') + def test_reset_mock_does_not_raise_on_attr_deletion(self): + # bpo-31177: reset_mock should not raise AttributeError when attributes + # were deleted in a mock instance + mock = Mock() + mock.child = True + del mock.child + mock.reset_mock() + self.assertFalse(hasattr(mock, 'child')) + + def test_class_assignable(self): for mock in Mock(), MagicMock(): self.assertNotIsInstance(mock, int) diff --git a/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst b/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst new file mode 100644 index 000000000000..f385571e99cc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst @@ -0,0 +1,2 @@ +Fix bug that prevented using :meth:`reset_mock ` +on mock instances with deleted attributes From webhook-mailer at python.org Sat Dec 1 05:24:53 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Sat, 01 Dec 2018 10:24:53 -0000 Subject: [Python-checkins] bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) Message-ID: https://github.com/python/cpython/commit/422c1658b7d34fdc73c5fc895b135862103d1983 commit: 422c1658b7d34fdc73c5fc895b135862103d1983 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-01T02:24:47-08:00 summary: bpo-31177: Skip deleted attributes while calling reset_mock (GH-9302) (cherry picked from commit edeca92c84a3b08902ecdfe987cde00c7e617887) Co-authored-by: Xtreak files: A Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testmock.py diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index b0c03c03bd6f..6ba186fb6631 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -541,7 +541,7 @@ def reset_mock(self, visited=None,*, return_value=False, side_effect=False): self._mock_side_effect = None for child in self._mock_children.values(): - if isinstance(child, _SpecState): + if isinstance(child, _SpecState) or child is _deleted: continue child.reset_mock(visited) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index c7bfa277b511..cc45f02409ef 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -1566,6 +1566,16 @@ def test_attribute_deletion(self): self.assertRaises(AttributeError, getattr, mock, 'f') + def test_reset_mock_does_not_raise_on_attr_deletion(self): + # bpo-31177: reset_mock should not raise AttributeError when attributes + # were deleted in a mock instance + mock = Mock() + mock.child = True + del mock.child + mock.reset_mock() + self.assertFalse(hasattr(mock, 'child')) + + def test_class_assignable(self): for mock in Mock(), MagicMock(): self.assertNotIsInstance(mock, int) diff --git a/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst b/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst new file mode 100644 index 000000000000..f385571e99cc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-14-10-38-18.bpo-31177.Sv91TN.rst @@ -0,0 +1,2 @@ +Fix bug that prevented using :meth:`reset_mock ` +on mock instances with deleted attributes From webhook-mailer at python.org Sat Dec 1 07:16:03 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Sat, 01 Dec 2018 12:16:03 -0000 Subject: [Python-checkins] [2.7] Fix signature of xml.dom.minidom.Document.toprettyxml(). (GH-10814). (GH-10827) Message-ID: https://github.com/python/cpython/commit/dfd4a1d8414ea54a3c56e909167983a503e51067 commit: dfd4a1d8414ea54a3c56e909167983a503e51067 branch: 2.7 author: E Kawashima committer: Serhiy Storchaka date: 2018-12-01T14:16:00+02:00 summary: [2.7] Fix signature of xml.dom.minidom.Document.toprettyxml(). (GH-10814). (GH-10827) (cherry picked from commit b7c2182604d5796b5af4c837991aa0b8c8a2d41f) files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index f91259a8fb5c..2f4022428db0 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -169,7 +169,7 @@ module documentation. This section lists the differences between the API and the *encoding* argument was introduced; see :meth:`writexml`. -.. method:: Node.toprettyxml([indent=""[, newl=""[, encoding=""]]]) +.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Sat Dec 1 07:19:42 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Sat, 01 Dec 2018 12:19:42 -0000 Subject: [Python-checkins] [3.7] Fix signature of xml.dom.minidom.Document.toprettyxml(). (GH-10814). (GH-10824) Message-ID: https://github.com/python/cpython/commit/7da9755021a27075f5856c6ffff4c949e790bbee commit: 7da9755021a27075f5856c6ffff4c949e790bbee branch: 3.7 author: E Kawashima committer: Serhiy Storchaka date: 2018-12-01T14:19:39+02:00 summary: [3.7] Fix signature of xml.dom.minidom.Document.toprettyxml(). (GH-10814). (GH-10824) (cherry picked from commit b7c2182604d5796b5af4c837991aa0b8c8a2d41f) files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index 40470e8736e7..d5d7b20efe60 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -156,7 +156,7 @@ module documentation. This section lists the differences between the API and encoding. Encoding this string in an encoding other than UTF-8 is likely incorrect, since UTF-8 is the default encoding of XML. -.. method:: Node.toprettyxml(indent="", newl="", encoding="") +.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Sat Dec 1 07:21:04 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Sat, 01 Dec 2018 12:21:04 -0000 Subject: [Python-checkins] [3.6] Fix signature of xml.dom.minidom.Document.toprettyxml(). (GH-10814). (GH-10825) Message-ID: https://github.com/python/cpython/commit/1e28daf0b58c9c3a08e98b3a3caebb53acf9a617 commit: 1e28daf0b58c9c3a08e98b3a3caebb53acf9a617 branch: 3.6 author: E Kawashima committer: Serhiy Storchaka date: 2018-12-01T14:21:00+02:00 summary: [3.6] Fix signature of xml.dom.minidom.Document.toprettyxml(). (GH-10814). (GH-10825) (cherry picked from commit b7c2182604d5796b5af4c837991aa0b8c8a2d41f) files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index 40470e8736e7..d5d7b20efe60 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -156,7 +156,7 @@ module documentation. This section lists the differences between the API and encoding. Encoding this string in an encoding other than UTF-8 is likely incorrect, since UTF-8 is the default encoding of XML. -.. method:: Node.toprettyxml(indent="", newl="", encoding="") +.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Sat Dec 1 07:30:23 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Sat, 01 Dec 2018 12:30:23 -0000 Subject: [Python-checkins] bpo-35371: Fix possible crash in os.utime() on Windows. (GH-10844) Message-ID: https://github.com/python/cpython/commit/32bc11c33cf5ccea165b5f4ac3799f02fdf9c76a commit: 32bc11c33cf5ccea165b5f4ac3799f02fdf9c76a branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-01T14:30:20+02:00 summary: bpo-35371: Fix possible crash in os.utime() on Windows. (GH-10844) files: A Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst M Lib/test/test_os.py M Modules/posixmodule.c diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 3f6e48f0c8e6..aca445f91620 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -634,6 +634,29 @@ def test_utime_invalid_arguments(self): # seconds and nanoseconds parameters are mutually exclusive with self.assertRaises(ValueError): os.utime(self.fname, (5, 5), ns=(5, 5)) + with self.assertRaises(TypeError): + os.utime(self.fname, [5, 5]) + with self.assertRaises(TypeError): + os.utime(self.fname, (5,)) + with self.assertRaises(TypeError): + os.utime(self.fname, (5, 5, 5)) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=[5, 5]) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=(5,)) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=(5, 5, 5)) + + if os.utime not in os.supports_follow_symlinks: + with self.assertRaises(NotImplementedError): + os.utime(self.fname, (5, 5), follow_symlinks=False) + if os.utime not in os.supports_fd: + with open(self.fname, 'wb', 0) as fp: + with self.assertRaises(TypeError): + os.utime(fp.fileno(), (5, 5)) + if os.utime not in os.supports_dir_fd: + with self.assertRaises(NotImplementedError): + os.utime(self.fname, (5, 5), dir_fd=0) @support.cpython_only def test_issue31577(self): diff --git a/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst b/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst new file mode 100644 index 000000000000..f40d13939311 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst @@ -0,0 +1,2 @@ +Fixed possible crash in ``os.utime()`` on Windows when pass incorrect +arguments. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d42e40f243e2..7571385ae51d 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4689,7 +4689,6 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, int result; #endif - PyObject *return_value = NULL; utime_t utime; memset(&utime, 0, sizeof(utime_t)); @@ -4698,7 +4697,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, PyErr_SetString(PyExc_ValueError, "utime: you may specify either 'times'" " or 'ns' but not both"); - goto exit; + return NULL; } if (times && (times != Py_None)) { @@ -4708,14 +4707,14 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, PyErr_SetString(PyExc_TypeError, "utime: 'times' must be either" " a tuple of two ints or None"); - goto exit; + return NULL; } utime.now = 0; if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0), &a_sec, &a_nsec, _PyTime_ROUND_FLOOR) == -1 || _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1), &m_sec, &m_nsec, _PyTime_ROUND_FLOOR) == -1) { - goto exit; + return NULL; } utime.atime_s = a_sec; utime.atime_ns = a_nsec; @@ -4726,14 +4725,14 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (!PyTuple_CheckExact(ns) || (PyTuple_Size(ns) != 2)) { PyErr_SetString(PyExc_TypeError, "utime: 'ns' must be a tuple of two ints"); - goto exit; + return NULL; } utime.now = 0; if (!split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 0), &utime.atime_s, &utime.atime_ns) || !split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 1), &utime.mtime_s, &utime.mtime_ns)) { - goto exit; + return NULL; } } else { @@ -4743,20 +4742,20 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, #if !defined(UTIME_HAVE_NOFOLLOW_SYMLINKS) if (follow_symlinks_specified("utime", follow_symlinks)) - goto exit; + return NULL; #endif if (path_and_dir_fd_invalid("utime", path, dir_fd) || dir_fd_and_fd_invalid("utime", dir_fd, path->fd) || fd_and_follow_symlinks_invalid("utime", path->fd, follow_symlinks)) - goto exit; + return NULL; #if !defined(HAVE_UTIMENSAT) if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { PyErr_SetString(PyExc_ValueError, "utime: cannot use dir_fd and follow_symlinks " "together on this platform"); - goto exit; + return NULL; } #endif @@ -4768,7 +4767,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, Py_END_ALLOW_THREADS if (hFile == INVALID_HANDLE_VALUE) { path_error(path); - goto exit; + return NULL; } if (utime.now) { @@ -4785,8 +4784,10 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, something is wrong with the file, when it also could be the time stamp that gives a problem. */ PyErr_SetFromWindowsErr(0); - goto exit; + CloseHandle(hFile); + return NULL; } + CloseHandle(hFile); #else /* MS_WINDOWS */ Py_BEGIN_ALLOW_THREADS @@ -4814,21 +4815,13 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (result < 0) { /* see previous comment about not putting filename in error here */ - return_value = posix_error(); - goto exit; + posix_error(); + return NULL; } #endif /* MS_WINDOWS */ - Py_INCREF(Py_None); - return_value = Py_None; - -exit: -#ifdef MS_WINDOWS - if (hFile != INVALID_HANDLE_VALUE) - CloseHandle(hFile); -#endif - return return_value; + Py_RETURN_NONE; } /* Process operations */ From webhook-mailer at python.org Sat Dec 1 07:52:07 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Sat, 01 Dec 2018 12:52:07 -0000 Subject: [Python-checkins] bpo-35371: Fix possible crash in os.utime() on Windows. (GH-10844) Message-ID: https://github.com/python/cpython/commit/265b41996aa3f604624a8046d1c314a1aee4b590 commit: 265b41996aa3f604624a8046d1c314a1aee4b590 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-01T04:52:04-08:00 summary: bpo-35371: Fix possible crash in os.utime() on Windows. (GH-10844) (cherry picked from commit 32bc11c33cf5ccea165b5f4ac3799f02fdf9c76a) Co-authored-by: Serhiy Storchaka files: A Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst M Lib/test/test_os.py M Modules/posixmodule.c diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 098d1d44fa35..fe7261dd76e9 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -634,6 +634,29 @@ def test_utime_invalid_arguments(self): # seconds and nanoseconds parameters are mutually exclusive with self.assertRaises(ValueError): os.utime(self.fname, (5, 5), ns=(5, 5)) + with self.assertRaises(TypeError): + os.utime(self.fname, [5, 5]) + with self.assertRaises(TypeError): + os.utime(self.fname, (5,)) + with self.assertRaises(TypeError): + os.utime(self.fname, (5, 5, 5)) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=[5, 5]) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=(5,)) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=(5, 5, 5)) + + if os.utime not in os.supports_follow_symlinks: + with self.assertRaises(NotImplementedError): + os.utime(self.fname, (5, 5), follow_symlinks=False) + if os.utime not in os.supports_fd: + with open(self.fname, 'wb', 0) as fp: + with self.assertRaises(TypeError): + os.utime(fp.fileno(), (5, 5)) + if os.utime not in os.supports_dir_fd: + with self.assertRaises(NotImplementedError): + os.utime(self.fname, (5, 5), dir_fd=0) @support.cpython_only def test_issue31577(self): diff --git a/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst b/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst new file mode 100644 index 000000000000..f40d13939311 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst @@ -0,0 +1,2 @@ +Fixed possible crash in ``os.utime()`` on Windows when pass incorrect +arguments. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index dbd534cab06a..cab30c21025c 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4645,7 +4645,6 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, int result; #endif - PyObject *return_value = NULL; utime_t utime; memset(&utime, 0, sizeof(utime_t)); @@ -4654,7 +4653,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, PyErr_SetString(PyExc_ValueError, "utime: you may specify either 'times'" " or 'ns' but not both"); - goto exit; + return NULL; } if (times && (times != Py_None)) { @@ -4664,14 +4663,14 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, PyErr_SetString(PyExc_TypeError, "utime: 'times' must be either" " a tuple of two ints or None"); - goto exit; + return NULL; } utime.now = 0; if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0), &a_sec, &a_nsec, _PyTime_ROUND_FLOOR) == -1 || _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1), &m_sec, &m_nsec, _PyTime_ROUND_FLOOR) == -1) { - goto exit; + return NULL; } utime.atime_s = a_sec; utime.atime_ns = a_nsec; @@ -4682,14 +4681,14 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (!PyTuple_CheckExact(ns) || (PyTuple_Size(ns) != 2)) { PyErr_SetString(PyExc_TypeError, "utime: 'ns' must be a tuple of two ints"); - goto exit; + return NULL; } utime.now = 0; if (!split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 0), &utime.atime_s, &utime.atime_ns) || !split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 1), &utime.mtime_s, &utime.mtime_ns)) { - goto exit; + return NULL; } } else { @@ -4699,20 +4698,20 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, #if !defined(UTIME_HAVE_NOFOLLOW_SYMLINKS) if (follow_symlinks_specified("utime", follow_symlinks)) - goto exit; + return NULL; #endif if (path_and_dir_fd_invalid("utime", path, dir_fd) || dir_fd_and_fd_invalid("utime", dir_fd, path->fd) || fd_and_follow_symlinks_invalid("utime", path->fd, follow_symlinks)) - goto exit; + return NULL; #if !defined(HAVE_UTIMENSAT) if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { PyErr_SetString(PyExc_ValueError, "utime: cannot use dir_fd and follow_symlinks " "together on this platform"); - goto exit; + return NULL; } #endif @@ -4724,7 +4723,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, Py_END_ALLOW_THREADS if (hFile == INVALID_HANDLE_VALUE) { path_error(path); - goto exit; + return NULL; } if (utime.now) { @@ -4741,8 +4740,10 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, something is wrong with the file, when it also could be the time stamp that gives a problem. */ PyErr_SetFromWindowsErr(0); - goto exit; + CloseHandle(hFile); + return NULL; } + CloseHandle(hFile); #else /* MS_WINDOWS */ Py_BEGIN_ALLOW_THREADS @@ -4770,21 +4771,13 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (result < 0) { /* see previous comment about not putting filename in error here */ - return_value = posix_error(); - goto exit; + posix_error(); + return NULL; } #endif /* MS_WINDOWS */ - Py_INCREF(Py_None); - return_value = Py_None; - -exit: -#ifdef MS_WINDOWS - if (hFile != INVALID_HANDLE_VALUE) - CloseHandle(hFile); -#endif - return return_value; + Py_RETURN_NONE; } /* Process operations */ From webhook-mailer at python.org Sat Dec 1 07:53:40 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Sat, 01 Dec 2018 12:53:40 -0000 Subject: [Python-checkins] bpo-35371: Fix possible crash in os.utime() on Windows. (GH-10844) Message-ID: https://github.com/python/cpython/commit/013832ff964a0b3b59e04a07a33bae65c1c3ae84 commit: 013832ff964a0b3b59e04a07a33bae65c1c3ae84 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-01T04:53:36-08:00 summary: bpo-35371: Fix possible crash in os.utime() on Windows. (GH-10844) (cherry picked from commit 32bc11c33cf5ccea165b5f4ac3799f02fdf9c76a) Co-authored-by: Serhiy Storchaka files: A Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst M Lib/test/test_os.py M Modules/posixmodule.c diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 9e35d55b8056..7a839c83fe4b 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -660,6 +660,29 @@ def test_utime_invalid_arguments(self): # seconds and nanoseconds parameters are mutually exclusive with self.assertRaises(ValueError): os.utime(self.fname, (5, 5), ns=(5, 5)) + with self.assertRaises(TypeError): + os.utime(self.fname, [5, 5]) + with self.assertRaises(TypeError): + os.utime(self.fname, (5,)) + with self.assertRaises(TypeError): + os.utime(self.fname, (5, 5, 5)) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=[5, 5]) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=(5,)) + with self.assertRaises(TypeError): + os.utime(self.fname, ns=(5, 5, 5)) + + if os.utime not in os.supports_follow_symlinks: + with self.assertRaises(NotImplementedError): + os.utime(self.fname, (5, 5), follow_symlinks=False) + if os.utime not in os.supports_fd: + with open(self.fname, 'wb', 0) as fp: + with self.assertRaises(TypeError): + os.utime(fp.fileno(), (5, 5)) + if os.utime not in os.supports_dir_fd: + with self.assertRaises(NotImplementedError): + os.utime(self.fname, (5, 5), dir_fd=0) from test import mapping_tests diff --git a/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst b/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst new file mode 100644 index 000000000000..f40d13939311 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-01-13-44-12.bpo-35371.fTAwlX.rst @@ -0,0 +1,2 @@ +Fixed possible crash in ``os.utime()`` on Windows when pass incorrect +arguments. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b7091ca4c234..03825c3228ca 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4589,7 +4589,6 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, int result; #endif - PyObject *return_value = NULL; utime_t utime; memset(&utime, 0, sizeof(utime_t)); @@ -4598,7 +4597,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, PyErr_SetString(PyExc_ValueError, "utime: you may specify either 'times'" " or 'ns' but not both"); - goto exit; + return NULL; } if (times && (times != Py_None)) { @@ -4608,14 +4607,14 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, PyErr_SetString(PyExc_TypeError, "utime: 'times' must be either" " a tuple of two ints or None"); - goto exit; + return NULL; } utime.now = 0; if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0), &a_sec, &a_nsec, _PyTime_ROUND_FLOOR) == -1 || _PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1), &m_sec, &m_nsec, _PyTime_ROUND_FLOOR) == -1) { - goto exit; + return NULL; } utime.atime_s = a_sec; utime.atime_ns = a_nsec; @@ -4626,14 +4625,14 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (!PyTuple_CheckExact(ns) || (PyTuple_Size(ns) != 2)) { PyErr_SetString(PyExc_TypeError, "utime: 'ns' must be a tuple of two ints"); - goto exit; + return NULL; } utime.now = 0; if (!split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 0), &utime.atime_s, &utime.atime_ns) || !split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 1), &utime.mtime_s, &utime.mtime_ns)) { - goto exit; + return NULL; } } else { @@ -4643,20 +4642,20 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, #if !defined(UTIME_HAVE_NOFOLLOW_SYMLINKS) if (follow_symlinks_specified("utime", follow_symlinks)) - goto exit; + return NULL; #endif if (path_and_dir_fd_invalid("utime", path, dir_fd) || dir_fd_and_fd_invalid("utime", dir_fd, path->fd) || fd_and_follow_symlinks_invalid("utime", path->fd, follow_symlinks)) - goto exit; + return NULL; #if !defined(HAVE_UTIMENSAT) if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { PyErr_SetString(PyExc_ValueError, "utime: cannot use dir_fd and follow_symlinks " "together on this platform"); - goto exit; + return NULL; } #endif @@ -4668,7 +4667,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, Py_END_ALLOW_THREADS if (hFile == INVALID_HANDLE_VALUE) { path_error(path); - goto exit; + return NULL; } if (utime.now) { @@ -4685,8 +4684,10 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, something is wrong with the file, when it also could be the time stamp that gives a problem. */ PyErr_SetFromWindowsErr(0); - goto exit; + CloseHandle(hFile); + return NULL; } + CloseHandle(hFile); #else /* MS_WINDOWS */ Py_BEGIN_ALLOW_THREADS @@ -4714,21 +4715,13 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns, if (result < 0) { /* see previous comment about not putting filename in error here */ - return_value = posix_error(); - goto exit; + posix_error(); + return NULL; } #endif /* MS_WINDOWS */ - Py_INCREF(Py_None); - return_value = Py_None; - -exit: -#ifdef MS_WINDOWS - if (hFile != INVALID_HANDLE_VALUE) - CloseHandle(hFile); -#endif - return return_value; + Py_RETURN_NONE; } /* Process operations */ From solipsis at pitrou.net Sun Dec 2 04:09:55 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Sun, 02 Dec 2018 09:09:55 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=4 Message-ID: <20181202090955.1.B61C2D2011B18F06@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_functools leaked [0, 3, 1] memory blocks, sum=4 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflog8smGO9', '--timeout', '7200'] From webhook-mailer at python.org Sun Dec 2 10:53:18 2018 From: webhook-mailer at python.org (Ivan Levkivskyi) Date: Sun, 02 Dec 2018 15:53:18 -0000 Subject: [Python-checkins] bpo-35341: Add generic version of OrderedDict to typing (GH-10850) Message-ID: https://github.com/python/cpython/commit/68b56d02ef20479b87c65e523cf3dec1b7b77d40 commit: 68b56d02ef20479b87c65e523cf3dec1b7b77d40 branch: master author: Ismo Toijala committer: Ivan Levkivskyi date: 2018-12-02T15:53:14Z summary: bpo-35341: Add generic version of OrderedDict to typing (GH-10850) files: A Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst M Doc/library/typing.rst M Lib/test/test_typing.py M Lib/typing.py diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 4469e88b2248..47ae4213f3c0 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -689,6 +689,12 @@ The module defines the following classes, functions and decorators: .. versionadded:: 3.5.2 +.. class:: OrderedDict(collections.OrderedDict, MutableMapping[KT, VT]) + + A generic version of :class:`collections.OrderedDict`. + + .. versionadded:: 3.7.2 + .. class:: Counter(collections.Counter, Dict[T, int]) A generic version of :class:`collections.Counter`. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6d8cc5319fb1..0d66ebbd1845 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2075,6 +2075,22 @@ class MyDefDict(typing.DefaultDict[str, int]): self.assertIsSubclass(MyDefDict, collections.defaultdict) self.assertNotIsSubclass(collections.defaultdict, MyDefDict) + def test_ordereddict_instantiation(self): + self.assertIs(type(typing.OrderedDict()), collections.OrderedDict) + self.assertIs(type(typing.OrderedDict[KT, VT]()), collections.OrderedDict) + self.assertIs(type(typing.OrderedDict[str, int]()), collections.OrderedDict) + + def test_ordereddict_subclass(self): + + class MyOrdDict(typing.OrderedDict[str, int]): + pass + + od = MyOrdDict() + self.assertIsInstance(od, MyOrdDict) + + self.assertIsSubclass(MyOrdDict, collections.OrderedDict) + self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) + @skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3') def test_chainmap_instantiation(self): self.assertIs(type(typing.ChainMap()), collections.ChainMap) diff --git a/Lib/typing.py b/Lib/typing.py index 4f9e04506fb5..3243e2af1119 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1241,6 +1241,7 @@ def _alias(origin, params, inst=True): AsyncContextManager = _alias(contextlib.AbstractAsyncContextManager, T_co) Dict = _alias(dict, (KT, VT), inst=False) DefaultDict = _alias(collections.defaultdict, (KT, VT)) +OrderedDict = _alias(collections.OrderedDict, (KT, VT)) Counter = _alias(collections.Counter, T) ChainMap = _alias(collections.ChainMap, (KT, VT)) Generator = _alias(collections.abc.Generator, (T_co, T_contra, V_co)) diff --git a/Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst b/Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst new file mode 100644 index 000000000000..43aa9956c1f7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst @@ -0,0 +1 @@ +Add generic version of ``collections.OrderedDict`` to the ``typing`` module. Patch by Ismo Toijala. From webhook-mailer at python.org Sun Dec 2 11:14:48 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Sun, 02 Dec 2018 16:14:48 -0000 Subject: [Python-checkins] bpo-35341: Add generic version of OrderedDict to typing (GH-10850) Message-ID: https://github.com/python/cpython/commit/6cb0486ce861903448bd6ba1095685b6cd48e3bd commit: 6cb0486ce861903448bd6ba1095685b6cd48e3bd branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-02T08:14:44-08:00 summary: bpo-35341: Add generic version of OrderedDict to typing (GH-10850) (cherry picked from commit 68b56d02ef20479b87c65e523cf3dec1b7b77d40) Co-authored-by: Ismo Toijala files: A Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst M Doc/library/typing.rst M Lib/test/test_typing.py M Lib/typing.py diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index f6d67d1ff9c5..0f37fc60896f 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -689,6 +689,12 @@ The module defines the following classes, functions and decorators: .. versionadded:: 3.5.2 +.. class:: OrderedDict(collections.OrderedDict, MutableMapping[KT, VT]) + + A generic version of :class:`collections.OrderedDict`. + + .. versionadded:: 3.7.2 + .. class:: Counter(collections.Counter, Dict[T, int]) A generic version of :class:`collections.Counter`. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 6d8cc5319fb1..0d66ebbd1845 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2075,6 +2075,22 @@ class MyDefDict(typing.DefaultDict[str, int]): self.assertIsSubclass(MyDefDict, collections.defaultdict) self.assertNotIsSubclass(collections.defaultdict, MyDefDict) + def test_ordereddict_instantiation(self): + self.assertIs(type(typing.OrderedDict()), collections.OrderedDict) + self.assertIs(type(typing.OrderedDict[KT, VT]()), collections.OrderedDict) + self.assertIs(type(typing.OrderedDict[str, int]()), collections.OrderedDict) + + def test_ordereddict_subclass(self): + + class MyOrdDict(typing.OrderedDict[str, int]): + pass + + od = MyOrdDict() + self.assertIsInstance(od, MyOrdDict) + + self.assertIsSubclass(MyOrdDict, collections.OrderedDict) + self.assertNotIsSubclass(collections.OrderedDict, MyOrdDict) + @skipUnless(sys.version_info >= (3, 3), 'ChainMap was added in 3.3') def test_chainmap_instantiation(self): self.assertIs(type(typing.ChainMap()), collections.ChainMap) diff --git a/Lib/typing.py b/Lib/typing.py index cfcbb3b76328..8cf0d00bceaf 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1242,6 +1242,7 @@ def _alias(origin, params, inst=True): AsyncContextManager = _alias(contextlib.AbstractAsyncContextManager, T_co) Dict = _alias(dict, (KT, VT), inst=False) DefaultDict = _alias(collections.defaultdict, (KT, VT)) +OrderedDict = _alias(collections.OrderedDict, (KT, VT)) Counter = _alias(collections.Counter, T) ChainMap = _alias(collections.ChainMap, (KT, VT)) Generator = _alias(collections.abc.Generator, (T_co, T_contra, V_co)) diff --git a/Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst b/Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst new file mode 100644 index 000000000000..43aa9956c1f7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-02-13-50-52.bpo-35341.32E8T_.rst @@ -0,0 +1 @@ +Add generic version of ``collections.OrderedDict`` to the ``typing`` module. Patch by Ismo Toijala. From webhook-mailer at python.org Mon Dec 3 02:58:26 2018 From: webhook-mailer at python.org (Chris Withers) Date: Mon, 03 Dec 2018 07:58:26 -0000 Subject: [Python-checkins] bpo-32153: Add unit test for create_autospec with partial function returned in getattr (#10398) Message-ID: https://github.com/python/cpython/commit/c667b094ae37799a7e42ba5cd2ad501cc7920888 commit: c667b094ae37799a7e42ba5cd2ad501cc7920888 branch: master author: Xtreak committer: Chris Withers date: 2018-12-03T07:58:15Z summary: bpo-32153: Add unit test for create_autospec with partial function returned in getattr (#10398) * Add create_autospec with partial function returned in getattr * Use self.assertFalse instead of assert * Use different names and remove object files: M Lib/unittest/test/testmock/testhelpers.py diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 7919482ae99c..9edebf551660 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -8,6 +8,7 @@ ) from datetime import datetime +from functools import partial class SomeClass(object): def one(self, a, b): @@ -871,6 +872,19 @@ def test_autospec_on_bound_builtin_function(self): mocked.assert_called_once_with(4, 5, 6) + def test_autospec_getattr_partial_function(self): + # bpo-32153 : getattr returning partial functions without + # __name__ should not create AttributeError in create_autospec + class Foo: + + def __getattr__(self, attribute): + return partial(lambda name: name, attribute) + + proxy = Foo() + autospec = create_autospec(proxy) + self.assertFalse(hasattr(autospec, '__name__')) + + class TestCallList(unittest.TestCase): def test_args_list_contains_call_list(self): From webhook-mailer at python.org Mon Dec 3 03:26:11 2018 From: webhook-mailer at python.org (Chris Withers) Date: Mon, 03 Dec 2018 08:26:11 -0000 Subject: [Python-checkins] bpo-32153: Add unit test for create_autospec with partial function returned in getattr (GH-10398) (#10855) Message-ID: https://github.com/python/cpython/commit/1ef06c62d3c05cbba6448c56af30a09c551c9ec2 commit: 1ef06c62d3c05cbba6448c56af30a09c551c9ec2 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Chris Withers date: 2018-12-03T08:26:06Z summary: bpo-32153: Add unit test for create_autospec with partial function returned in getattr (GH-10398) (#10855) * Add create_autospec with partial function returned in getattr * Use self.assertFalse instead of assert * Use different names and remove object (cherry picked from commit c667b094ae37799a7e42ba5cd2ad501cc7920888) Co-authored-by: Xtreak files: M Lib/unittest/test/testmock/testhelpers.py diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 7919482ae99c..9edebf551660 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -8,6 +8,7 @@ ) from datetime import datetime +from functools import partial class SomeClass(object): def one(self, a, b): @@ -871,6 +872,19 @@ def test_autospec_on_bound_builtin_function(self): mocked.assert_called_once_with(4, 5, 6) + def test_autospec_getattr_partial_function(self): + # bpo-32153 : getattr returning partial functions without + # __name__ should not create AttributeError in create_autospec + class Foo: + + def __getattr__(self, attribute): + return partial(lambda name: name, attribute) + + proxy = Foo() + autospec = create_autospec(proxy) + self.assertFalse(hasattr(autospec, '__name__')) + + class TestCallList(unittest.TestCase): def test_args_list_contains_call_list(self): From webhook-mailer at python.org Mon Dec 3 03:31:38 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Mon, 03 Dec 2018 08:31:38 -0000 Subject: [Python-checkins] bpo-10320: Replace nonstandard sprintf() length modifier in ctypes' PyCArg_repr(). (GH-10853) Message-ID: https://github.com/python/cpython/commit/062cbb67726f26794b1b461853e40696b4a0b220 commit: 062cbb67726f26794b1b461853e40696b4a0b220 branch: master author: Zackery Spytz committer: Serhiy Storchaka date: 2018-12-03T10:31:35+02:00 summary: bpo-10320: Replace nonstandard sprintf() length modifier in ctypes' PyCArg_repr(). (GH-10853) Use "ll" instead of the nonstandard "q". files: M Modules/_ctypes/callproc.c diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index d485c58e8c01..ad40ca1c5247 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -484,7 +484,7 @@ PyCArg_repr(PyCArgObject *self) #ifdef MS_WIN32 "", #else - "", + "", #endif self->tag, self->value.q); break; From webhook-mailer at python.org Mon Dec 3 03:36:48 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Mon, 03 Dec 2018 08:36:48 -0000 Subject: [Python-checkins] bpo-35372: Fix the code page decoder for input > 2 GiB. (GH-10848) Message-ID: https://github.com/python/cpython/commit/4013c179117754b039957db4730880bf3285919d commit: 4013c179117754b039957db4730880bf3285919d branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-03T10:36:45+02:00 summary: bpo-35372: Fix the code page decoder for input > 2 GiB. (GH-10848) files: A Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst M Lib/test/test_codecs.py M Objects/unicodeobject.c diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index 8c92556e8429..79cddb8715b2 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -3190,6 +3190,24 @@ def test_mbcs_alias(self): codec = codecs.lookup('cp123') self.assertEqual(codec.name, 'mbcs') + @support.bigmemtest(size=2**31, memuse=7, dry_run=False) + def test_large_input(self): + # Test input longer than INT_MAX. + # Input should contain undecodable bytes before and after + # the INT_MAX limit. + encoded = (b'01234567' * (2**28-1) + + b'\x85\x86\xea\xeb\xec\xef\xfc\xfd\xfe\xff') + self.assertEqual(len(encoded), 2**31+2) + decoded = codecs.code_page_decode(932, encoded, 'surrogateescape', True) + self.assertEqual(decoded[1], len(encoded)) + del encoded + self.assertEqual(len(decoded[0]), decoded[1]) + self.assertEqual(decoded[0][:10], '0123456701') + self.assertEqual(decoded[0][-20:], + '6701234567' + '\udc85\udc86\udcea\udceb\udcec' + '\udcef\udcfc\udcfd\udcfe\udcff') + class ASCIITest(unittest.TestCase): def test_encode(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst new file mode 100644 index 000000000000..dc2de44b4f63 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst @@ -0,0 +1,2 @@ +Fixed the code page decoder for input longer than 2 GiB containing +undecodable bytes. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index bc98c44c7407..1351eece8e92 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -7211,7 +7211,7 @@ decode_code_page_errors(UINT code_page, "in the target code page."; /* each step cannot decode more than 1 character, but a character can be represented as a surrogate pair */ - wchar_t buffer[2], *startout, *out; + wchar_t buffer[2], *out; int insize; Py_ssize_t outsize; PyObject *errorHandler = NULL; @@ -7248,7 +7248,7 @@ decode_code_page_errors(UINT code_page, *v = (PyObject*)_PyUnicode_New(size * Py_ARRAY_LENGTH(buffer)); if (*v == NULL) goto error; - startout = PyUnicode_AS_UNICODE(*v); + out = PyUnicode_AS_UNICODE(*v); } else { /* Extend unicode object */ @@ -7259,11 +7259,10 @@ decode_code_page_errors(UINT code_page, } if (unicode_resize(v, n + size * Py_ARRAY_LENGTH(buffer)) < 0) goto error; - startout = PyUnicode_AS_UNICODE(*v) + n; + out = PyUnicode_AS_UNICODE(*v) + n; } /* Decode the byte string character per character */ - out = startout; while (in < endin) { /* Decode a character */ @@ -7318,7 +7317,7 @@ decode_code_page_errors(UINT code_page, *out = 0; /* Extend unicode object */ - outsize = out - startout; + outsize = out - PyUnicode_AS_UNICODE(*v); assert(outsize <= PyUnicode_WSTR_LENGTH(*v)); if (unicode_resize(v, outsize) < 0) goto error; From solipsis at pitrou.net Mon Dec 3 04:08:51 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Mon, 03 Dec 2018 09:08:51 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=5 Message-ID: <20181203090851.1.15BB8933AF7934A1@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_collections leaked [7, -7, 1] memory blocks, sum=1 test_functools leaked [0, 3, 1] memory blocks, sum=4 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogB9HhnK', '--timeout', '7200'] From webhook-mailer at python.org Mon Dec 3 04:09:18 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Mon, 03 Dec 2018 09:09:18 -0000 Subject: [Python-checkins] bpo-35372: Fix the code page decoder for input > 2 GiB. (GH-10848) Message-ID: https://github.com/python/cpython/commit/bdeb56cd21ef3f4f086c93045d80f2a753823379 commit: bdeb56cd21ef3f4f086c93045d80f2a753823379 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-03T01:09:11-08:00 summary: bpo-35372: Fix the code page decoder for input > 2 GiB. (GH-10848) (cherry picked from commit 4013c179117754b039957db4730880bf3285919d) Co-authored-by: Serhiy Storchaka files: A Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst M Lib/test/test_codecs.py M Objects/unicodeobject.c diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index a59a5e21358e..5c2de212b199 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -3185,6 +3185,24 @@ def test_mbcs_alias(self): codec = codecs.lookup('cp123') self.assertEqual(codec.name, 'mbcs') + @support.bigmemtest(size=2**31, memuse=7, dry_run=False) + def test_large_input(self): + # Test input longer than INT_MAX. + # Input should contain undecodable bytes before and after + # the INT_MAX limit. + encoded = (b'01234567' * (2**28-1) + + b'\x85\x86\xea\xeb\xec\xef\xfc\xfd\xfe\xff') + self.assertEqual(len(encoded), 2**31+2) + decoded = codecs.code_page_decode(932, encoded, 'surrogateescape', True) + self.assertEqual(decoded[1], len(encoded)) + del encoded + self.assertEqual(len(decoded[0]), decoded[1]) + self.assertEqual(decoded[0][:10], '0123456701') + self.assertEqual(decoded[0][-20:], + '6701234567' + '\udc85\udc86\udcea\udceb\udcec' + '\udcef\udcfc\udcfd\udcfe\udcff') + class ASCIITest(unittest.TestCase): def test_encode(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst new file mode 100644 index 000000000000..dc2de44b4f63 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst @@ -0,0 +1,2 @@ +Fixed the code page decoder for input longer than 2 GiB containing +undecodable bytes. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index bd3f151c6a50..d46ab2a1e2ab 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -7178,7 +7178,7 @@ decode_code_page_errors(UINT code_page, "in the target code page."; /* each step cannot decode more than 1 character, but a character can be represented as a surrogate pair */ - wchar_t buffer[2], *startout, *out; + wchar_t buffer[2], *out; int insize; Py_ssize_t outsize; PyObject *errorHandler = NULL; @@ -7215,7 +7215,7 @@ decode_code_page_errors(UINT code_page, *v = (PyObject*)_PyUnicode_New(size * Py_ARRAY_LENGTH(buffer)); if (*v == NULL) goto error; - startout = PyUnicode_AS_UNICODE(*v); + out = PyUnicode_AS_UNICODE(*v); } else { /* Extend unicode object */ @@ -7226,11 +7226,10 @@ decode_code_page_errors(UINT code_page, } if (unicode_resize(v, n + size * Py_ARRAY_LENGTH(buffer)) < 0) goto error; - startout = PyUnicode_AS_UNICODE(*v) + n; + out = PyUnicode_AS_UNICODE(*v) + n; } /* Decode the byte string character per character */ - out = startout; while (in < endin) { /* Decode a character */ @@ -7285,7 +7284,7 @@ decode_code_page_errors(UINT code_page, *out = 0; /* Extend unicode object */ - outsize = out - startout; + outsize = out - PyUnicode_AS_UNICODE(*v); assert(outsize <= PyUnicode_WSTR_LENGTH(*v)); if (unicode_resize(v, outsize) < 0) goto error; From webhook-mailer at python.org Mon Dec 3 04:11:34 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Mon, 03 Dec 2018 09:11:34 -0000 Subject: [Python-checkins] bpo-10320: Replace nonstandard sprintf() length modifier in ctypes' PyCArg_repr(). (GH-10853) Message-ID: https://github.com/python/cpython/commit/a9f435e5d856fb62516b70a78217e40b90bec233 commit: a9f435e5d856fb62516b70a78217e40b90bec233 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-03T01:11:30-08:00 summary: bpo-10320: Replace nonstandard sprintf() length modifier in ctypes' PyCArg_repr(). (GH-10853) Use "ll" instead of the nonstandard "q". (cherry picked from commit 062cbb67726f26794b1b461853e40696b4a0b220) Co-authored-by: Zackery Spytz files: M Modules/_ctypes/callproc.c diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index d485c58e8c01..ad40ca1c5247 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -484,7 +484,7 @@ PyCArg_repr(PyCArgObject *self) #ifdef MS_WIN32 "", #else - "", + "", #endif self->tag, self->value.q); break; From webhook-mailer at python.org Mon Dec 3 04:11:40 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Mon, 03 Dec 2018 09:11:40 -0000 Subject: [Python-checkins] bpo-10320: Replace nonstandard sprintf() length modifier in ctypes' PyCArg_repr(). (GH-10853) Message-ID: https://github.com/python/cpython/commit/f65ede3f8fa08493facf48177540d0ec26e59560 commit: f65ede3f8fa08493facf48177540d0ec26e59560 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-03T01:11:37-08:00 summary: bpo-10320: Replace nonstandard sprintf() length modifier in ctypes' PyCArg_repr(). (GH-10853) Use "ll" instead of the nonstandard "q". (cherry picked from commit 062cbb67726f26794b1b461853e40696b4a0b220) Co-authored-by: Zackery Spytz files: M Modules/_ctypes/callproc.c diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index e6079e9938ce..04fbc010ca7d 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -484,7 +484,7 @@ PyCArg_repr(PyCArgObject *self) #ifdef MS_WIN32 "", #else - "", + "", #endif self->tag, self->value.q); break; From webhook-mailer at python.org Mon Dec 3 04:15:06 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Mon, 03 Dec 2018 09:15:06 -0000 Subject: [Python-checkins] bpo-35372: Fix the code page decoder for input > 2 GiB. (GH-10848) Message-ID: https://github.com/python/cpython/commit/0f9b6687eb8b26dd804abcc6efd4d6430ae16f24 commit: 0f9b6687eb8b26dd804abcc6efd4d6430ae16f24 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-03T01:15:02-08:00 summary: bpo-35372: Fix the code page decoder for input > 2 GiB. (GH-10848) (cherry picked from commit 4013c179117754b039957db4730880bf3285919d) Co-authored-by: Serhiy Storchaka files: A Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst M Lib/test/test_codecs.py M Objects/unicodeobject.c diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index eb21a3915b93..56485de3f6e9 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -3191,6 +3191,24 @@ def _get_fake_codepage(*a): finally: _bootlocale.getpreferredencoding = old_getpreferredencoding + @support.bigmemtest(size=2**31, memuse=7, dry_run=False) + def test_large_input(self): + # Test input longer than INT_MAX. + # Input should contain undecodable bytes before and after + # the INT_MAX limit. + encoded = (b'01234567' * (2**28-1) + + b'\x85\x86\xea\xeb\xec\xef\xfc\xfd\xfe\xff') + self.assertEqual(len(encoded), 2**31+2) + decoded = codecs.code_page_decode(932, encoded, 'surrogateescape', True) + self.assertEqual(decoded[1], len(encoded)) + del encoded + self.assertEqual(len(decoded[0]), decoded[1]) + self.assertEqual(decoded[0][:10], '0123456701') + self.assertEqual(decoded[0][-20:], + '6701234567' + '\udc85\udc86\udcea\udceb\udcec' + '\udcef\udcfc\udcfd\udcfe\udcff') + class ASCIITest(unittest.TestCase): def test_encode(self): diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst new file mode 100644 index 000000000000..dc2de44b4f63 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-01-19-20-53.bpo-35372.RwVJjZ.rst @@ -0,0 +1,2 @@ +Fixed the code page decoder for input longer than 2 GiB containing +undecodable bytes. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 29b019887ac0..0e64bf943db3 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -7285,7 +7285,7 @@ decode_code_page_errors(UINT code_page, "in the target code page."; /* each step cannot decode more than 1 character, but a character can be represented as a surrogate pair */ - wchar_t buffer[2], *startout, *out; + wchar_t buffer[2], *out; int insize; Py_ssize_t outsize; PyObject *errorHandler = NULL; @@ -7322,7 +7322,7 @@ decode_code_page_errors(UINT code_page, *v = (PyObject*)_PyUnicode_New(size * Py_ARRAY_LENGTH(buffer)); if (*v == NULL) goto error; - startout = PyUnicode_AS_UNICODE(*v); + out = PyUnicode_AS_UNICODE(*v); } else { /* Extend unicode object */ @@ -7333,11 +7333,10 @@ decode_code_page_errors(UINT code_page, } if (unicode_resize(v, n + size * Py_ARRAY_LENGTH(buffer)) < 0) goto error; - startout = PyUnicode_AS_UNICODE(*v) + n; + out = PyUnicode_AS_UNICODE(*v) + n; } /* Decode the byte string character per character */ - out = startout; while (in < endin) { /* Decode a character */ @@ -7392,7 +7391,7 @@ decode_code_page_errors(UINT code_page, *out = 0; /* Extend unicode object */ - outsize = out - startout; + outsize = out - PyUnicode_AS_UNICODE(*v); assert(outsize <= PyUnicode_WSTR_LENGTH(*v)); if (unicode_resize(v, outsize) < 0) goto error; From webhook-mailer at python.org Mon Dec 3 06:02:48 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Mon, 03 Dec 2018 11:02:48 -0000 Subject: [Python-checkins] bpo-35373: Fix PyInit_timezone() if HAVE_DECL_TZNAME is defined (GH-10861) Message-ID: https://github.com/python/cpython/commit/ab6614969301b238fcc27f43923a0189a57a2a3c commit: ab6614969301b238fcc27f43923a0189a57a2a3c branch: master author: Victor Stinner committer: GitHub date: 2018-12-03T12:02:43+01:00 summary: bpo-35373: Fix PyInit_timezone() if HAVE_DECL_TZNAME is defined (GH-10861) If HAVE_DECL_TZNAME, PyInit_timezone() now returns -1 on error. files: M Modules/timemodule.c diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 188f1e6ef571..61041c90b87e 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1581,16 +1581,17 @@ PyInit_timezone(PyObject *m) PyModule_AddIntConstant(m, "daylight", daylight); otz0 = PyUnicode_DecodeLocale(tzname[0], "surrogateescape"); if (otz0 == NULL) { - return; + return -1; } otz1 = PyUnicode_DecodeLocale(tzname[1], "surrogateescape"); if (otz1 == NULL) { Py_DECREF(otz0); - return; + return -1; } PyObject *tzname_obj = Py_BuildValue("(NN)", otz0, otz1); - if (tzname_obj == NULL) - return; + if (tzname_obj == NULL) { + return -1; + } PyModule_AddObject(m, "tzname", tzname_obj); #else // !HAVE_DECL_TZNAME static const time_t YEAR = (365 * 24 + 6) * 3600; From webhook-mailer at python.org Mon Dec 3 06:29:32 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Mon, 03 Dec 2018 11:29:32 -0000 Subject: [Python-checkins] bpo-35368: Make PyMem_Malloc() thread-safe in debug mode (GH-10828) Message-ID: https://github.com/python/cpython/commit/c275be54411d425c90e7c679ddb5321ba458f61d commit: c275be54411d425c90e7c679ddb5321ba458f61d branch: 2.7 author: Victor Stinner committer: GitHub date: 2018-12-03T12:29:29+01:00 summary: bpo-35368: Make PyMem_Malloc() thread-safe in debug mode (GH-10828) When Python is compiled in debug mode, PyMem_Malloc() uses debug hooks, but it also uses pymalloc allocator instead of malloc(). Problem: pymalloc is not thread-safe, whereas PyMem_Malloc() is thread-safe in release mode (it's a thin wrapper to malloc() in this case). Modify the debug hook to use malloc() for PyMem_Malloc(). files: A Misc/NEWS.d/next/Core and Builtins/2018-11-30-17-50-28.bpo-35368.DNaDao.rst M Objects/obmalloc.c diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-11-30-17-50-28.bpo-35368.DNaDao.rst b/Misc/NEWS.d/next/Core and Builtins/2018-11-30-17-50-28.bpo-35368.DNaDao.rst new file mode 100644 index 000000000000..4bcf608fdd44 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-11-30-17-50-28.bpo-35368.DNaDao.rst @@ -0,0 +1 @@ +:c:func:`PyMem_Malloc` is now also thread-safe in debug mode. diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c index 9adcff7a27ef..0778c851faf0 100644 --- a/Objects/obmalloc.c +++ b/Objects/obmalloc.c @@ -1413,6 +1413,38 @@ pool_is_in_list(const poolp target, poolp list) #endif /* Py_DEBUG */ +static void * +_PyMem_Malloc(size_t nbytes) +{ + if (nbytes > (size_t)PY_SSIZE_T_MAX) { + return NULL; + } + if (nbytes == 0) { + nbytes = 1; + } + return malloc(nbytes); +} + +static void * +_PyMem_Realloc(void *p, size_t nbytes) +{ + if (nbytes > (size_t)PY_SSIZE_T_MAX) { + return NULL; + } + if (nbytes == 0) { + nbytes = 1; + } + return realloc(p, nbytes); +} + + +static void +_PyMem_Free(void *p) +{ + free(p); +} + + /* Let S = sizeof(size_t). The debug malloc asks for 4*S extra bytes and fills them with useful stuff, here calling the underlying malloc's result p: @@ -1479,7 +1511,7 @@ _PyObject_DebugCheckAddress(const void *p) /* generic debug memory api, with an "id" to identify the API in use */ void * -_PyObject_DebugMallocApi(char id, size_t nbytes) +_PyObject_DebugMallocApi(char api, size_t nbytes) { uchar *p; /* base address of malloc'ed block */ uchar *tail; /* p + 2*SST + nbytes == pointer to tail pad bytes */ @@ -1491,13 +1523,18 @@ _PyObject_DebugMallocApi(char id, size_t nbytes) /* overflow: can't represent total as a size_t */ return NULL; - p = (uchar *)PyObject_Malloc(total); + if (api == _PYMALLOC_OBJ_ID) { + p = (uchar *)PyObject_Malloc(total); + } + else { + p = (uchar *)_PyMem_Malloc(total); + } if (p == NULL) return NULL; - /* at p, write size (SST bytes), id (1 byte), pad (SST-1 bytes) */ + /* at p, write size (SST bytes), api (1 byte), pad (SST-1 bytes) */ write_size_t(p, nbytes); - p[SST] = (uchar)id; + p[SST] = (uchar)api; memset(p + SST + 1 , FORBIDDENBYTE, SST-1); if (nbytes > 0) @@ -1529,7 +1566,12 @@ _PyObject_DebugFreeApi(char api, void *p) nbytes += 4*SST; if (nbytes > 0) memset(q, DEADBYTE, nbytes); - PyObject_Free(q); + if (api == _PYMALLOC_OBJ_ID) { + PyObject_Free(q); + } + else { + _PyMem_Free(q); + } } void * @@ -1561,7 +1603,12 @@ _PyObject_DebugReallocApi(char api, void *p, size_t nbytes) * case we didn't get the chance to mark the old memory with DEADBYTE, * but we live with that. */ - q = (uchar *)PyObject_Realloc(q - 2*SST, total); + if (api == _PYMALLOC_OBJ_ID) { + q = (uchar *)PyObject_Realloc(q - 2*SST, total); + } + else { + q = (uchar *)_PyMem_Realloc(q - 2*SST, total); + } if (q == NULL) { if (nbytes <= original_nbytes) { /* bpo-31626: the memset() above expects that realloc never fails From webhook-mailer at python.org Mon Dec 3 07:45:42 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Mon, 03 Dec 2018 12:45:42 -0000 Subject: [Python-checkins] bpo-35373: Fix PyInit_time() error handling (GH-10865) Message-ID: https://github.com/python/cpython/commit/3bb150d8148e3cc08418077a58f43e064b9fde61 commit: 3bb150d8148e3cc08418077a58f43e064b9fde61 branch: master author: Victor Stinner committer: GitHub date: 2018-12-03T13:45:38+01:00 summary: bpo-35373: Fix PyInit_time() error handling (GH-10865) * PyInit_time() now returns NULL if an exception is raised. * Rename PyInit_timezone() to init_timezone(). "PyInit_" prefix is a special prefix for function initializing a module. init_timezone() doesn't initialize a module and the function is not exported. files: M Modules/timemodule.c diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 61041c90b87e..01bb430ece60 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1019,7 +1019,7 @@ of the timezone or altzone attributes on the time module."); #endif /* HAVE_MKTIME */ #ifdef HAVE_WORKING_TZSET -static int PyInit_timezone(PyObject *module); +static int init_timezone(PyObject *module); static PyObject * time_tzset(PyObject *self, PyObject *unused) @@ -1034,7 +1034,7 @@ time_tzset(PyObject *self, PyObject *unused) tzset(); /* Reset timezone, altzone, daylight and tzname */ - if (PyInit_timezone(m) < 0) { + if (init_timezone(m) < 0) { return NULL; } Py_DECREF(m); @@ -1549,7 +1549,7 @@ get_gmtoff(time_t t, struct tm *p) #endif // !HAVE_DECL_TZNAME static int -PyInit_timezone(PyObject *m) +init_timezone(PyObject *m) { assert(!PyErr_Occurred()); @@ -1745,7 +1745,7 @@ PyInit_time(void) return NULL; /* Set, or reset, module variables like time.timezone */ - if (PyInit_timezone(m) < 0) { + if (init_timezone(m) < 0) { return NULL; } @@ -1798,6 +1798,9 @@ PyInit_time(void) utc_string = tm.tm_zone; #endif + if (PyErr_Occurred()) { + return NULL; + } return m; } From webhook-mailer at python.org Mon Dec 3 10:49:35 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Mon, 03 Dec 2018 15:49:35 -0000 Subject: [Python-checkins] bpo-26544: Make platform.libc_ver() less slow (GH-10868) Message-ID: https://github.com/python/cpython/commit/8687bd86e6f138ef0699a1e9f3f9555765949b51 commit: 8687bd86e6f138ef0699a1e9f3f9555765949b51 branch: 2.7 author: Victor Stinner committer: GitHub date: 2018-12-03T16:49:24+01:00 summary: bpo-26544: Make platform.libc_ver() less slow (GH-10868) Coarse benchmark on Fedora 29: 1.6 sec => 0.1 sec. Co-Authored-By: Antoine Pitrou (cherry-picked from commit ba7c226095703f63c78b00e56f1db8d99ac3a54a) files: M Lib/platform.py diff --git a/Lib/platform.py b/Lib/platform.py index 62a5476a8f4d..e04d87f258a7 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -194,7 +194,10 @@ def libc_ver(executable=sys.executable,lib='',version='', chunksize=2048): binary = f.read(chunksize) pos = 0 while pos < len(binary): - m = _libc_search.search(binary,pos) + if 'libc' in binary or 'GLIBC' in binary: + m = _libc_search.search(binary, pos) + else: + m = None if not m or m.end() == len(binary): chunk = f.read(chunksize) if chunk: From webhook-mailer at python.org Mon Dec 3 14:08:16 2018 From: webhook-mailer at python.org (Andrew Svetlov) Date: Mon, 03 Dec 2018 19:08:16 -0000 Subject: [Python-checkins] bpo-35380: Enable TCP_NODELAY for proactor event loop (#10867) Message-ID: https://github.com/python/cpython/commit/3bc0ebab17bf5a2c29d2214743c82034f82e6573 commit: 3bc0ebab17bf5a2c29d2214743c82034f82e6573 branch: master author: Andrew Svetlov committer: GitHub date: 2018-12-03T21:08:13+02:00 summary: bpo-35380: Enable TCP_NODELAY for proactor event loop (#10867) files: A Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst M Lib/asyncio/base_events.py M Lib/asyncio/proactor_events.py M Lib/asyncio/selector_events.py M Lib/test/test_asyncio/test_base_events.py M Lib/test/test_asyncio/test_selector_events.py diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index f5ab6e7b2d21..60a189bdfb7e 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -168,6 +168,17 @@ def _run_until_complete_cb(fut): futures._get_loop(fut).stop() +if hasattr(socket, 'TCP_NODELAY'): + def _set_nodelay(sock): + if (sock.family in {socket.AF_INET, socket.AF_INET6} and + sock.type == socket.SOCK_STREAM and + sock.proto == socket.IPPROTO_TCP): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) +else: + def _set_nodelay(sock): + pass + + class _SendfileFallbackProtocol(protocols.Protocol): def __init__(self, transp): if not isinstance(transp, transports._FlowControlMixin): diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index ad23918802fa..69d96a81035e 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -13,7 +13,6 @@ from . import base_events from . import constants -from . import events from . import futures from . import exceptions from . import protocols @@ -445,6 +444,11 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport, _sendfile_compatible = constants._SendfileMode.TRY_NATIVE + def __init__(self, loop, sock, protocol, waiter=None, + extra=None, server=None): + super().__init__(loop, sock, protocol, waiter, extra, server) + base_events._set_nodelay(sock) + def _set_extra(self, sock): self._extra['socket'] = sock diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index ad093001dd9a..112c4b15b8d8 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -39,17 +39,6 @@ def _test_selector_event(selector, fd, event): return bool(key.events & event) -if hasattr(socket, 'TCP_NODELAY'): - def _set_nodelay(sock): - if (sock.family in {socket.AF_INET, socket.AF_INET6} and - sock.type == socket.SOCK_STREAM and - sock.proto == socket.IPPROTO_TCP): - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) -else: - def _set_nodelay(sock): - pass - - class BaseSelectorEventLoop(base_events.BaseEventLoop): """Selector event loop. @@ -742,7 +731,7 @@ def __init__(self, loop, sock, protocol, waiter=None, # Disable the Nagle algorithm -- small writes will be # sent without waiting for the TCP ACK. This generally # decreases the latency (in some cases significantly.) - _set_nodelay(self._sock) + base_events._set_nodelay(self._sock) self._loop.call_soon(self._protocol.connection_made, self) # only start reading when connection_made() has been called diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 6d544d1eda86..53854758a27d 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -2,7 +2,6 @@ import concurrent.futures import errno -import logging import math import os import socket @@ -15,7 +14,6 @@ import asyncio from asyncio import base_events from asyncio import constants -from asyncio import events from test.test_asyncio import utils as test_utils from test import support from test.support.script_helper import assert_python_ok @@ -288,7 +286,7 @@ def cb(): loop.set_debug(debug) if debug: msg = ("Non-thread-safe operation invoked on an event loop other " - "than the current one") + "than the current one") with self.assertRaisesRegex(RuntimeError, msg): loop.call_soon(cb) with self.assertRaisesRegex(RuntimeError, msg): @@ -2075,5 +2073,31 @@ def test_negative_offset(self): self.run_loop(self.loop.sock_sendfile(sock, self.file, -1)) +class TestSelectorUtils(test_utils.TestCase): + def check_set_nodelay(self, sock): + opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) + self.assertFalse(opt) + + base_events._set_nodelay(sock) + + opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) + self.assertTrue(opt) + + @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), + 'need socket.TCP_NODELAY') + def test_set_nodelay(self): + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + with sock: + self.check_set_nodelay(sock) + + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + with sock: + sock.setblocking(False) + self.check_set_nodelay(sock) + + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index b99e8e696070..d0d171a9853a 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -14,7 +14,6 @@ from asyncio.selector_events import _SelectorTransport from asyncio.selector_events import _SelectorSocketTransport from asyncio.selector_events import _SelectorDatagramTransport -from asyncio.selector_events import _set_nodelay from test.test_asyncio import utils as test_utils @@ -1344,30 +1343,5 @@ def test_fatal_error_connected(self, m_exc): exc_info=(ConnectionRefusedError, MOCK_ANY, MOCK_ANY)) -class TestSelectorUtils(test_utils.TestCase): - def check_set_nodelay(self, sock): - opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertFalse(opt) - - _set_nodelay(sock) - - opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertTrue(opt) - - @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), - 'need socket.TCP_NODELAY') - def test_set_nodelay(self): - sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, - proto=socket.IPPROTO_TCP) - with sock: - self.check_set_nodelay(sock) - - sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, - proto=socket.IPPROTO_TCP) - with sock: - sock.setblocking(False) - self.check_set_nodelay(sock) - - if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst b/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst new file mode 100644 index 000000000000..91f86e604ea8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst @@ -0,0 +1 @@ +Enable TCP_NODELAY on Windows for proactor asyncio event loop. From webhook-mailer at python.org Mon Dec 3 16:11:47 2018 From: webhook-mailer at python.org (Andrew Svetlov) Date: Mon, 03 Dec 2018 21:11:47 -0000 Subject: [Python-checkins] [3.7] bpo-35380: Enable TCP_NODELAY for proactor event loop (GH-10867) (GH-10872) Message-ID: https://github.com/python/cpython/commit/fe91e9ba08a8854e2149398386702828fe3c0038 commit: fe91e9ba08a8854e2149398386702828fe3c0038 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Andrew Svetlov date: 2018-12-03T23:11:41+02:00 summary: [3.7] bpo-35380: Enable TCP_NODELAY for proactor event loop (GH-10867) (GH-10872) * bpo-35380: Enable TCP_NODELAY for proactor event loop (GH-10867) (cherry picked from commit 3bc0ebab17bf5a2c29d2214743c82034f82e6573) Co-authored-by: Andrew Svetlov files: A Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst M Lib/asyncio/base_events.py M Lib/asyncio/proactor_events.py M Lib/asyncio/selector_events.py M Lib/test/test_asyncio/test_base_events.py M Lib/test/test_asyncio/test_selector_events.py diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index b3b07554ec19..65e0529e1d90 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -165,6 +165,17 @@ def _run_until_complete_cb(fut): futures._get_loop(fut).stop() +if hasattr(socket, 'TCP_NODELAY'): + def _set_nodelay(sock): + if (sock.family in {socket.AF_INET, socket.AF_INET6} and + sock.type == socket.SOCK_STREAM and + sock.proto == socket.IPPROTO_TCP): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) +else: + def _set_nodelay(sock): + pass + + class _SendfileFallbackProtocol(protocols.Protocol): def __init__(self, transp): if not isinstance(transp, transports._FlowControlMixin): diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 66bfb0ab11ee..e350e8bc0c24 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -444,6 +444,11 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport, _sendfile_compatible = constants._SendfileMode.TRY_NATIVE + def __init__(self, loop, sock, protocol, waiter=None, + extra=None, server=None): + super().__init__(loop, sock, protocol, waiter, extra, server) + base_events._set_nodelay(sock) + def _set_extra(self, sock): self._extra['socket'] = sock diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 116c08d6ff7f..d2861d34332a 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -39,17 +39,6 @@ def _test_selector_event(selector, fd, event): return bool(key.events & event) -if hasattr(socket, 'TCP_NODELAY'): - def _set_nodelay(sock): - if (sock.family in {socket.AF_INET, socket.AF_INET6} and - sock.type == socket.SOCK_STREAM and - sock.proto == socket.IPPROTO_TCP): - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) -else: - def _set_nodelay(sock): - pass - - class BaseSelectorEventLoop(base_events.BaseEventLoop): """Selector event loop. @@ -733,7 +722,7 @@ def __init__(self, loop, sock, protocol, waiter=None, # Disable the Nagle algorithm -- small writes will be # sent without waiting for the TCP ACK. This generally # decreases the latency (in some cases significantly.) - _set_nodelay(self._sock) + base_events._set_nodelay(self._sock) self._loop.call_soon(self._protocol.connection_made, self) # only start reading when connection_made() has been called diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 59d321e1c691..559ed3ec5279 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -272,7 +272,7 @@ def cb(): loop.set_debug(debug) if debug: msg = ("Non-thread-safe operation invoked on an event loop other " - "than the current one") + "than the current one") with self.assertRaisesRegex(RuntimeError, msg): loop.call_soon(cb) with self.assertRaisesRegex(RuntimeError, msg): @@ -2056,5 +2056,31 @@ def test_negative_offset(self): self.run_loop(self.loop.sock_sendfile(sock, self.file, -1)) +class TestSelectorUtils(test_utils.TestCase): + def check_set_nodelay(self, sock): + opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) + self.assertFalse(opt) + + base_events._set_nodelay(sock) + + opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) + self.assertTrue(opt) + + @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), + 'need socket.TCP_NODELAY') + def test_set_nodelay(self): + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + with sock: + self.check_set_nodelay(sock) + + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + with sock: + sock.setblocking(False) + self.check_set_nodelay(sock) + + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index 68b6ee9abbf1..e94af28bade3 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -15,7 +15,6 @@ from asyncio.selector_events import _SelectorTransport from asyncio.selector_events import _SelectorSocketTransport from asyncio.selector_events import _SelectorDatagramTransport -from asyncio.selector_events import _set_nodelay from test.test_asyncio import utils as test_utils @@ -1746,30 +1745,5 @@ def test_fatal_error_connected(self, m_exc): exc_info=(ConnectionRefusedError, MOCK_ANY, MOCK_ANY)) -class TestSelectorUtils(test_utils.TestCase): - def check_set_nodelay(self, sock): - opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertFalse(opt) - - _set_nodelay(sock) - - opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertTrue(opt) - - @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), - 'need socket.TCP_NODELAY') - def test_set_nodelay(self): - sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, - proto=socket.IPPROTO_TCP) - with sock: - self.check_set_nodelay(sock) - - sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, - proto=socket.IPPROTO_TCP) - with sock: - sock.setblocking(False) - self.check_set_nodelay(sock) - - if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst b/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst new file mode 100644 index 000000000000..91f86e604ea8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst @@ -0,0 +1 @@ +Enable TCP_NODELAY on Windows for proactor asyncio event loop. From webhook-mailer at python.org Mon Dec 3 16:31:41 2018 From: webhook-mailer at python.org (Chris Withers) Date: Mon, 03 Dec 2018 21:31:41 -0000 Subject: [Python-checkins] bpo-35226: Fix equality for nested unittest.mock.call objects. (#10555) Message-ID: https://github.com/python/cpython/commit/8ca0fa9d2f4de6e69f0902790432e0ab2f37ba68 commit: 8ca0fa9d2f4de6e69f0902790432e0ab2f37ba68 branch: master author: Chris Withers committer: GitHub date: 2018-12-03T21:31:37Z summary: bpo-35226: Fix equality for nested unittest.mock.call objects. (#10555) Also refactor the call recording imolementation and add some notes about its limitations. files: A Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst M Doc/library/unittest.mock-examples.rst M Doc/library/unittest.mock.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testhelpers.py M Lib/unittest/test/testmock/testmock.py diff --git a/Doc/library/unittest.mock-examples.rst b/Doc/library/unittest.mock-examples.rst index 60db4c2ba4da..16690f349822 100644 --- a/Doc/library/unittest.mock-examples.rst +++ b/Doc/library/unittest.mock-examples.rst @@ -166,6 +166,15 @@ You use the :data:`call` object to construct lists for comparing with >>> mock.mock_calls == expected True +However, parameters to calls that return mocks are not recorded, which means it is not +possible to track nested calls where the parameters used to create ancestors are important: + + >>> m = Mock() + >>> m.factory(important=True).deliver() + + >>> m.mock_calls[-1] == call.factory(important=False).deliver() + True + Setting Return Values and Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 0ae29546586a..bfab00eb7514 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -702,6 +702,19 @@ the *new_callable* argument to :func:`patch`. unpacked as tuples to get at the individual arguments. See :ref:`calls as tuples `. + .. note:: + + The way :attr:`mock_calls` are recorded means that where nested + calls are made, the parameters of ancestor calls are not recorded + and so will always compare equal: + + >>> mock = MagicMock() + >>> mock.top(a=3).bottom() + + >>> mock.mock_calls + [call.top(a=3), call.top().bottom()] + >>> mock.mock_calls[-1] == call.top(a=-1).bottom() + True .. attribute:: __class__ diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 9547b1a1fada..d13f74982086 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -977,46 +977,51 @@ def _mock_call(_mock_self, *args, **kwargs): self = _mock_self self.called = True self.call_count += 1 - _new_name = self._mock_new_name - _new_parent = self._mock_new_parent + # handle call_args _call = _Call((args, kwargs), two=True) self.call_args = _call self.call_args_list.append(_call) - self.mock_calls.append(_Call(('', args, kwargs))) seen = set() - skip_next_dot = _new_name == '()' + + # initial stuff for method_calls: do_method_calls = self._mock_parent is not None - name = self._mock_name - while _new_parent is not None: - this_mock_call = _Call((_new_name, args, kwargs)) - if _new_parent._mock_new_name: - dot = '.' - if skip_next_dot: - dot = '' + method_call_name = self._mock_name - skip_next_dot = False - if _new_parent._mock_new_name == '()': - skip_next_dot = True + # initial stuff for mock_calls: + mock_call_name = self._mock_new_name + is_a_call = mock_call_name == '()' + self.mock_calls.append(_Call(('', args, kwargs))) - _new_name = _new_parent._mock_new_name + dot + _new_name + # follow up the chain of mocks: + _new_parent = self._mock_new_parent + while _new_parent is not None: + # handle method_calls: if do_method_calls: - if _new_name == name: - this_method_call = this_mock_call - else: - this_method_call = _Call((name, args, kwargs)) - _new_parent.method_calls.append(this_method_call) - + _new_parent.method_calls.append(_Call((method_call_name, args, kwargs))) do_method_calls = _new_parent._mock_parent is not None if do_method_calls: - name = _new_parent._mock_name + '.' + name + method_call_name = _new_parent._mock_name + '.' + method_call_name + # handle mock_calls: + this_mock_call = _Call((mock_call_name, args, kwargs)) _new_parent.mock_calls.append(this_mock_call) + + if _new_parent._mock_new_name: + if is_a_call: + dot = '' + else: + dot = '.' + is_a_call = _new_parent._mock_new_name == '()' + mock_call_name = _new_parent._mock_new_name + dot + mock_call_name + + # follow the parental chain: _new_parent = _new_parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks + # check we're not in an infinite loop: + # ( use ids here so as not to call __hash__ on the mocks) _new_parent_id = id(_new_parent) if _new_parent_id in seen: break @@ -2054,6 +2059,10 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self + if (getattr(self, 'parent', None) and getattr(other, 'parent', None) + and self.parent != other.parent): + return False + other_name = '' if len_other == 0: other_args, other_kwargs = (), {} diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 9edebf551660..9388311e3e25 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -270,6 +270,22 @@ def test_extended_call(self): self.assertEqual(mock.mock_calls, last_call.call_list()) + def test_extended_not_equal(self): + a = call(x=1).foo + b = call(x=2).foo + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_nested_calls_not_equal(self): + a = call(x=1).foo().bar + b = call(x=2).foo().bar + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + def test_call_list(self): mock = MagicMock() mock(1) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index ac6eea3720b8..e46ef7bd5f0f 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -925,6 +925,57 @@ def test_mock_calls(self): call().__int__().call_list()) + def test_child_mock_call_equal(self): + m = Mock() + result = m() + result.wibble() + # parent looks like this: + self.assertEqual(m.mock_calls, [call(), call().wibble()]) + # but child should look like this: + self.assertEqual(result.mock_calls, [call.wibble()]) + + + def test_mock_call_not_equal_leaf(self): + m = Mock() + m.foo().something() + self.assertNotEqual(m.mock_calls[1], call.foo().different()) + self.assertEqual(m.mock_calls[0], call.foo()) + + + def test_mock_call_not_equal_non_leaf(self): + m = Mock() + m.foo().bar() + self.assertNotEqual(m.mock_calls[1], call.baz().bar()) + self.assertNotEqual(m.mock_calls[0], call.baz()) + + + def test_mock_call_not_equal_non_leaf_params_different(self): + m = Mock() + m.foo(x=1).bar() + # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: + self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) + + + def test_mock_call_not_equal_non_leaf_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.baz.bar()) + + + def test_mock_call_not_equal_non_leaf_call_versus_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.foo().bar()) + + + def test_mock_call_repr(self): + m = Mock() + m.foo().bar().baz.bob() + self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') + self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') + self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + + def test_subclassing(self): class Subclass(Mock): pass diff --git a/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst b/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst new file mode 100644 index 000000000000..b95cc979573e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst @@ -0,0 +1,3 @@ +Recursively check arguments when testing for equality of +:class:`unittest.mock.call` objects and add note that tracking of parameters +used to create ancestors of mocks in ``mock_calls`` is not possible. From webhook-mailer at python.org Mon Dec 3 16:54:26 2018 From: webhook-mailer at python.org (Chris Withers) Date: Mon, 03 Dec 2018 21:54:26 -0000 Subject: [Python-checkins] bpo-35226: Fix equality for nested unittest.mock.call objects. (GH-10555) Message-ID: https://github.com/python/cpython/commit/67e6136a6d5c07141d4dba820c450a70db7aedd5 commit: 67e6136a6d5c07141d4dba820c450a70db7aedd5 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Chris Withers date: 2018-12-03T21:54:22Z summary: bpo-35226: Fix equality for nested unittest.mock.call objects. (GH-10555) Also refactor the call recording implementation and add some notes about its limitations. (cherry picked from commit 8ca0fa9d2f4de6e69f0902790432e0ab2f37ba68) Co-authored-by: Chris Withers files: A Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst M Doc/library/unittest.mock-examples.rst M Doc/library/unittest.mock.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testhelpers.py M Lib/unittest/test/testmock/testmock.py diff --git a/Doc/library/unittest.mock-examples.rst b/Doc/library/unittest.mock-examples.rst index 05f33740d752..cfba72795cc6 100644 --- a/Doc/library/unittest.mock-examples.rst +++ b/Doc/library/unittest.mock-examples.rst @@ -153,6 +153,15 @@ You use the :data:`call` object to construct lists for comparing with >>> mock.mock_calls == expected True +However, parameters to calls that return mocks are not recorded, which means it is not +possible to track nested calls where the parameters used to create ancestors are important: + + >>> m = Mock() + >>> m.factory(important=True).deliver() + + >>> m.mock_calls[-1] == call.factory(important=False).deliver() + True + Setting Return Values and Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 6841ef8ecc2b..01f5a6454cf7 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -680,6 +680,19 @@ the *new_callable* argument to :func:`patch`. unpacked as tuples to get at the individual arguments. See :ref:`calls as tuples `. + .. note:: + + The way :attr:`mock_calls` are recorded means that where nested + calls are made, the parameters of ancestor calls are not recorded + and so will always compare equal: + + >>> mock = MagicMock() + >>> mock.top(a=3).bottom() + + >>> mock.mock_calls + [call.top(a=3), call.top().bottom()] + >>> mock.mock_calls[-1] == call.top(a=-1).bottom() + True .. attribute:: __class__ diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 4bb0c32c745b..a8f28cefc7a9 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -943,46 +943,51 @@ def _mock_call(_mock_self, *args, **kwargs): self = _mock_self self.called = True self.call_count += 1 - _new_name = self._mock_new_name - _new_parent = self._mock_new_parent + # handle call_args _call = _Call((args, kwargs), two=True) self.call_args = _call self.call_args_list.append(_call) - self.mock_calls.append(_Call(('', args, kwargs))) seen = set() - skip_next_dot = _new_name == '()' + + # initial stuff for method_calls: do_method_calls = self._mock_parent is not None - name = self._mock_name - while _new_parent is not None: - this_mock_call = _Call((_new_name, args, kwargs)) - if _new_parent._mock_new_name: - dot = '.' - if skip_next_dot: - dot = '' + method_call_name = self._mock_name - skip_next_dot = False - if _new_parent._mock_new_name == '()': - skip_next_dot = True + # initial stuff for mock_calls: + mock_call_name = self._mock_new_name + is_a_call = mock_call_name == '()' + self.mock_calls.append(_Call(('', args, kwargs))) - _new_name = _new_parent._mock_new_name + dot + _new_name + # follow up the chain of mocks: + _new_parent = self._mock_new_parent + while _new_parent is not None: + # handle method_calls: if do_method_calls: - if _new_name == name: - this_method_call = this_mock_call - else: - this_method_call = _Call((name, args, kwargs)) - _new_parent.method_calls.append(this_method_call) - + _new_parent.method_calls.append(_Call((method_call_name, args, kwargs))) do_method_calls = _new_parent._mock_parent is not None if do_method_calls: - name = _new_parent._mock_name + '.' + name + method_call_name = _new_parent._mock_name + '.' + method_call_name + # handle mock_calls: + this_mock_call = _Call((mock_call_name, args, kwargs)) _new_parent.mock_calls.append(this_mock_call) + + if _new_parent._mock_new_name: + if is_a_call: + dot = '' + else: + dot = '.' + is_a_call = _new_parent._mock_new_name == '()' + mock_call_name = _new_parent._mock_new_name + dot + mock_call_name + + # follow the parental chain: _new_parent = _new_parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks + # check we're not in an infinite loop: + # ( use ids here so as not to call __hash__ on the mocks) _new_parent_id = id(_new_parent) if _new_parent_id in seen: break @@ -2018,6 +2023,10 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self + if (getattr(self, 'parent', None) and getattr(other, 'parent', None) + and self.parent != other.parent): + return False + other_name = '' if len_other == 0: other_args, other_kwargs = (), {} diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 7919482ae99c..0a4289086040 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -269,6 +269,22 @@ def test_extended_call(self): self.assertEqual(mock.mock_calls, last_call.call_list()) + def test_extended_not_equal(self): + a = call(x=1).foo + b = call(x=2).foo + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_nested_calls_not_equal(self): + a = call(x=1).foo().bar + b = call(x=2).foo().bar + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + def test_call_list(self): mock = MagicMock() mock(1) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index 4601136eff9c..c5c8f5ccbd81 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -916,6 +916,57 @@ def test_mock_calls(self): call().__int__().call_list()) + def test_child_mock_call_equal(self): + m = Mock() + result = m() + result.wibble() + # parent looks like this: + self.assertEqual(m.mock_calls, [call(), call().wibble()]) + # but child should look like this: + self.assertEqual(result.mock_calls, [call.wibble()]) + + + def test_mock_call_not_equal_leaf(self): + m = Mock() + m.foo().something() + self.assertNotEqual(m.mock_calls[1], call.foo().different()) + self.assertEqual(m.mock_calls[0], call.foo()) + + + def test_mock_call_not_equal_non_leaf(self): + m = Mock() + m.foo().bar() + self.assertNotEqual(m.mock_calls[1], call.baz().bar()) + self.assertNotEqual(m.mock_calls[0], call.baz()) + + + def test_mock_call_not_equal_non_leaf_params_different(self): + m = Mock() + m.foo(x=1).bar() + # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: + self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) + + + def test_mock_call_not_equal_non_leaf_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.baz.bar()) + + + def test_mock_call_not_equal_non_leaf_call_versus_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.foo().bar()) + + + def test_mock_call_repr(self): + m = Mock() + m.foo().bar().baz.bob() + self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') + self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') + self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + + def test_subclassing(self): class Subclass(Mock): pass diff --git a/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst b/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst new file mode 100644 index 000000000000..b95cc979573e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst @@ -0,0 +1,3 @@ +Recursively check arguments when testing for equality of +:class:`unittest.mock.call` objects and add note that tracking of parameters +used to create ancestors of mocks in ``mock_calls`` is not possible. From webhook-mailer at python.org Mon Dec 3 16:54:48 2018 From: webhook-mailer at python.org (Chris Withers) Date: Mon, 03 Dec 2018 21:54:48 -0000 Subject: [Python-checkins] bpo-35226: Fix equality for nested unittest.mock.call objects. (GH-10555) Message-ID: https://github.com/python/cpython/commit/e8f9e4785caeef8a68bb7859280e91a4cb424b79 commit: e8f9e4785caeef8a68bb7859280e91a4cb424b79 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Chris Withers date: 2018-12-03T21:54:44Z summary: bpo-35226: Fix equality for nested unittest.mock.call objects. (GH-10555) Also refactor the call recording imolementation and add some notes about its limitations. (cherry picked from commit 8ca0fa9d2f4de6e69f0902790432e0ab2f37ba68) Co-authored-by: Chris Withers files: A Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst M Doc/library/unittest.mock-examples.rst M Doc/library/unittest.mock.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testhelpers.py M Lib/unittest/test/testmock/testmock.py diff --git a/Doc/library/unittest.mock-examples.rst b/Doc/library/unittest.mock-examples.rst index 65dee7c0eb2d..00b9bc166a7f 100644 --- a/Doc/library/unittest.mock-examples.rst +++ b/Doc/library/unittest.mock-examples.rst @@ -153,6 +153,15 @@ You use the :data:`call` object to construct lists for comparing with >>> mock.mock_calls == expected True +However, parameters to calls that return mocks are not recorded, which means it is not +possible to track nested calls where the parameters used to create ancestors are important: + + >>> m = Mock() + >>> m.factory(important=True).deliver() + + >>> m.mock_calls[-1] == call.factory(important=False).deliver() + True + Setting Return Values and Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index 91633b505f56..f1b25958a670 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -680,6 +680,19 @@ the *new_callable* argument to :func:`patch`. unpacked as tuples to get at the individual arguments. See :ref:`calls as tuples `. + .. note:: + + The way :attr:`mock_calls` are recorded means that where nested + calls are made, the parameters of ancestor calls are not recorded + and so will always compare equal: + + >>> mock = MagicMock() + >>> mock.top(a=3).bottom() + + >>> mock.mock_calls + [call.top(a=3), call.top().bottom()] + >>> mock.mock_calls[-1] == call.top(a=-1).bottom() + True .. attribute:: __class__ diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 6ba186fb6631..69a08ece919b 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -955,46 +955,51 @@ def _mock_call(_mock_self, *args, **kwargs): self = _mock_self self.called = True self.call_count += 1 - _new_name = self._mock_new_name - _new_parent = self._mock_new_parent + # handle call_args _call = _Call((args, kwargs), two=True) self.call_args = _call self.call_args_list.append(_call) - self.mock_calls.append(_Call(('', args, kwargs))) seen = set() - skip_next_dot = _new_name == '()' + + # initial stuff for method_calls: do_method_calls = self._mock_parent is not None - name = self._mock_name - while _new_parent is not None: - this_mock_call = _Call((_new_name, args, kwargs)) - if _new_parent._mock_new_name: - dot = '.' - if skip_next_dot: - dot = '' + method_call_name = self._mock_name - skip_next_dot = False - if _new_parent._mock_new_name == '()': - skip_next_dot = True + # initial stuff for mock_calls: + mock_call_name = self._mock_new_name + is_a_call = mock_call_name == '()' + self.mock_calls.append(_Call(('', args, kwargs))) - _new_name = _new_parent._mock_new_name + dot + _new_name + # follow up the chain of mocks: + _new_parent = self._mock_new_parent + while _new_parent is not None: + # handle method_calls: if do_method_calls: - if _new_name == name: - this_method_call = this_mock_call - else: - this_method_call = _Call((name, args, kwargs)) - _new_parent.method_calls.append(this_method_call) - + _new_parent.method_calls.append(_Call((method_call_name, args, kwargs))) do_method_calls = _new_parent._mock_parent is not None if do_method_calls: - name = _new_parent._mock_name + '.' + name + method_call_name = _new_parent._mock_name + '.' + method_call_name + # handle mock_calls: + this_mock_call = _Call((mock_call_name, args, kwargs)) _new_parent.mock_calls.append(this_mock_call) + + if _new_parent._mock_new_name: + if is_a_call: + dot = '' + else: + dot = '.' + is_a_call = _new_parent._mock_new_name == '()' + mock_call_name = _new_parent._mock_new_name + dot + mock_call_name + + # follow the parental chain: _new_parent = _new_parent._mock_new_parent - # use ids here so as not to call __hash__ on the mocks + # check we're not in an infinite loop: + # ( use ids here so as not to call __hash__ on the mocks) _new_parent_id = id(_new_parent) if _new_parent_id in seen: break @@ -2030,6 +2035,10 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self + if (getattr(self, 'parent', None) and getattr(other, 'parent', None) + and self.parent != other.parent): + return False + other_name = '' if len_other == 0: other_args, other_kwargs = (), {} diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index 9edebf551660..9388311e3e25 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -270,6 +270,22 @@ def test_extended_call(self): self.assertEqual(mock.mock_calls, last_call.call_list()) + def test_extended_not_equal(self): + a = call(x=1).foo + b = call(x=2).foo + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + + def test_nested_calls_not_equal(self): + a = call(x=1).foo().bar + b = call(x=2).foo().bar + self.assertEqual(a, a) + self.assertEqual(b, b) + self.assertNotEqual(a, b) + + def test_call_list(self): mock = MagicMock() mock(1) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index cc45f02409ef..fa2ac34fc3d1 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -916,6 +916,57 @@ def test_mock_calls(self): call().__int__().call_list()) + def test_child_mock_call_equal(self): + m = Mock() + result = m() + result.wibble() + # parent looks like this: + self.assertEqual(m.mock_calls, [call(), call().wibble()]) + # but child should look like this: + self.assertEqual(result.mock_calls, [call.wibble()]) + + + def test_mock_call_not_equal_leaf(self): + m = Mock() + m.foo().something() + self.assertNotEqual(m.mock_calls[1], call.foo().different()) + self.assertEqual(m.mock_calls[0], call.foo()) + + + def test_mock_call_not_equal_non_leaf(self): + m = Mock() + m.foo().bar() + self.assertNotEqual(m.mock_calls[1], call.baz().bar()) + self.assertNotEqual(m.mock_calls[0], call.baz()) + + + def test_mock_call_not_equal_non_leaf_params_different(self): + m = Mock() + m.foo(x=1).bar() + # This isn't ideal, but there's no way to fix it without breaking backwards compatibility: + self.assertEqual(m.mock_calls[1], call.foo(x=2).bar()) + + + def test_mock_call_not_equal_non_leaf_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.baz.bar()) + + + def test_mock_call_not_equal_non_leaf_call_versus_attr(self): + m = Mock() + m.foo.bar() + self.assertNotEqual(m.mock_calls[0], call.foo().bar()) + + + def test_mock_call_repr(self): + m = Mock() + m.foo().bar().baz.bob() + self.assertEqual(repr(m.mock_calls[0]), 'call.foo()') + self.assertEqual(repr(m.mock_calls[1]), 'call.foo().bar()') + self.assertEqual(repr(m.mock_calls[2]), 'call.foo().bar().baz.bob()') + + def test_subclassing(self): class Subclass(Mock): pass diff --git a/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst b/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst new file mode 100644 index 000000000000..b95cc979573e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-15-07-14-32.bpo-35226.wJPEEe.rst @@ -0,0 +1,3 @@ +Recursively check arguments when testing for equality of +:class:`unittest.mock.call` objects and add note that tracking of parameters +used to create ancestors of mocks in ``mock_calls`` is not possible. From webhook-mailer at python.org Mon Dec 3 18:09:07 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Mon, 03 Dec 2018 23:09:07 -0000 Subject: [Python-checkins] [3.7] bpo-35373: Fix PyInit_timezone() error handling (GH-10864) Message-ID: https://github.com/python/cpython/commit/5eb78c75128187a36d8e983027632fa51cc2ff4d commit: 5eb78c75128187a36d8e983027632fa51cc2ff4d branch: 3.7 author: Victor Stinner committer: GitHub date: 2018-12-04T00:09:02+01:00 summary: [3.7] bpo-35373: Fix PyInit_timezone() error handling (GH-10864) * bpo-35373: Fix PyInit_timezone() error handling PyInit_timezone() now returns -1 at exit if an exception is raised. Check also explicitly PyUnicode_DecodeLocale() and Py_BuildValue() errors. * bpo-35373: Fix PyInit_time() error handling (GH-10865) * PyInit_time() now returns NULL if an exception is raised. * Rename PyInit_timezone() to init_timezone(). "PyInit_" prefix is a special prefix for function initializing a module. init_timezone() doesn't initialize a module and the function is not exported. (cherry picked from commit 3bb150d8148e3cc08418077a58f43e064b9fde61) files: M Modules/timemodule.c diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 6cf9e7c641e3..13a174a4ea87 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -994,7 +994,7 @@ of the timezone or altzone attributes on the time module."); #endif /* HAVE_MKTIME */ #ifdef HAVE_WORKING_TZSET -static int PyInit_timezone(PyObject *module); +static int init_timezone(PyObject *module); static PyObject * time_tzset(PyObject *self, PyObject *unused) @@ -1009,7 +1009,7 @@ time_tzset(PyObject *self, PyObject *unused) tzset(); /* Reset timezone, altzone, daylight and tzname */ - if (PyInit_timezone(m) < 0) { + if (init_timezone(m) < 0) { return NULL; } Py_DECREF(m); @@ -1524,7 +1524,7 @@ get_gmtoff(time_t t, struct tm *p) #endif /* !defined(HAVE_TZNAME) || defined(__GLIBC__) || defined(__CYGWIN__) */ static int -PyInit_timezone(PyObject *m) +init_timezone(PyObject *m) { assert(!PyErr_Occurred()); @@ -1555,8 +1555,19 @@ PyInit_timezone(PyObject *m) #endif PyModule_AddIntConstant(m, "daylight", daylight); otz0 = PyUnicode_DecodeLocale(tzname[0], "surrogateescape"); + if (otz0 == NULL) { + return -1; + } otz1 = PyUnicode_DecodeLocale(tzname[1], "surrogateescape"); - PyModule_AddObject(m, "tzname", Py_BuildValue("(NN)", otz0, otz1)); + if (otz1 == NULL) { + Py_DECREF(otz0); + return -1; + } + PyObject *tzname_obj = Py_BuildValue("(NN)", otz0, otz1); + if (tzname_obj == NULL) { + return -1; + } + PyModule_AddObject(m, "tzname", tzname_obj); #else /* !HAVE_TZNAME || __GLIBC__ || __CYGWIN__*/ { #define YEAR ((time_t)((365 * 24 + 6) * 3600)) @@ -1615,6 +1626,10 @@ PyInit_timezone(PyObject *m) Py_BuildValue("(zz)", _tzname[0], _tzname[1])); #endif /* __CYGWIN__ */ #endif /* !HAVE_TZNAME || __GLIBC__ || __CYGWIN__*/ + + if (PyErr_Occurred()) { + return -1; + } return 0; } @@ -1716,7 +1731,7 @@ PyInit_time(void) return NULL; /* Set, or reset, module variables like time.timezone */ - if (PyInit_timezone(m) < 0) { + if (init_timezone(m) < 0) { return NULL; } @@ -1761,6 +1776,10 @@ PyInit_time(void) PyModule_AddIntConstant(m, "_STRUCT_TM_ITEMS", 11); PyModule_AddObject(m, "struct_time", (PyObject*) &StructTimeType); initialized = 1; + + if (PyErr_Occurred()) { + return NULL; + } return m; } From webhook-mailer at python.org Mon Dec 3 18:22:39 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Mon, 03 Dec 2018 23:22:39 -0000 Subject: [Python-checkins] [3.7] bpo-35373: Fix PyInit_timezone() error handling (GH-10864) Message-ID: https://github.com/python/cpython/commit/f455353bc0d195e092f09fec92ed16e9be02b7b1 commit: f455353bc0d195e092f09fec92ed16e9be02b7b1 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-03T15:22:35-08:00 summary: [3.7] bpo-35373: Fix PyInit_timezone() error handling (GH-10864) * bpo-35373: Fix PyInit_timezone() error handling PyInit_timezone() now returns -1 at exit if an exception is raised. Check also explicitly PyUnicode_DecodeLocale() and Py_BuildValue() errors. * bpo-35373: Fix PyInit_time() error handling (GH-10865) * PyInit_time() now returns NULL if an exception is raised. * Rename PyInit_timezone() to init_timezone(). "PyInit_" prefix is a special prefix for function initializing a module. init_timezone() doesn't initialize a module and the function is not exported. (cherry picked from commit 3bb150d8148e3cc08418077a58f43e064b9fde61) (cherry picked from commit 5eb78c75128187a36d8e983027632fa51cc2ff4d) Co-authored-by: Victor Stinner files: M Modules/timemodule.c diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 0bc32083e006..a963bb1dd154 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -860,7 +860,7 @@ of the timezone or altzone attributes on the time module."); #endif /* HAVE_MKTIME */ #ifdef HAVE_WORKING_TZSET -static int PyInit_timezone(PyObject *module); +static int init_timezone(PyObject *module); static PyObject * time_tzset(PyObject *self, PyObject *unused) @@ -875,7 +875,7 @@ time_tzset(PyObject *self, PyObject *unused) tzset(); /* Reset timezone, altzone, daylight and tzname */ - if (PyInit_timezone(m) < 0) { + if (init_timezone(m) < 0) { return NULL; } Py_DECREF(m); @@ -1187,7 +1187,7 @@ get_gmtoff(time_t t, struct tm *p) } static int -PyInit_timezone(PyObject *m) +init_timezone(PyObject *m) { assert(!PyErr_Occurred()); @@ -1218,8 +1218,19 @@ PyInit_timezone(PyObject *m) #endif PyModule_AddIntConstant(m, "daylight", daylight); otz0 = PyUnicode_DecodeLocale(tzname[0], "surrogateescape"); + if (otz0 == NULL) { + return -1; + } otz1 = PyUnicode_DecodeLocale(tzname[1], "surrogateescape"); - PyModule_AddObject(m, "tzname", Py_BuildValue("(NN)", otz0, otz1)); + if (otz1 == NULL) { + Py_DECREF(otz0); + return -1; + } + PyObject *tzname_obj = Py_BuildValue("(NN)", otz0, otz1); + if (tzname_obj == NULL) { + return -1; + } + PyModule_AddObject(m, "tzname", tzname_obj); #else /* !HAVE_TZNAME || __GLIBC__ || __CYGWIN__*/ { #define YEAR ((time_t)((365 * 24 + 6) * 3600)) @@ -1278,6 +1289,10 @@ PyInit_timezone(PyObject *m) Py_BuildValue("(zz)", _tzname[0], _tzname[1])); #endif /* __CYGWIN__ */ #endif /* !HAVE_TZNAME || __GLIBC__ || __CYGWIN__*/ + + if (PyErr_Occurred()) { + return -1; + } return 0; } @@ -1366,7 +1381,7 @@ PyInit_time(void) return NULL; /* Set, or reset, module variables like time.timezone */ - if (PyInit_timezone(m) < 0) { + if (init_timezone(m) < 0) { return NULL; } @@ -1402,6 +1417,10 @@ PyInit_time(void) PyModule_AddIntConstant(m, "_STRUCT_TM_ITEMS", 11); PyModule_AddObject(m, "struct_time", (PyObject*) &StructTimeType); initialized = 1; + + if (PyErr_Occurred()) { + return NULL; + } return m; } From webhook-mailer at python.org Tue Dec 4 02:31:20 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Tue, 04 Dec 2018 07:31:20 -0000 Subject: [Python-checkins] bpo-35395: fix typos in asyncio eventloop documentation (GH-10880) Message-ID: https://github.com/python/cpython/commit/17473347942353946fe455f797a2197cb89c1090 commit: 17473347942353946fe455f797a2197cb89c1090 branch: master author: Naglis committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-03T23:31:15-08:00 summary: bpo-35395: fix typos in asyncio eventloop documentation (GH-10880) Fixes `loop.add_writer` and `loop.add_signal_handler` method documentation to correctly reference the callback parameter from method signature. https://bugs.python.org/issue35395 files: M Doc/library/asyncio-eventloop.rst diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index d24413a4a7fb..da2339132a40 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -756,7 +756,7 @@ Watching file descriptors writing. Use :func:`functools.partial` :ref:`to pass keyword arguments - ` to *func*. + ` to *callback*. .. method:: loop.remove_writer(fd) @@ -970,7 +970,7 @@ Unix signals Raise :exc:`RuntimeError` if there is a problem setting up the handler. Use :func:`functools.partial` :ref:`to pass keyword arguments - ` to *func*. + ` to *callback*. .. method:: loop.remove_signal_handler(sig) From webhook-mailer at python.org Tue Dec 4 02:36:35 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Tue, 04 Dec 2018 07:36:35 -0000 Subject: [Python-checkins] bpo-35395: fix typos in asyncio eventloop documentation (GH-10880) Message-ID: https://github.com/python/cpython/commit/6627d3ae1e151095fda4ec2592c7cfb759f23669 commit: 6627d3ae1e151095fda4ec2592c7cfb759f23669 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-03T23:36:31-08:00 summary: bpo-35395: fix typos in asyncio eventloop documentation (GH-10880) Fixes `loop.add_writer` and `loop.add_signal_handler` method documentation to correctly reference the callback parameter from method signature. https://bugs.python.org/issue35395 (cherry picked from commit 17473347942353946fe455f797a2197cb89c1090) Co-authored-by: Naglis files: M Doc/library/asyncio-eventloop.rst diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 8215198ce91f..647b7fc5e7a5 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -750,7 +750,7 @@ Watching file descriptors writing. Use :func:`functools.partial` :ref:`to pass keyword arguments - ` to *func*. + ` to *callback*. .. method:: loop.remove_writer(fd) @@ -964,7 +964,7 @@ Unix signals Raise :exc:`RuntimeError` if there is a problem setting up the handler. Use :func:`functools.partial` :ref:`to pass keyword arguments - ` to *func*. + ` to *callback*. .. method:: loop.remove_signal_handler(sig) From webhook-mailer at python.org Tue Dec 4 03:13:43 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Tue, 04 Dec 2018 08:13:43 -0000 Subject: [Python-checkins] Add comments regarding speed/space/entropy trade-offs (GH-10885) Message-ID: https://github.com/python/cpython/commit/7fc633f5a56d9e672cd24133e2e1376347abac6c commit: 7fc633f5a56d9e672cd24133e2e1376347abac6c branch: master author: Raymond Hettinger committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-04T00:13:38-08:00 summary: Add comments regarding speed/space/entropy trade-offs (GH-10885) files: M Lib/random.py diff --git a/Lib/random.py b/Lib/random.py index 4b51b6696bfc..a7a86070c0a9 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -333,6 +333,19 @@ def sample(self, population, k): # preferred since the list takes less space than the # set and it doesn't suffer from frequent reselections. + # The number of calls to _randbelow() is kept at or near k, the + # theoretical minimum. This is important because running time + # is dominated by _randbelow() and because it extracts the + # least entropy from the underlying random number generators. + + # Memory requirements are kept to the smaller of a k-length + # set or an n-length list. + + # There are other sampling algorithms that do not require + # auxiliary memory, but they were rejected because they made + # too many calls to _randbelow(), making them slower and + # causing them to eat more entropy than necessary. + if isinstance(population, _Set): population = tuple(population) if not isinstance(population, _Sequence): From webhook-mailer at python.org Tue Dec 4 03:25:54 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Tue, 04 Dec 2018 08:25:54 -0000 Subject: [Python-checkins] bpo-35365: Use a wchar_t* buffer in the code page decoder. (GH-10837) Message-ID: https://github.com/python/cpython/commit/eeb719eac6347f5b6e85389aa13a386024766806 commit: eeb719eac6347f5b6e85389aa13a386024766806 branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-04T10:25:50+02:00 summary: bpo-35365: Use a wchar_t* buffer in the code page decoder. (GH-10837) files: M Objects/unicodeobject.c diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 1351eece8e92..d0f0358cfc69 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -4059,6 +4059,21 @@ make_decode_exception(PyObject **exceptionObject, } #ifdef MS_WINDOWS +static int +widechar_resize(wchar_t **buf, Py_ssize_t *size, Py_ssize_t newsize) +{ + if (newsize > *size) { + wchar_t *newbuf = *buf; + if (PyMem_Resize(newbuf, wchar_t, newsize) == NULL) { + PyErr_NoMemory(); + return -1; + } + *buf = newbuf; + } + *size = newsize; + return 0; +} + /* error handling callback helper: build arguments, call the callback and check the arguments, if no exception occurred, copy the replacement to the output @@ -4072,7 +4087,7 @@ unicode_decode_call_errorhandler_wchar( const char *encoding, const char *reason, const char **input, const char **inend, Py_ssize_t *startinpos, Py_ssize_t *endinpos, PyObject **exceptionObject, const char **inptr, - PyObject **output, Py_ssize_t *outpos) + wchar_t **buf, Py_ssize_t *bufsize, Py_ssize_t *outpos) { static const char *argparse = "Un;decoding error handler must return (str, int) tuple"; @@ -4086,9 +4101,6 @@ unicode_decode_call_errorhandler_wchar( wchar_t *repwstr; Py_ssize_t repwlen; - assert (_PyUnicode_KIND(*output) == PyUnicode_WCHAR_KIND); - outsize = _PyUnicode_WSTR_LENGTH(*output); - if (*errorHandler == NULL) { *errorHandler = PyCodec_LookupError(errors); if (*errorHandler == NULL) @@ -4146,13 +4158,15 @@ unicode_decode_call_errorhandler_wchar( if (requiredsize > PY_SSIZE_T_MAX - (insize - newpos)) goto overflow; requiredsize += insize - newpos; + outsize = *bufsize; if (requiredsize > outsize) { if (outsize <= PY_SSIZE_T_MAX/2 && requiredsize < 2*outsize) requiredsize = 2*outsize; - if (unicode_resize(output, requiredsize) < 0) + if (widechar_resize(buf, bufsize, requiredsize) < 0) { goto onError; + } } - wcsncpy(_PyUnicode_WSTR(*output) + *outpos, repwstr, repwlen); + wcsncpy(*buf + *outpos, repwstr, repwlen); *outpos += repwlen; *endinpos = newpos; *inptr = *input + newpos; @@ -7146,7 +7160,8 @@ decode_code_page_flags(UINT code_page) */ static int decode_code_page_strict(UINT code_page, - PyObject **v, + wchar_t **buf, + Py_ssize_t *bufsize, const char *in, int insize) { @@ -7160,21 +7175,12 @@ decode_code_page_strict(UINT code_page, if (outsize <= 0) goto error; - if (*v == NULL) { - /* Create unicode object */ - /* FIXME: don't use _PyUnicode_New(), but allocate a wchar_t* buffer */ - *v = (PyObject*)_PyUnicode_New(outsize); - if (*v == NULL) - return -1; - out = PyUnicode_AS_UNICODE(*v); - } - else { - /* Extend unicode object */ - Py_ssize_t n = PyUnicode_GET_SIZE(*v); - if (unicode_resize(v, n + outsize) < 0) - return -1; - out = PyUnicode_AS_UNICODE(*v) + n; + /* Extend a wchar_t* buffer */ + Py_ssize_t n = *bufsize; /* Get the current length */ + if (widechar_resize(buf, bufsize, n + outsize) < 0) { + return -1; } + out = *buf + n; /* Do the conversion */ outsize = MultiByteToWideChar(code_page, flags, in, insize, out, outsize); @@ -7198,7 +7204,8 @@ decode_code_page_strict(UINT code_page, */ static int decode_code_page_errors(UINT code_page, - PyObject **v, + wchar_t **buf, + Py_ssize_t *bufsize, const char *in, const int size, const char *errors, int final) { @@ -7238,29 +7245,16 @@ decode_code_page_errors(UINT code_page, goto error; } - if (*v == NULL) { - /* Create unicode object */ - if (size > PY_SSIZE_T_MAX / (Py_ssize_t)Py_ARRAY_LENGTH(buffer)) { - PyErr_NoMemory(); - goto error; - } - /* FIXME: don't use _PyUnicode_New(), but allocate a wchar_t* buffer */ - *v = (PyObject*)_PyUnicode_New(size * Py_ARRAY_LENGTH(buffer)); - if (*v == NULL) - goto error; - out = PyUnicode_AS_UNICODE(*v); + /* Extend a wchar_t* buffer */ + Py_ssize_t n = *bufsize; /* Get the current length */ + if (size > (PY_SSIZE_T_MAX - n) / (Py_ssize_t)Py_ARRAY_LENGTH(buffer)) { + PyErr_NoMemory(); + goto error; } - else { - /* Extend unicode object */ - Py_ssize_t n = PyUnicode_GET_SIZE(*v); - if (size > (PY_SSIZE_T_MAX - n) / (Py_ssize_t)Py_ARRAY_LENGTH(buffer)) { - PyErr_NoMemory(); - goto error; - } - if (unicode_resize(v, n + size * Py_ARRAY_LENGTH(buffer)) < 0) - goto error; - out = PyUnicode_AS_UNICODE(*v) + n; + if (widechar_resize(buf, bufsize, n + size * Py_ARRAY_LENGTH(buffer)) < 0) { + goto error; } + out = *buf + n; /* Decode the byte string character per character */ while (in < endin) @@ -7295,16 +7289,16 @@ decode_code_page_errors(UINT code_page, startinpos = in - startin; endinpos = startinpos + 1; - outpos = out - PyUnicode_AS_UNICODE(*v); + outpos = out - *buf; if (unicode_decode_call_errorhandler_wchar( errors, &errorHandler, encoding, reason, &startin, &endin, &startinpos, &endinpos, &exc, &in, - v, &outpos)) + buf, bufsize, &outpos)) { goto error; } - out = PyUnicode_AS_UNICODE(*v) + outpos; + out = *buf + outpos; } else { in += insize; @@ -7313,14 +7307,9 @@ decode_code_page_errors(UINT code_page, } } - /* write a NUL character at the end */ - *out = 0; - - /* Extend unicode object */ - outsize = out - PyUnicode_AS_UNICODE(*v); - assert(outsize <= PyUnicode_WSTR_LENGTH(*v)); - if (unicode_resize(v, outsize) < 0) - goto error; + /* Shrink the buffer */ + assert(out - *buf <= *bufsize); + *bufsize = out - *buf; /* (in - startin) <= size and size is an int */ ret = Py_SAFE_DOWNCAST(in - startin, Py_ssize_t, int); @@ -7336,7 +7325,8 @@ decode_code_page_stateful(int code_page, const char *s, Py_ssize_t size, const char *errors, Py_ssize_t *consumed) { - PyObject *v = NULL; + wchar_t *buf = NULL; + Py_ssize_t bufsize = 0; int chunk_size, final, converted, done; if (code_page < 0) { @@ -7368,21 +7358,21 @@ decode_code_page_stateful(int code_page, } if (chunk_size == 0 && done) { - if (v != NULL) + if (buf != NULL) break; _Py_RETURN_UNICODE_EMPTY(); } - converted = decode_code_page_strict(code_page, &v, + converted = decode_code_page_strict(code_page, &buf, &bufsize, s, chunk_size); if (converted == -2) - converted = decode_code_page_errors(code_page, &v, + converted = decode_code_page_errors(code_page, &buf, &bufsize, s, chunk_size, errors, final); assert(converted != 0 || done); if (converted < 0) { - Py_XDECREF(v); + PyMem_Free(buf); return NULL; } @@ -7393,7 +7383,9 @@ decode_code_page_stateful(int code_page, size -= converted; } while (!done); - return unicode_result(v); + PyObject *v = PyUnicode_FromWideChar(buf, bufsize); + PyMem_Free(buf); + return v; } PyObject * From webhook-mailer at python.org Tue Dec 4 04:08:52 2018 From: webhook-mailer at python.org (Chris Withers) Date: Tue, 04 Dec 2018 09:08:52 -0000 Subject: [Python-checkins] bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (#10873) Message-ID: https://github.com/python/cpython/commit/e63e617ebbe481c498bdf037a62e09f4f9f3963f commit: e63e617ebbe481c498bdf037a62e09f4f9f3963f branch: master author: Andrew Dunai committer: Chris Withers date: 2018-12-04T09:08:45Z summary: bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (#10873) Fix minor typo in test function name. files: A Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testcallable.py M Lib/unittest/test/testmock/testmock.py diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index d13f74982086..b75a1fa5cf1d 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2040,9 +2040,9 @@ def __new__(cls, value=(), name='', parent=None, two=False, def __init__(self, value=(), name=None, parent=None, two=False, from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall + self._mock_name = name + self._mock_parent = parent + self._mock_from_kall = from_kall def __eq__(self, other): @@ -2059,8 +2059,8 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self - if (getattr(self, 'parent', None) and getattr(other, 'parent', None) - and self.parent != other.parent): + if (getattr(self, '_mock_parent', None) and getattr(other, '_mock_parent', None) + and self._mock_parent != other._mock_parent): return False other_name = '' @@ -2104,17 +2104,17 @@ def __eq__(self, other): def __call__(self, *args, **kwargs): - if self.name is None: + if self._mock_name is None: return _Call(('', args, kwargs), name='()') - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) + name = self._mock_name + '()' + return _Call((self._mock_name, args, kwargs), name=name, parent=self) def __getattr__(self, attr): - if self.name is None: + if self._mock_name is None: return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) + name = '%s.%s' % (self._mock_name, attr) return _Call(name=name, parent=self, from_kall=False) @@ -2125,8 +2125,8 @@ def index(self, *args, **kwargs): return self.__getattr__('index')(*args, **kwargs) def __repr__(self): - if not self.from_kall: - name = self.name or 'call' + if not self._mock_from_kall: + name = self._mock_name or 'call' if name.startswith('()'): name = 'call%s' % name return name @@ -2152,9 +2152,9 @@ def call_list(self): vals = [] thing = self while thing is not None: - if thing.from_kall: + if thing._mock_from_kall: vals.append(thing) - thing = thing.parent + thing = thing._mock_parent return _CallList(reversed(vals)) diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py index af1ce7ebbae4..34474c4c816e 100644 --- a/Lib/unittest/test/testmock/testcallable.py +++ b/Lib/unittest/test/testmock/testcallable.py @@ -128,7 +128,7 @@ class Multi(SomeClass, Sub): result.foo.assert_called_once_with(3, 2, 1) - def test_create_autopsec(self): + def test_create_autospec(self): mock = create_autospec(X) instance = mock() self.assertRaises(TypeError, instance) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index e46ef7bd5f0f..0a638b7c2107 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -9,7 +9,7 @@ from unittest.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, _CallList, + NonCallableMagicMock, _Call, _CallList, create_autospec ) @@ -1665,6 +1665,20 @@ def test_class_assignable(self): self.assertIsInstance(mock, int) mock.foo + def test_name_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".name") + self.assertIsNotNone(call.name) + self.assertEqual(type(call.name), _Call) + self.assertEqual(type(call.name().name), _Call) + + def test_parent_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".parent") + self.assertIsNotNone(call.parent) + self.assertEqual(type(call.parent), _Call) + self.assertEqual(type(call.parent().parent), _Call) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst new file mode 100644 index 000000000000..1dade5baf4e0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst @@ -0,0 +1,4 @@ +Internal attributes' names of unittest.mock._Call and +unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with +_mock_ in order to prevent clashes with widely used object attributes. +Fixed minor typo in test function name. From solipsis at pitrou.net Tue Dec 4 04:16:12 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Tue, 04 Dec 2018 09:16:12 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=3 Message-ID: <20181204091612.1.87ED7302000F302E@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_collections leaked [-7, 8, 0] memory blocks, sum=1 test_functools leaked [0, 3, 1] memory blocks, sum=4 test_multiprocessing_fork leaked [-1, 2, -1] memory blocks, sum=0 test_multiprocessing_forkserver leaked [-2, 1, 0] memory blocks, sum=-1 test_multiprocessing_spawn leaked [1, 0, -2] memory blocks, sum=-1 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflog4bQT1M', '--timeout', '7200'] From webhook-mailer at python.org Tue Dec 4 04:34:39 2018 From: webhook-mailer at python.org (Chris Withers) Date: Tue, 04 Dec 2018 09:34:39 -0000 Subject: [Python-checkins] bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (GH-10873) (#10887) Message-ID: https://github.com/python/cpython/commit/12735c14134082584b899308af8dd8fcc9f15696 commit: 12735c14134082584b899308af8dd8fcc9f15696 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Chris Withers date: 2018-12-04T09:34:34Z summary: bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (GH-10873) (#10887) Fix minor typo in test function name. (cherry picked from commit e63e617ebbe481c498bdf037a62e09f4f9f3963f) Co-authored-by: Andrew Dunai files: A Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testcallable.py M Lib/unittest/test/testmock/testmock.py diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 69a08ece919b..0a6535d0fc44 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2016,9 +2016,9 @@ def __new__(cls, value=(), name='', parent=None, two=False, def __init__(self, value=(), name=None, parent=None, two=False, from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall + self._mock_name = name + self._mock_parent = parent + self._mock_from_kall = from_kall def __eq__(self, other): @@ -2035,8 +2035,8 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self - if (getattr(self, 'parent', None) and getattr(other, 'parent', None) - and self.parent != other.parent): + if (getattr(self, '_mock_parent', None) and getattr(other, '_mock_parent', None) + and self._mock_parent != other._mock_parent): return False other_name = '' @@ -2080,17 +2080,17 @@ def __eq__(self, other): def __call__(self, *args, **kwargs): - if self.name is None: + if self._mock_name is None: return _Call(('', args, kwargs), name='()') - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) + name = self._mock_name + '()' + return _Call((self._mock_name, args, kwargs), name=name, parent=self) def __getattr__(self, attr): - if self.name is None: + if self._mock_name is None: return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) + name = '%s.%s' % (self._mock_name, attr) return _Call(name=name, parent=self, from_kall=False) @@ -2101,8 +2101,8 @@ def index(self, *args, **kwargs): return self.__getattr__('index')(*args, **kwargs) def __repr__(self): - if not self.from_kall: - name = self.name or 'call' + if not self._mock_from_kall: + name = self._mock_name or 'call' if name.startswith('()'): name = 'call%s' % name return name @@ -2128,9 +2128,9 @@ def call_list(self): vals = [] thing = self while thing is not None: - if thing.from_kall: + if thing._mock_from_kall: vals.append(thing) - thing = thing.parent + thing = thing._mock_parent return _CallList(reversed(vals)) diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py index af1ce7ebbae4..34474c4c816e 100644 --- a/Lib/unittest/test/testmock/testcallable.py +++ b/Lib/unittest/test/testmock/testcallable.py @@ -128,7 +128,7 @@ class Multi(SomeClass, Sub): result.foo.assert_called_once_with(3, 2, 1) - def test_create_autopsec(self): + def test_create_autospec(self): mock = create_autospec(X) instance = mock() self.assertRaises(TypeError, instance) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index fa2ac34fc3d1..401a01277b06 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -8,7 +8,7 @@ from unittest.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, _CallList, + NonCallableMagicMock, _Call, _CallList, create_autospec ) @@ -1635,6 +1635,20 @@ def test_class_assignable(self): self.assertIsInstance(mock, int) mock.foo + def test_name_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".name") + self.assertIsNotNone(call.name) + self.assertEqual(type(call.name), _Call) + self.assertEqual(type(call.name().name), _Call) + + def test_parent_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".parent") + self.assertIsNotNone(call.parent) + self.assertEqual(type(call.parent), _Call) + self.assertEqual(type(call.parent().parent), _Call) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst new file mode 100644 index 000000000000..1dade5baf4e0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst @@ -0,0 +1,4 @@ +Internal attributes' names of unittest.mock._Call and +unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with +_mock_ in order to prevent clashes with widely used object attributes. +Fixed minor typo in test function name. From webhook-mailer at python.org Tue Dec 4 04:34:51 2018 From: webhook-mailer at python.org (Chris Withers) Date: Tue, 04 Dec 2018 09:34:51 -0000 Subject: [Python-checkins] bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (GH-10873) Message-ID: https://github.com/python/cpython/commit/70ca3fce9fe2bdd7bf97d5fe1299cfa5e32b3ad4 commit: 70ca3fce9fe2bdd7bf97d5fe1299cfa5e32b3ad4 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Chris Withers date: 2018-12-04T09:34:48Z summary: bpo-35357: Add _mock_ prefix to name/parent/from_kall attributes of _Call/_MagicProxy. (GH-10873) Fix minor typo in test function name. (cherry picked from commit e63e617ebbe481c498bdf037a62e09f4f9f3963f) Co-authored-by: Andrew Dunai files: A Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst M Lib/unittest/mock.py M Lib/unittest/test/testmock/testcallable.py M Lib/unittest/test/testmock/testmock.py diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index a8f28cefc7a9..707ef0b734d8 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -2004,9 +2004,9 @@ def __new__(cls, value=(), name='', parent=None, two=False, def __init__(self, value=(), name=None, parent=None, two=False, from_kall=True): - self.name = name - self.parent = parent - self.from_kall = from_kall + self._mock_name = name + self._mock_parent = parent + self._mock_from_kall = from_kall def __eq__(self, other): @@ -2023,8 +2023,8 @@ def __eq__(self, other): else: self_name, self_args, self_kwargs = self - if (getattr(self, 'parent', None) and getattr(other, 'parent', None) - and self.parent != other.parent): + if (getattr(self, '_mock_parent', None) and getattr(other, '_mock_parent', None) + and self._mock_parent != other._mock_parent): return False other_name = '' @@ -2068,17 +2068,17 @@ def __eq__(self, other): def __call__(self, *args, **kwargs): - if self.name is None: + if self._mock_name is None: return _Call(('', args, kwargs), name='()') - name = self.name + '()' - return _Call((self.name, args, kwargs), name=name, parent=self) + name = self._mock_name + '()' + return _Call((self._mock_name, args, kwargs), name=name, parent=self) def __getattr__(self, attr): - if self.name is None: + if self._mock_name is None: return _Call(name=attr, from_kall=False) - name = '%s.%s' % (self.name, attr) + name = '%s.%s' % (self._mock_name, attr) return _Call(name=name, parent=self, from_kall=False) @@ -2089,8 +2089,8 @@ def index(self, *args, **kwargs): return self.__getattr__('index')(*args, **kwargs) def __repr__(self): - if not self.from_kall: - name = self.name or 'call' + if not self._mock_from_kall: + name = self._mock_name or 'call' if name.startswith('()'): name = 'call%s' % name return name @@ -2116,9 +2116,9 @@ def call_list(self): vals = [] thing = self while thing is not None: - if thing.from_kall: + if thing._mock_from_kall: vals.append(thing) - thing = thing.parent + thing = thing._mock_parent return _CallList(reversed(vals)) diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py index af1ce7ebbae4..34474c4c816e 100644 --- a/Lib/unittest/test/testmock/testcallable.py +++ b/Lib/unittest/test/testmock/testcallable.py @@ -128,7 +128,7 @@ class Multi(SomeClass, Sub): result.foo.assert_called_once_with(3, 2, 1) - def test_create_autopsec(self): + def test_create_autospec(self): mock = create_autospec(X) instance = mock() self.assertRaises(TypeError, instance) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index c5c8f5ccbd81..14b1ecc84b33 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -8,7 +8,7 @@ from unittest.mock import ( call, DEFAULT, patch, sentinel, MagicMock, Mock, NonCallableMock, - NonCallableMagicMock, _CallList, + NonCallableMagicMock, _Call, _CallList, create_autospec ) @@ -1625,6 +1625,20 @@ def test_class_assignable(self): self.assertIsInstance(mock, int) mock.foo + def test_name_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".name") + self.assertIsNotNone(call.name) + self.assertEqual(type(call.name), _Call) + self.assertEqual(type(call.name().name), _Call) + + def test_parent_attribute_of_call(self): + # bpo-35357: _Call should not disclose any attributes whose names + # may clash with popular ones (such as ".parent") + self.assertIsNotNone(call.parent) + self.assertEqual(type(call.parent), _Call) + self.assertEqual(type(call.parent().parent), _Call) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst new file mode 100644 index 000000000000..1dade5baf4e0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-03-21-20-24.bpo-35357.rhhoiC.rst @@ -0,0 +1,4 @@ +Internal attributes' names of unittest.mock._Call and +unittest.mock.MagicProxy (name, parent & from_kall) are now prefixed with +_mock_ in order to prevent clashes with widely used object attributes. +Fixed minor typo in test function name. From webhook-mailer at python.org Tue Dec 4 05:03:00 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Tue, 04 Dec 2018 10:03:00 -0000 Subject: [Python-checkins] [2.7] bpo-25862: Fix several bugs in the _io module. (GH-8026) (GH-8033) Message-ID: https://github.com/python/cpython/commit/eab421bff954e4fb77516bfe6c98d30ced1412d0 commit: eab421bff954e4fb77516bfe6c98d30ced1412d0 branch: 2.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-04T12:02:48+02:00 summary: [2.7] bpo-25862: Fix several bugs in the _io module. (GH-8026) (GH-8033) They can be exposed when some C API calls fail due to lack of memory. * Failed Py_BuildValue() could cause an assertion error in the following TextIOWrapper.tell(). * initvalue could leak in StringIO.__getstate__() after failed PyDict_Copy(). (cherry picked from commit fdb5a50ef34f7951c3b01eb77b1359725a9ad670) files: M Modules/_io/stringio.c M Modules/_io/textio.c diff --git a/Modules/_io/stringio.c b/Modules/_io/stringio.c index 83bb7acacbb8..c52ca27ad9d6 100644 --- a/Modules/_io/stringio.c +++ b/Modules/_io/stringio.c @@ -693,8 +693,10 @@ stringio_getstate(stringio *self) } else { dict = PyDict_Copy(self->dict); - if (dict == NULL) + if (dict == NULL) { + Py_DECREF(initvalue); return NULL; + } } state = Py_BuildValue("(OOnN)", initvalue, diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 825e84937c15..5501da4b3f5c 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -1466,6 +1466,7 @@ textiowrapper_read_chunk(textio *self) /* At the snapshot point, len(dec_buffer) bytes before the read, the * next input to be decoded is dec_buffer + input_chunk. */ + PyObject *snapshot; PyObject *next_input = PyNumber_Add(dec_buffer, input_chunk); if (next_input == NULL) goto fail; @@ -1477,8 +1478,13 @@ textiowrapper_read_chunk(textio *self) Py_DECREF(next_input); goto fail; } + snapshot = Py_BuildValue("NN", dec_flags, next_input); + if (snapshot == NULL) { + dec_flags = NULL; + goto fail; + } + Py_XSETREF(self->snapshot, snapshot); Py_DECREF(dec_buffer); - Py_XSETREF(self->snapshot, Py_BuildValue("NN", dec_flags, next_input)); } Py_DECREF(input_chunk); @@ -2013,6 +2019,7 @@ textiowrapper_seek(textio *self, PyObject *args) int whence = 0; PyObject *res; int cmp; + PyObject *snapshot; CHECK_ATTACHED(self); @@ -2149,11 +2156,11 @@ textiowrapper_seek(textio *self, PyObject *args) goto fail; } - self->snapshot = Py_BuildValue("iN", cookie.dec_flags, input_chunk); - if (self->snapshot == NULL) { - Py_DECREF(input_chunk); + snapshot = Py_BuildValue("iN", cookie.dec_flags, input_chunk); + if (snapshot == NULL) { goto fail; } + Py_XSETREF(self->snapshot, snapshot); decoded = PyObject_CallMethod(self->decoder, "decode", "Oi", input_chunk, (int)cookie.need_eof); @@ -2171,9 +2178,10 @@ textiowrapper_seek(textio *self, PyObject *args) self->decoded_chars_used = cookie.chars_to_skip; } else { - self->snapshot = Py_BuildValue("is", cookie.dec_flags, ""); - if (self->snapshot == NULL) + snapshot = Py_BuildValue("is", cookie.dec_flags, ""); + if (snapshot == NULL) goto fail; + Py_XSETREF(self->snapshot, snapshot); } /* Finally, reset the encoder (merely useful for proper BOM handling) */ From webhook-mailer at python.org Tue Dec 4 05:38:11 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Tue, 04 Dec 2018 10:38:11 -0000 Subject: [Python-checkins] [2.7] bpo-16865: Support arrays >=2GB in ctypes. (GH-3006). (GH-7441) Message-ID: https://github.com/python/cpython/commit/93d7918f77278f973a4a106c1d01ad2d9805816d commit: 93d7918f77278f973a4a106c1d01ad2d9805816d branch: 2.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-04T12:38:07+02:00 summary: [2.7] bpo-16865: Support arrays >=2GB in ctypes. (GH-3006). (GH-7441) (cherry picked from commit 735abadd5bd91db4a9e6f4311969b0afacca0a1a) Co-Authored-By: Segev Finer files: A Misc/NEWS.d/next/Library/2017-09-29-16-40-38.bpo-16865.l-f6I_.rst M Lib/ctypes/test/test_arrays.py M Modules/_ctypes/_ctypes.c diff --git a/Lib/ctypes/test/test_arrays.py b/Lib/ctypes/test/test_arrays.py index 49aaab52b93b..53859a3e5e93 100644 --- a/Lib/ctypes/test/test_arrays.py +++ b/Lib/ctypes/test/test_arrays.py @@ -1,4 +1,6 @@ import unittest +from test.support import precisionbigmemtest, _2G +import sys from ctypes import * from ctypes.test import need_symbol @@ -132,5 +134,10 @@ class my_int(c_int): t2 = my_int * 1 self.assertIs(t1, t2) + @unittest.skipUnless(sys.maxsize > 2**32, 'requires 64bit platform') + @precisionbigmemtest(size=_2G, memuse=1, dry_run=False) + def test_large_array(self, size): + a = c_char * size + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2017-09-29-16-40-38.bpo-16865.l-f6I_.rst b/Misc/NEWS.d/next/Library/2017-09-29-16-40-38.bpo-16865.l-f6I_.rst new file mode 100644 index 000000000000..afaff736bf1c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-09-29-16-40-38.bpo-16865.l-f6I_.rst @@ -0,0 +1 @@ +Support arrays >=2GiB in :mod:`ctypes`. Patch by Segev Finer. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index fabbdf13e940..9b289af91be7 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1460,24 +1460,36 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) PyTypeObject *result; StgDictObject *stgdict; StgDictObject *itemdict; - PyObject *proto; + PyObject *proto, *length_attr; PyObject *typedict; - long length; - + Py_ssize_t length; Py_ssize_t itemsize, itemalign; typedict = PyTuple_GetItem(args, 2); if (!typedict) return NULL; - proto = PyDict_GetItemString(typedict, "_length_"); /* Borrowed ref */ - if (!proto || !PyInt_Check(proto)) { + length_attr = PyDict_GetItemString(typedict, "_length_"); /* Borrowed ref */ + if (!length_attr || !_PyAnyInt_Check(length_attr)) { PyErr_SetString(PyExc_AttributeError, "class must define a '_length_' attribute, " "which must be a positive integer"); return NULL; } - length = PyInt_AS_LONG(proto); + if (PyInt_Check(length_attr)) { + length = PyInt_AS_LONG(length_attr); + } + else { + assert(PyLong_Check(length_attr)); + length = PyLong_AsSsize_t(length_attr); + if (length == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_SetString(PyExc_OverflowError, + "The '_length_' attribute is too large"); + } + return NULL; + } + } proto = PyDict_GetItemString(typedict, "_type_"); /* Borrowed ref */ if (!proto) { From webhook-mailer at python.org Tue Dec 4 09:54:07 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Tue, 04 Dec 2018 14:54:07 -0000 Subject: [Python-checkins] bpo-35351: Pass link time optimization flags to CFLAGS_NODIST (GH-10797) Message-ID: https://github.com/python/cpython/commit/f92c7aa1ae81efa475b5aecf66e4711ef0f52c4c commit: f92c7aa1ae81efa475b5aecf66e4711ef0f52c4c branch: master author: stratakis committer: Victor Stinner date: 2018-12-04T15:54:01+01:00 summary: bpo-35351: Pass link time optimization flags to CFLAGS_NODIST (GH-10797) When using link time optimizations, the -flto flag is passed to BASECFLAGS, which makes it propagate to distutils. Those flags should be reserved for the interpreter and the stdlib extension modules only, thus moving those flags to CFLAGS_NODIST. files: A Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst M configure M configure.ac diff --git a/Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst b/Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst new file mode 100644 index 000000000000..ee6c870b060f --- /dev/null +++ b/Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst @@ -0,0 +1,2 @@ +When building Python with clang and LTO, LTO flags are no longer passed into +CFLAGS to build third-party C extensions through distutils. diff --git a/configure b/configure index e9e33b3715d1..8d3336387c34 100755 --- a/configure +++ b/configure @@ -6626,7 +6626,7 @@ $as_echo "$as_me: llvm-ar found via xcrun: ${LLVM_AR}" >&6;} LTOFLAGS="$LTOFLAGS -g" fi - BASECFLAGS="$BASECFLAGS $LTOFLAGS" + CFLAGS_NODIST="$CFLAGS_NODIST $LTOFLAGS" LDFLAGS="$LDFLAGS $LTOFLAGS" fi diff --git a/configure.ac b/configure.ac index 15d03ba38129..5c1c02192087 100644 --- a/configure.ac +++ b/configure.ac @@ -1357,7 +1357,7 @@ if test "$Py_LTO" = 'true' ; then LTOFLAGS="$LTOFLAGS -g" fi - BASECFLAGS="$BASECFLAGS $LTOFLAGS" + CFLAGS_NODIST="$CFLAGS_NODIST $LTOFLAGS" LDFLAGS="$LDFLAGS $LTOFLAGS" fi From webhook-mailer at python.org Tue Dec 4 10:06:27 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Tue, 04 Dec 2018 15:06:27 -0000 Subject: [Python-checkins] bpo-35351: Pass link time optimization flags to CFLAGS_NODIST (GH-10797) Message-ID: https://github.com/python/cpython/commit/1751423686d05e3facdd6da2620202728e5d7917 commit: 1751423686d05e3facdd6da2620202728e5d7917 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-04T07:06:16-08:00 summary: bpo-35351: Pass link time optimization flags to CFLAGS_NODIST (GH-10797) When using link time optimizations, the -flto flag is passed to BASECFLAGS, which makes it propagate to distutils. Those flags should be reserved for the interpreter and the stdlib extension modules only, thus moving those flags to CFLAGS_NODIST. (cherry picked from commit f92c7aa1ae81efa475b5aecf66e4711ef0f52c4c) Co-authored-by: stratakis files: A Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst M configure M configure.ac diff --git a/Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst b/Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst new file mode 100644 index 000000000000..ee6c870b060f --- /dev/null +++ b/Misc/NEWS.d/next/Build/2018-12-04-15-33-28.bpo-35351.ZhhBfT.rst @@ -0,0 +1,2 @@ +When building Python with clang and LTO, LTO flags are no longer passed into +CFLAGS to build third-party C extensions through distutils. diff --git a/configure b/configure index bccedd18b7f8..4714ae887a67 100755 --- a/configure +++ b/configure @@ -6670,7 +6670,7 @@ $as_echo "$as_me: llvm-ar found via xcrun: ${LLVM_AR}" >&6;} LTOFLAGS="$LTOFLAGS -g" fi - BASECFLAGS="$BASECFLAGS $LTOFLAGS" + CFLAGS_NODIST="$CFLAGS_NODIST $LTOFLAGS" LDFLAGS="$LDFLAGS $LTOFLAGS" fi diff --git a/configure.ac b/configure.ac index 320671edada8..c35aea98e5b2 100644 --- a/configure.ac +++ b/configure.ac @@ -1394,7 +1394,7 @@ if test "$Py_LTO" = 'true' ; then LTOFLAGS="$LTOFLAGS -g" fi - BASECFLAGS="$BASECFLAGS $LTOFLAGS" + CFLAGS_NODIST="$CFLAGS_NODIST $LTOFLAGS" LDFLAGS="$LDFLAGS $LTOFLAGS" fi From webhook-mailer at python.org Tue Dec 4 11:13:37 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Tue, 04 Dec 2018 16:13:37 -0000 Subject: [Python-checkins] bpo-35363, test_eintr: skip test_open() on macOS (GH-10896) Message-ID: https://github.com/python/cpython/commit/4752e65250bce60b97d5af702d586092d02fbf58 commit: 4752e65250bce60b97d5af702d586092d02fbf58 branch: master author: Victor Stinner committer: GitHub date: 2018-12-04T17:13:33+01:00 summary: bpo-35363, test_eintr: skip test_open() on macOS (GH-10896) files: M Lib/test/eintrdata/eintr_tester.py diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py index aa7cfd14d9f9..25c169bde500 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/eintrdata/eintr_tester.py @@ -349,16 +349,18 @@ def python_open(self, path): fp = open(path, 'w') fp.close() + @unittest.skipIf(sys.platform == "darwin", + "hangs under macOS; see bpo-25234, bpo-35363") def test_open(self): self._test_open("fp = open(path, 'r')\nfp.close()", self.python_open) - @unittest.skipIf(sys.platform == 'darwin', "hangs under OS X; see issue #25234") def os_open(self, path): fd = os.open(path, os.O_WRONLY) os.close(fd) - @unittest.skipIf(sys.platform == "darwin", "hangs under OS X; see issue #25234") + @unittest.skipIf(sys.platform == "darwin", + "hangs under macOS; see bpo-25234, bpo-35363") def test_os_open(self): self._test_open("fd = os.open(path, os.O_RDONLY)\nos.close(fd)", self.os_open) From webhook-mailer at python.org Tue Dec 4 11:18:16 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Tue, 04 Dec 2018 16:18:16 -0000 Subject: [Python-checkins] bpo-35346, platform: import subprocess in _syscmd_file() (GH-10892) Message-ID: https://github.com/python/cpython/commit/b8e689a6e8134e88f857a55e50b6a4977967e385 commit: b8e689a6e8134e88f857a55e50b6a4977967e385 branch: master author: Victor Stinner committer: GitHub date: 2018-12-04T17:18:12+01:00 summary: bpo-35346, platform: import subprocess in _syscmd_file() (GH-10892) Only platform._syscmd_file() uses subprocess. Move subprocess import inside this function to reduce the number of imports at Python startup. Remove also warnings import which is no longer needed. files: M Lib/platform.py diff --git a/Lib/platform.py b/Lib/platform.py index 98ee06f85ef1..74346c4cab09 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -113,9 +113,9 @@ __version__ = '1.0.8' import collections -import sys, os, re, subprocess - -import warnings +import os +import re +import sys ### Globals & Constants @@ -612,11 +612,13 @@ def _syscmd_file(target, default=''): if sys.platform in ('dos', 'win32', 'win16'): # XXX Others too ? return default + + import subprocess target = _follow_symlinks(target) try: proc = subprocess.Popen(['file', target], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) except (AttributeError, OSError): return default output = proc.communicate()[0].decode('latin-1') From webhook-mailer at python.org Tue Dec 4 15:26:01 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Tue, 04 Dec 2018 20:26:01 -0000 Subject: [Python-checkins] bpo-29564: warnings suggests to enable tracemalloc (GH-10486) (GH-10509) Message-ID: https://github.com/python/cpython/commit/0091f349cde179ea991f4ee4d095119cd1fc3802 commit: 0091f349cde179ea991f4ee4d095119cd1fc3802 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Victor Stinner date: 2018-12-04T21:25:57+01:00 summary: bpo-29564: warnings suggests to enable tracemalloc (GH-10486) (GH-10509) The warnings module now suggests to enable tracemalloc if the source is specified, tracemalloc module is available, but tracemalloc is not tracing memory allocations. (cherry picked from commit 2c07c493d2eb45101312e3eb3a77f94d0c9cad1f) Co-authored-by: Victor Stinner files: A Misc/NEWS.d/next/Library/2018-11-12-17-40-04.bpo-29564.SFNBT5.rst M Lib/test/test_warnings/__init__.py M Lib/warnings.py diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 7f3f3fffb5d2..a40a9a297c82 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -960,12 +960,27 @@ def func(): func() """)) - res = assert_python_ok('-Wd', '-X', 'tracemalloc=2', support.TESTFN) + def run(*args): + res = assert_python_ok(*args) + stderr = res.err.decode('ascii', 'replace') + stderr = '\n'.join(stderr.splitlines()) - stderr = res.err.decode('ascii', 'replace') - # normalize newlines - stderr = '\n'.join(stderr.splitlines()) - stderr = re.sub('<.*>', '<...>', stderr) + # normalize newlines + stderr = re.sub('<.*>', '<...>', stderr) + return stderr + + # tracemalloc disabled + stderr = run('-Wd', support.TESTFN) + expected = textwrap.dedent(''' + {fname}:5: ResourceWarning: unclosed file <...> + f = None + ResourceWarning: Enable tracemalloc to get the object allocation traceback + ''') + expected = expected.format(fname=support.TESTFN).strip() + self.assertEqual(stderr, expected) + + # tracemalloc enabled + stderr = run('-Wd', '-X', 'tracemalloc=2', support.TESTFN) expected = textwrap.dedent(''' {fname}:5: ResourceWarning: unclosed file <...> f = None diff --git a/Lib/warnings.py b/Lib/warnings.py index 81f98647786d..ae4295e120f6 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -33,9 +33,8 @@ def _showwarnmsg_impl(msg): pass def _formatwarnmsg_impl(msg): - s = ("%s:%s: %s: %s\n" - % (msg.filename, msg.lineno, msg.category.__name__, - msg.message)) + category = msg.category.__name__ + s = f"{msg.filename}:{msg.lineno}: {category}: {msg.message}\n" if msg.line is None: try: @@ -55,11 +54,20 @@ def _formatwarnmsg_impl(msg): if msg.source is not None: try: import tracemalloc - tb = tracemalloc.get_object_traceback(msg.source) + # Logging a warning should not raise a new exception: + # catch Exception, not only ImportError and RecursionError. except Exception: - # When a warning is logged during Python shutdown, tracemalloc - # and the import machinery don't work anymore + # don't suggest to enable tracemalloc if it's not available + tracing = True tb = None + else: + tracing = tracemalloc.is_tracing() + try: + tb = tracemalloc.get_object_traceback(msg.source) + except Exception: + # When a warning is logged during Python shutdown, tracemalloc + # and the import machinery don't work anymore + tb = None if tb is not None: s += 'Object allocated at (most recent call last):\n' @@ -77,6 +85,9 @@ def _formatwarnmsg_impl(msg): if line: line = line.strip() s += ' %s\n' % line + elif not tracing: + s += (f'{category}: Enable tracemalloc to get the object ' + f'allocation traceback\n') return s # Keep a reference to check if the function was replaced diff --git a/Misc/NEWS.d/next/Library/2018-11-12-17-40-04.bpo-29564.SFNBT5.rst b/Misc/NEWS.d/next/Library/2018-11-12-17-40-04.bpo-29564.SFNBT5.rst new file mode 100644 index 000000000000..7ef3adeb73b9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-12-17-40-04.bpo-29564.SFNBT5.rst @@ -0,0 +1,3 @@ +The warnings module now suggests to enable tracemalloc if the source is +specified, the tracemalloc module is available, but tracemalloc is not +tracing memory allocations. From webhook-mailer at python.org Tue Dec 4 15:28:33 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Tue, 04 Dec 2018 20:28:33 -0000 Subject: [Python-checkins] bpo-35296: make install now installs the internal API (GH-10665) (GH-10897) Message-ID: https://github.com/python/cpython/commit/b02774f42108aaf18eb19865472c8d5cd95b5f11 commit: b02774f42108aaf18eb19865472c8d5cd95b5f11 branch: 3.7 author: Victor Stinner committer: GitHub date: 2018-12-04T21:28:28+01:00 summary: bpo-35296: make install now installs the internal API (GH-10665) (GH-10897) * bpo-35296: make install now installs the internal API (GH-10665) make install now also installs the internal API: Include/internal/*.h header files. (cherry picked from commit f653fd4d950ac092719b6152e38d77c62b443125) * Windows installer now also install Include/internal/ The Windows installer (MSI) now also install header files of the Include/internal/ subdirectory. files: A Misc/NEWS.d/next/Build/2018-12-04-17-10-17.bpo-35296.2ktH40.rst A Misc/NEWS.d/next/C API/2018-11-22-18-34-23.bpo-35296.nxrIQt.rst M Makefile.pre.in M Tools/msi/dev/dev.wixproj diff --git a/Makefile.pre.in b/Makefile.pre.in index afbc8f8c9bea..7d9cbd5c8bf3 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1443,11 +1443,21 @@ inclinstall: else true; \ fi; \ done + @if test ! -d $(DESTDIR)$(INCLUDEPY)/internal; then \ + echo "Creating directory $(DESTDIR)$(INCLUDEPY)/internal"; \ + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(INCLUDEPY)/internal; \ + else true; \ + fi @for i in $(srcdir)/Include/*.h; \ do \ echo $(INSTALL_DATA) $$i $(INCLUDEPY); \ $(INSTALL_DATA) $$i $(DESTDIR)$(INCLUDEPY); \ done + @for i in $(srcdir)/Include/internal/*.h; \ + do \ + echo $(INSTALL_DATA) $$i $(INCLUDEPY)/internal; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(INCLUDEPY)/internal; \ + done $(INSTALL_DATA) pyconfig.h $(DESTDIR)$(CONFINCLUDEPY)/pyconfig.h # Install the library and miscellaneous stuff needed for extending/embedding diff --git a/Misc/NEWS.d/next/Build/2018-12-04-17-10-17.bpo-35296.2ktH40.rst b/Misc/NEWS.d/next/Build/2018-12-04-17-10-17.bpo-35296.2ktH40.rst new file mode 100644 index 000000000000..e6eda2dcf3c5 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2018-12-04-17-10-17.bpo-35296.2ktH40.rst @@ -0,0 +1,2 @@ +The Windows installer (MSI) now also install internal header files +(``Include/internal/`` subdirectory). diff --git a/Misc/NEWS.d/next/C API/2018-11-22-18-34-23.bpo-35296.nxrIQt.rst b/Misc/NEWS.d/next/C API/2018-11-22-18-34-23.bpo-35296.nxrIQt.rst new file mode 100644 index 000000000000..c5f877a4e323 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2018-11-22-18-34-23.bpo-35296.nxrIQt.rst @@ -0,0 +1,2 @@ +``make install`` now also installs the internal API: +``Include/internal/*.h`` header files. diff --git a/Tools/msi/dev/dev.wixproj b/Tools/msi/dev/dev.wixproj index 682b66031f1e..bc3a19ce33ca 100644 --- a/Tools/msi/dev/dev.wixproj +++ b/Tools/msi/dev/dev.wixproj @@ -21,7 +21,7 @@ - + $(PySourcePath) !(bindpath.src) $(PySourcePath) @@ -29,7 +29,7 @@ dev_include - + - \ No newline at end of file + From webhook-mailer at python.org Tue Dec 4 17:53:18 2018 From: webhook-mailer at python.org (Raymond Hettinger) Date: Tue, 04 Dec 2018 22:53:18 -0000 Subject: [Python-checkins] Remove unnecessary and over-restrictive type check (GH-10905) Message-ID: https://github.com/python/cpython/commit/09473ac0636c16c0ee96c4caf59f3da8ba8b4a57 commit: 09473ac0636c16c0ee96c4caf59f3da8ba8b4a57 branch: master author: Raymond Hettinger committer: GitHub date: 2018-12-04T14:53:14-08:00 summary: Remove unnecessary and over-restrictive type check (GH-10905) files: M Lib/random.py diff --git a/Lib/random.py b/Lib/random.py index a7a86070c0a9..03c058a39d6e 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -718,8 +718,6 @@ def getrandbits(self, k): """getrandbits(k) -> x. Generates an int with k random bits.""" if k <= 0: raise ValueError('number of bits must be greater than zero') - if k != int(k): - raise TypeError('number of bits should be an integer') numbytes = (k + 7) // 8 # bits / 8 and rounded up x = int.from_bytes(_urandom(numbytes), 'big') return x >> (numbytes * 8 - k) # trim excess bits From webhook-mailer at python.org Tue Dec 4 19:58:42 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 00:58:42 -0000 Subject: [Python-checkins] bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) Message-ID: https://github.com/python/cpython/commit/c11b3b19a5b022c6c229043d37f9a9fd06f22500 commit: c11b3b19a5b022c6c229043d37f9a9fd06f22500 branch: master author: Victor Stinner committer: GitHub date: 2018-12-05T01:58:31+01:00 summary: bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) On Travis CI, FTP tests of test_urllib2net randomly fail with "425 Security: Bad IP connecting". test.pythoninfo now also logs TRAVIS environment variable. files: M Lib/test/pythoninfo.py M Lib/test/test_urllib2net.py diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 30e6f21c2b48..9befd12e4095 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -256,6 +256,7 @@ def format_groups(groups): "TIX_LIBRARY", "TMP", "TMPDIR", + "TRAVIS", "TZ", "USERPROFILE", "VIRTUAL_ENV", diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index 15f73de9f029..1aa64cbee1ca 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -27,6 +27,13 @@ def wrapped(*args, **kwargs): return _retry_thrice(func, exc, *args, **kwargs) return wrapped +# bpo-35411: FTP tests of test_urllib2net randomly fail +# with "425 Security: Bad IP connecting" on Travis CI +skip_ftp_test_on_travis = unittest.skipIf('TRAVIS' in os.environ, + 'bpo-35411: skip FTP test ' + 'on Travis CI') + + # Connecting to remote hosts is flaky. Make it more robust by retrying # the connection several times. _urlopen_with_retry = _wrap_with_retry_thrice(urllib.request.urlopen, @@ -95,6 +102,7 @@ def setUp(self): # XXX The rest of these tests aren't very good -- they don't check much. # They do sometimes catch some major disasters, though. + @skip_ftp_test_on_travis def test_ftp(self): urls = [ 'ftp://www.pythontest.net/README', @@ -290,6 +298,7 @@ def test_http_timeout(self): FTP_HOST = 'ftp://www.pythontest.net/' + @skip_ftp_test_on_travis def test_ftp_basic(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST, timeout=None): @@ -297,6 +306,7 @@ def test_ftp_basic(self): self.addCleanup(u.close) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_default_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST): @@ -308,6 +318,7 @@ def test_ftp_default_timeout(self): socket.setdefaulttimeout(None) self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 60) + @skip_ftp_test_on_travis def test_ftp_no_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST): @@ -319,6 +330,7 @@ def test_ftp_no_timeout(self): socket.setdefaulttimeout(None) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_timeout(self): with support.transient_internet(self.FTP_HOST): u = _urlopen_with_retry(self.FTP_HOST, timeout=60) From webhook-mailer at python.org Tue Dec 4 20:16:44 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 01:16:44 -0000 Subject: [Python-checkins] bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) Message-ID: https://github.com/python/cpython/commit/74a80e1ed0c9067ef47f0a637d7f718a51b4f34e commit: 74a80e1ed0c9067ef47f0a637d7f718a51b4f34e branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-04T17:16:39-08:00 summary: bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) On Travis CI, FTP tests of test_urllib2net randomly fail with "425 Security: Bad IP connecting". test.pythoninfo now also logs TRAVIS environment variable. (cherry picked from commit c11b3b19a5b022c6c229043d37f9a9fd06f22500) Co-authored-by: Victor Stinner files: M Lib/test/pythoninfo.py M Lib/test/test_urllib2net.py diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 30e6f21c2b48..9befd12e4095 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -256,6 +256,7 @@ def format_groups(groups): "TIX_LIBRARY", "TMP", "TMPDIR", + "TRAVIS", "TZ", "USERPROFILE", "VIRTUAL_ENV", diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index 15f73de9f029..1aa64cbee1ca 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -27,6 +27,13 @@ def wrapped(*args, **kwargs): return _retry_thrice(func, exc, *args, **kwargs) return wrapped +# bpo-35411: FTP tests of test_urllib2net randomly fail +# with "425 Security: Bad IP connecting" on Travis CI +skip_ftp_test_on_travis = unittest.skipIf('TRAVIS' in os.environ, + 'bpo-35411: skip FTP test ' + 'on Travis CI') + + # Connecting to remote hosts is flaky. Make it more robust by retrying # the connection several times. _urlopen_with_retry = _wrap_with_retry_thrice(urllib.request.urlopen, @@ -95,6 +102,7 @@ def setUp(self): # XXX The rest of these tests aren't very good -- they don't check much. # They do sometimes catch some major disasters, though. + @skip_ftp_test_on_travis def test_ftp(self): urls = [ 'ftp://www.pythontest.net/README', @@ -290,6 +298,7 @@ def test_http_timeout(self): FTP_HOST = 'ftp://www.pythontest.net/' + @skip_ftp_test_on_travis def test_ftp_basic(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST, timeout=None): @@ -297,6 +306,7 @@ def test_ftp_basic(self): self.addCleanup(u.close) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_default_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST): @@ -308,6 +318,7 @@ def test_ftp_default_timeout(self): socket.setdefaulttimeout(None) self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 60) + @skip_ftp_test_on_travis def test_ftp_no_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST): @@ -319,6 +330,7 @@ def test_ftp_no_timeout(self): socket.setdefaulttimeout(None) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_timeout(self): with support.transient_internet(self.FTP_HOST): u = _urlopen_with_retry(self.FTP_HOST, timeout=60) From webhook-mailer at python.org Tue Dec 4 20:19:00 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 01:19:00 -0000 Subject: [Python-checkins] bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) (GH-10909) Message-ID: https://github.com/python/cpython/commit/65a5eff67474703329477b070d9c081fee17c69c commit: 65a5eff67474703329477b070d9c081fee17c69c branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Victor Stinner date: 2018-12-05T02:18:57+01:00 summary: bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) (GH-10909) On Travis CI, FTP tests of test_urllib2net randomly fail with "425 Security: Bad IP connecting". test.pythoninfo now also logs TRAVIS environment variable. (cherry picked from commit c11b3b19a5b022c6c229043d37f9a9fd06f22500) Co-authored-by: Victor Stinner files: M Lib/test/pythoninfo.py M Lib/test/test_urllib2net.py diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 606d511433cf..c453340b80d7 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -256,6 +256,7 @@ def format_groups(groups): "TIX_LIBRARY", "TMP", "TMPDIR", + "TRAVIS", "TZ", "USERPROFILE", "VIRTUAL_ENV", diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index 15f73de9f029..1aa64cbee1ca 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -27,6 +27,13 @@ def wrapped(*args, **kwargs): return _retry_thrice(func, exc, *args, **kwargs) return wrapped +# bpo-35411: FTP tests of test_urllib2net randomly fail +# with "425 Security: Bad IP connecting" on Travis CI +skip_ftp_test_on_travis = unittest.skipIf('TRAVIS' in os.environ, + 'bpo-35411: skip FTP test ' + 'on Travis CI') + + # Connecting to remote hosts is flaky. Make it more robust by retrying # the connection several times. _urlopen_with_retry = _wrap_with_retry_thrice(urllib.request.urlopen, @@ -95,6 +102,7 @@ def setUp(self): # XXX The rest of these tests aren't very good -- they don't check much. # They do sometimes catch some major disasters, though. + @skip_ftp_test_on_travis def test_ftp(self): urls = [ 'ftp://www.pythontest.net/README', @@ -290,6 +298,7 @@ def test_http_timeout(self): FTP_HOST = 'ftp://www.pythontest.net/' + @skip_ftp_test_on_travis def test_ftp_basic(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST, timeout=None): @@ -297,6 +306,7 @@ def test_ftp_basic(self): self.addCleanup(u.close) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_default_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST): @@ -308,6 +318,7 @@ def test_ftp_default_timeout(self): socket.setdefaulttimeout(None) self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 60) + @skip_ftp_test_on_travis def test_ftp_no_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with support.transient_internet(self.FTP_HOST): @@ -319,6 +330,7 @@ def test_ftp_no_timeout(self): socket.setdefaulttimeout(None) self.assertIsNone(u.fp.fp.raw._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_timeout(self): with support.transient_internet(self.FTP_HOST): u = _urlopen_with_retry(self.FTP_HOST, timeout=60) From webhook-mailer at python.org Tue Dec 4 20:22:06 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 01:22:06 -0000 Subject: [Python-checkins] bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) Message-ID: https://github.com/python/cpython/commit/c7976da5c262be818a06c344d43ac09b1083c80b commit: c7976da5c262be818a06c344d43ac09b1083c80b branch: 2.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-04T17:22:02-08:00 summary: bpo-35411: Skip test_urllib2net FTP tests on Travis CI (GH-10907) On Travis CI, FTP tests of test_urllib2net randomly fail with "425 Security: Bad IP connecting". test.pythoninfo now also logs TRAVIS environment variable. (cherry picked from commit c11b3b19a5b022c6c229043d37f9a9fd06f22500) Co-authored-by: Victor Stinner files: M Lib/test/pythoninfo.py M Lib/test/test_urllib2net.py diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 693a13525ff3..ae1c07b43ac3 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -256,6 +256,7 @@ def format_groups(groups): "TIX_LIBRARY", "TMP", "TMPDIR", + "TRAVIS", "TZ", "USERPROFILE", "VIRTUAL_ENV", diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index 4ba79cbc31c3..ee0b7fe453e6 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -25,6 +25,13 @@ def wrapped(*args, **kwargs): return _retry_thrice(func, exc, *args, **kwargs) return wrapped +# bpo-35411: FTP tests of test_urllib2net randomly fail +# with "425 Security: Bad IP connecting" on Travis CI +skip_ftp_test_on_travis = unittest.skipIf('TRAVIS' in os.environ, + 'bpo-35411: skip FTP test ' + 'on Travis CI') + + # Connecting to remote hosts is flaky. Make it more robust by retrying # the connection several times. _urlopen_with_retry = _wrap_with_retry_thrice(urllib2.urlopen, urllib2.URLError) @@ -100,6 +107,7 @@ def setUp(self): # XXX The rest of these tests aren't very good -- they don't check much. # They do sometimes catch some major disasters, though. + @skip_ftp_test_on_travis def test_ftp(self): urls = [ 'ftp://www.pythontest.net/README', @@ -285,12 +293,14 @@ def test_http_timeout(self): FTP_HOST = 'ftp://www.pythontest.net/' + @skip_ftp_test_on_travis def test_ftp_basic(self): self.assertIsNone(socket.getdefaulttimeout()) with test_support.transient_internet(self.FTP_HOST, timeout=None): u = _urlopen_with_retry(self.FTP_HOST) self.assertIsNone(u.fp.fp._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_default_timeout(self): self.assertIsNone(socket.getdefaulttimeout()) with test_support.transient_internet(self.FTP_HOST): @@ -301,6 +311,7 @@ def test_ftp_default_timeout(self): socket.setdefaulttimeout(None) self.assertEqual(u.fp.fp._sock.gettimeout(), 60) + @skip_ftp_test_on_travis def test_ftp_no_timeout(self): self.assertIsNone(socket.getdefaulttimeout(),) with test_support.transient_internet(self.FTP_HOST): @@ -311,6 +322,7 @@ def test_ftp_no_timeout(self): socket.setdefaulttimeout(None) self.assertIsNone(u.fp.fp._sock.gettimeout()) + @skip_ftp_test_on_travis def test_ftp_timeout(self): with test_support.transient_internet(self.FTP_HOST): u = _urlopen_with_retry(self.FTP_HOST, timeout=60) From webhook-mailer at python.org Tue Dec 4 21:03:26 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 02:03:26 -0000 Subject: [Python-checkins] bpo-35363, test_eintr: skip test_open() on macOS (GH-10896) (GH-10912) Message-ID: https://github.com/python/cpython/commit/93d038c91dc3075dd34b41ce6b6fb4ea07fbb8c3 commit: 93d038c91dc3075dd34b41ce6b6fb4ea07fbb8c3 branch: 3.6 author: Victor Stinner committer: GitHub date: 2018-12-05T03:03:21+01:00 summary: bpo-35363, test_eintr: skip test_open() on macOS (GH-10896) (GH-10912) (cherry picked from commit 4752e65250bce60b97d5af702d586092d02fbf58) files: M Lib/test/eintrdata/eintr_tester.py diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py index aa7cfd14d9f9..25c169bde500 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/eintrdata/eintr_tester.py @@ -349,16 +349,18 @@ def python_open(self, path): fp = open(path, 'w') fp.close() + @unittest.skipIf(sys.platform == "darwin", + "hangs under macOS; see bpo-25234, bpo-35363") def test_open(self): self._test_open("fp = open(path, 'r')\nfp.close()", self.python_open) - @unittest.skipIf(sys.platform == 'darwin', "hangs under OS X; see issue #25234") def os_open(self, path): fd = os.open(path, os.O_WRONLY) os.close(fd) - @unittest.skipIf(sys.platform == "darwin", "hangs under OS X; see issue #25234") + @unittest.skipIf(sys.platform == "darwin", + "hangs under macOS; see bpo-25234, bpo-35363") def test_os_open(self): self._test_open("fd = os.open(path, os.O_RDONLY)\nos.close(fd)", self.os_open) From webhook-mailer at python.org Tue Dec 4 21:03:31 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 02:03:31 -0000 Subject: [Python-checkins] bpo-35363, test_eintr: skip test_open() on macOS (GH-10896) (GH-10911) Message-ID: https://github.com/python/cpython/commit/c93e3b05d5672dc9e8d6e2f2dce332799d5b95d2 commit: c93e3b05d5672dc9e8d6e2f2dce332799d5b95d2 branch: 3.7 author: Victor Stinner committer: GitHub date: 2018-12-05T03:03:28+01:00 summary: bpo-35363, test_eintr: skip test_open() on macOS (GH-10896) (GH-10911) (cherry picked from commit 4752e65250bce60b97d5af702d586092d02fbf58) files: M Lib/test/eintrdata/eintr_tester.py diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py index aa7cfd14d9f9..25c169bde500 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/eintrdata/eintr_tester.py @@ -349,16 +349,18 @@ def python_open(self, path): fp = open(path, 'w') fp.close() + @unittest.skipIf(sys.platform == "darwin", + "hangs under macOS; see bpo-25234, bpo-35363") def test_open(self): self._test_open("fp = open(path, 'r')\nfp.close()", self.python_open) - @unittest.skipIf(sys.platform == 'darwin', "hangs under OS X; see issue #25234") def os_open(self, path): fd = os.open(path, os.O_WRONLY) os.close(fd) - @unittest.skipIf(sys.platform == "darwin", "hangs under OS X; see issue #25234") + @unittest.skipIf(sys.platform == "darwin", + "hangs under macOS; see bpo-25234, bpo-35363") def test_os_open(self): self._test_open("fd = os.open(path, os.O_RDONLY)\nos.close(fd)", self.os_open) From webhook-mailer at python.org Wed Dec 5 02:14:06 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 07:14:06 -0000 Subject: [Python-checkins] bpo-35414: Add a missing Py_INCREF(Py_None) in PyState_RemoveModule(). (GH-10914) Message-ID: https://github.com/python/cpython/commit/2a893430c9c8378cbdfac95895a64fa07aaff9ed commit: 2a893430c9c8378cbdfac95895a64fa07aaff9ed branch: master author: Zackery Spytz committer: Serhiy Storchaka date: 2018-12-05T09:14:00+02:00 summary: bpo-35414: Add a missing Py_INCREF(Py_None) in PyState_RemoveModule(). (GH-10914) files: M Python/pystate.c diff --git a/Python/pystate.c b/Python/pystate.c index f86f5a96f074..98882eb7589c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -592,6 +592,7 @@ PyState_RemoveModule(struct PyModuleDef* def) Py_FatalError("PyState_RemoveModule: Module index out of bounds."); return -1; } + Py_INCREF(Py_None); return PyList_SetItem(state->modules_by_index, index, Py_None); } From webhook-mailer at python.org Wed Dec 5 02:51:17 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 07:51:17 -0000 Subject: [Python-checkins] bpo-35414: Add a missing Py_INCREF(Py_None) in PyState_RemoveModule(). (GH-10914) Message-ID: https://github.com/python/cpython/commit/2d594f857865a4719876ac545ddfd62f474522cd commit: 2d594f857865a4719876ac545ddfd62f474522cd branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-04T23:51:08-08:00 summary: bpo-35414: Add a missing Py_INCREF(Py_None) in PyState_RemoveModule(). (GH-10914) (cherry picked from commit 2a893430c9c8378cbdfac95895a64fa07aaff9ed) Co-authored-by: Zackery Spytz files: M Python/pystate.c diff --git a/Python/pystate.c b/Python/pystate.c index 15761e7da90f..8077a3ee7ab0 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -532,6 +532,7 @@ PyState_RemoveModule(struct PyModuleDef* def) Py_FatalError("PyState_RemoveModule: Module index out of bounds."); return -1; } + Py_INCREF(Py_None); return PyList_SetItem(state->modules_by_index, index, Py_None); } From webhook-mailer at python.org Wed Dec 5 02:51:19 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 07:51:19 -0000 Subject: [Python-checkins] bpo-35414: Add a missing Py_INCREF(Py_None) in PyState_RemoveModule(). (GH-10914) Message-ID: https://github.com/python/cpython/commit/afb07fccf013f20b89b33516f126695388be47b9 commit: afb07fccf013f20b89b33516f126695388be47b9 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-04T23:51:15-08:00 summary: bpo-35414: Add a missing Py_INCREF(Py_None) in PyState_RemoveModule(). (GH-10914) (cherry picked from commit 2a893430c9c8378cbdfac95895a64fa07aaff9ed) Co-authored-by: Zackery Spytz files: M Python/pystate.c diff --git a/Python/pystate.c b/Python/pystate.c index c0e088055a60..71494daa471a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -377,6 +377,7 @@ PyState_RemoveModule(struct PyModuleDef* def) Py_FatalError("PyState_RemoveModule: Module index out of bounds."); return -1; } + Py_INCREF(Py_None); return PyList_SetItem(state->modules_by_index, index, Py_None); } From webhook-mailer at python.org Wed Dec 5 03:30:10 2018 From: webhook-mailer at python.org (Andrew Svetlov) Date: Wed, 05 Dec 2018 08:30:10 -0000 Subject: [Python-checkins] [3.6] bpo-35380: Enable TCP_NODELAY for proactor event loop (GH-10867). (GH-10874) Message-ID: https://github.com/python/cpython/commit/bfb881849f588cd2046776fb431c3045781c8214 commit: bfb881849f588cd2046776fb431c3045781c8214 branch: 3.6 author: Andrew Svetlov committer: GitHub date: 2018-12-05T10:30:06+02:00 summary: [3.6] bpo-35380: Enable TCP_NODELAY for proactor event loop (GH-10867). (GH-10874) * [3.6] bpo-35380: Enable TCP_NODELAY for proactor event loop (GH-10867). (cherry picked from commit 3bc0ebab17bf5a2c29d2214743c82034f82e6573) Co-authored-by: Andrew Svetlov files: A Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst M Lib/asyncio/base_events.py M Lib/asyncio/proactor_events.py M Lib/asyncio/selector_events.py M Lib/test/test_asyncio/test_base_events.py M Lib/test/test_asyncio/test_selector_events.py diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index accd669250cb..48dd1fc54ae4 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -182,6 +182,17 @@ def _ensure_resolved(address, *, family=0, type=socket.SOCK_STREAM, proto=0, proto=proto, flags=flags) +if hasattr(socket, 'TCP_NODELAY'): + def _set_nodelay(sock): + if (sock.family in {socket.AF_INET, socket.AF_INET6} and + _is_stream_socket(sock.type) and + sock.proto == socket.IPPROTO_TCP): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) +else: + def _set_nodelay(sock): + pass + + def _run_until_complete_cb(fut): exc = fut._exception if (isinstance(exc, BaseException) diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 6f621ef0cc4f..079b7a24db42 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -350,6 +350,11 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport, transports.Transport): """Transport for connected sockets.""" + def __init__(self, loop, sock, protocol, waiter=None, + extra=None, server=None): + super().__init__(loop, sock, protocol, waiter, extra, server) + base_events._set_nodelay(sock) + def _set_extra(self, sock): self._extra['socket'] = sock try: diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index bc7c740cc2af..af21d5ee8914 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -40,17 +40,6 @@ def _test_selector_event(selector, fd, event): return bool(key.events & event) -if hasattr(socket, 'TCP_NODELAY'): - def _set_nodelay(sock): - if (sock.family in {socket.AF_INET, socket.AF_INET6} and - base_events._is_stream_socket(sock.type) and - sock.proto == socket.IPPROTO_TCP): - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) -else: - def _set_nodelay(sock): - pass - - class BaseSelectorEventLoop(base_events.BaseEventLoop): """Selector event loop. @@ -691,7 +680,7 @@ def __init__(self, loop, sock, protocol, waiter=None, # Disable the Nagle algorithm -- small writes will be # sent without waiting for the TCP ACK. This generally # decreases the latency (in some cases significantly.) - _set_nodelay(self._sock) + base_events._set_nodelay(self._sock) self._loop.call_soon(self._protocol.connection_made, self) # only start reading when connection_made() has been called diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 09f71f2f3900..052a559c3cbb 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -27,7 +27,6 @@ except ImportError: from asyncio.test_support import assert_python_ok - MOCK_ANY = mock.ANY PY34 = sys.version_info >= (3, 4) @@ -288,7 +287,7 @@ def cb(): loop.set_debug(debug) if debug: msg = ("Non-thread-safe operation invoked on an event loop other " - "than the current one") + "than the current one") with self.assertRaisesRegex(RuntimeError, msg): loop.call_soon(cb) with self.assertRaisesRegex(RuntimeError, msg): @@ -1839,5 +1838,30 @@ def runner(loop): +class TestSelectorUtils(test_utils.TestCase): + def check_set_nodelay(self, sock): + opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) + self.assertFalse(opt) + + base_events._set_nodelay(sock) + + opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) + self.assertTrue(opt) + + @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), + 'need socket.TCP_NODELAY') + def test_set_nodelay(self): + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + with sock: + self.check_set_nodelay(sock) + + sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, + proto=socket.IPPROTO_TCP) + with sock: + sock.setblocking(False) + self.check_set_nodelay(sock) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index 533d2898e7fc..6716e8916cd5 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -17,7 +17,6 @@ from asyncio.selector_events import _SelectorSslTransport from asyncio.selector_events import _SelectorSocketTransport from asyncio.selector_events import _SelectorDatagramTransport -from asyncio.selector_events import _set_nodelay MOCK_ANY = mock.ANY @@ -1858,30 +1857,5 @@ def test_fatal_error_connected(self, m_exc): exc_info=(ConnectionRefusedError, MOCK_ANY, MOCK_ANY)) -class TestSelectorUtils(test_utils.TestCase): - def check_set_nodelay(self, sock): - opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertFalse(opt) - - _set_nodelay(sock) - - opt = sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY) - self.assertTrue(opt) - - @unittest.skipUnless(hasattr(socket, 'TCP_NODELAY'), - 'need socket.TCP_NODELAY') - def test_set_nodelay(self): - sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, - proto=socket.IPPROTO_TCP) - with sock: - self.check_set_nodelay(sock) - - sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, - proto=socket.IPPROTO_TCP) - with sock: - sock.setblocking(False) - self.check_set_nodelay(sock) - - if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst b/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst new file mode 100644 index 000000000000..91f86e604ea8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-03-14-41-11.bpo-35380.SdRF9l.rst @@ -0,0 +1 @@ +Enable TCP_NODELAY on Windows for proactor asyncio event loop. From solipsis at pitrou.net Wed Dec 5 04:10:28 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Wed, 05 Dec 2018 09:10:28 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=5 Message-ID: <20181205091028.1.95010E1546E4245D@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_functools leaked [0, 3, 1] memory blocks, sum=4 test_multiprocessing_spawn leaked [-1, 2, 0] memory blocks, sum=1 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogsEB5em', '--timeout', '7200'] From webhook-mailer at python.org Wed Dec 5 08:04:56 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 13:04:56 -0000 Subject: [Python-checkins] bpo-35389: platform.libc_ver() uses os.confstr() (GH-10891) Message-ID: https://github.com/python/cpython/commit/476b113ed8531b9fbb0bd023a05eb3af21996600 commit: 476b113ed8531b9fbb0bd023a05eb3af21996600 branch: master author: Victor Stinner committer: GitHub date: 2018-12-05T14:04:52+01:00 summary: bpo-35389: platform.libc_ver() uses os.confstr() (GH-10891) platform.libc_ver() now uses os.confstr('CS_GNU_LIBC_VERSION') if available and the *executable* parameter is not set. The default value of the libc_ver() *executable* parameter becomes None. Quick benchmark on Fedora 29: python3 -m perf command ./python -S -c 'import platform; platform.libc_ver()' 94.9 ms +- 4.3 ms -> 33.2 ms +- 1.4 ms: 2.86x faster (-65%) files: A Misc/NEWS.d/next/Library/2018-12-04-12-46-05.bpo-35389.CTZ9iA.rst M Lib/platform.py M Lib/test/test_platform.py diff --git a/Lib/platform.py b/Lib/platform.py index 74346c4cab09..f089a463ef9f 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -169,7 +169,7 @@ def _comparable_version(version): b'|' br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII) -def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384): +def libc_ver(executable=None, lib='', version='', chunksize=16384): """ Tries to determine the libc version that the file executable (which defaults to the Python interpreter) is linked against. @@ -184,6 +184,19 @@ def libc_ver(executable=sys.executable, lib='', version='', chunksize=16384): The file is read and scanned in chunks of chunksize bytes. """ + if executable is None: + try: + ver = os.confstr('CS_GNU_LIBC_VERSION') + # parse 'glibc 2.28' as ('glibc', '2.28') + parts = ver.split(maxsplit=1) + if len(parts) == 2: + return tuple(parts) + except (AttributeError, ValueError, OSError): + # os.confstr() or CS_GNU_LIBC_VERSION value not available + pass + + executable = sys.executable + V = _comparable_version if hasattr(os.path, 'realpath'): # Python 2.2 introduced os.path.realpath(); it is used diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 686f454827fd..978d2f76ab68 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -3,7 +3,9 @@ import subprocess import sys import sysconfig +import tempfile import unittest +from unittest import mock from test import support @@ -263,19 +265,46 @@ def test_mac_ver_with_fork(self): self.assertEqual(sts, 0) def test_libc_ver(self): + # check that libc_ver(executable) doesn't raise an exception if os.path.isdir(sys.executable) and \ os.path.exists(sys.executable+'.exe'): # Cygwin horror executable = sys.executable + '.exe' else: executable = sys.executable - res = platform.libc_ver(executable) - - self.addCleanup(support.unlink, support.TESTFN) - with open(support.TESTFN, 'wb') as f: - f.write(b'x'*(16384-10)) + platform.libc_ver(executable) + + filename = support.TESTFN + self.addCleanup(support.unlink, filename) + + with mock.patch('os.confstr', create=True, return_value='mock 1.0'): + # test os.confstr() code path + self.assertEqual(platform.libc_ver(), ('mock', '1.0')) + + # test the different regular expressions + for data, expected in ( + (b'__libc_init', ('libc', '')), + (b'GLIBC_2.9', ('glibc', '2.9')), + (b'libc.so.1.2.5', ('libc', '1.2.5')), + (b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')), + (b'', ('', '')), + ): + with open(filename, 'wb') as fp: + fp.write(b'[xxx%sxxx]' % data) + fp.flush() + + # os.confstr() must not be used if executable is set + self.assertEqual(platform.libc_ver(executable=filename), + expected) + + # binary containing multiple versions: get the most recent, + # make sure that 1.9 is seen as older than 1.23.4 + chunksize = 16384 + with open(filename, 'wb') as f: + # test match at chunk boundary + f.write(b'x'*(chunksize - 10)) f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0') - self.assertEqual(platform.libc_ver(support.TESTFN), + self.assertEqual(platform.libc_ver(filename, chunksize=chunksize), ('glibc', '1.23.4')) @support.cpython_only diff --git a/Misc/NEWS.d/next/Library/2018-12-04-12-46-05.bpo-35389.CTZ9iA.rst b/Misc/NEWS.d/next/Library/2018-12-04-12-46-05.bpo-35389.CTZ9iA.rst new file mode 100644 index 000000000000..8e2f9dd21cc0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-04-12-46-05.bpo-35389.CTZ9iA.rst @@ -0,0 +1,2 @@ +:func:`platform.libc_ver` now uses ``os.confstr('CS_GNU_LIBC_VERSION')`` if +available and the *executable* parameter is not set. From webhook-mailer at python.org Wed Dec 5 09:44:19 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 14:44:19 -0000 Subject: [Python-checkins] bpo-32787: Better error handling in ctypes. (#3727) Message-ID: https://github.com/python/cpython/commit/398bd27967690f2c1a8cbf8d47a5613edd9cfb2a commit: 398bd27967690f2c1a8cbf8d47a5613edd9cfb2a branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-05T16:44:14+02:00 summary: bpo-32787: Better error handling in ctypes. (#3727) * bpo-31572: Get rid of PyObject_HasAttrString() in ctypes. * Fix error handling for _pack_. * Don't silence errors when look up in a dict. * Use _PyObject_LookupAttrId(). * More changes. files: M Modules/_ctypes/_ctypes.c M Modules/_ctypes/callbacks.c M Modules/_ctypes/callproc.c M Modules/_ctypes/cfield.c M Modules/_ctypes/stgdict.c diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 36ef5d9b67fc..3debe3ace695 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -236,7 +236,7 @@ PyObject * PyDict_GetItemProxy(PyObject *dict, PyObject *key) { PyObject *result; - PyObject *item = PyDict_GetItem(dict, key); + PyObject *item = PyDict_GetItemWithError(dict, key); if (item == NULL) return NULL; @@ -426,6 +426,8 @@ StructUnionType_new(PyTypeObject *type, PyObject *args, PyObject *kwds, int isSt PyTypeObject *result; PyObject *fields; StgDictObject *dict; + _Py_IDENTIFIER(_abstract_); + _Py_IDENTIFIER(_fields_); /* create the new instance (which is a class, since we are a metatype!) */ @@ -434,8 +436,12 @@ StructUnionType_new(PyTypeObject *type, PyObject *args, PyObject *kwds, int isSt return NULL; /* keep this for bw compatibility */ - if (PyDict_GetItemString(result->tp_dict, "_abstract_")) + if (_PyDict_GetItemIdWithError(result->tp_dict, &PyId__abstract_)) return (PyObject *)result; + if (PyErr_Occurred()) { + Py_DECREF(result); + return NULL; + } dict = (StgDictObject *)_PyObject_CallNoArg((PyObject *)&PyCStgDict_Type); if (!dict) { @@ -458,8 +464,19 @@ StructUnionType_new(PyTypeObject *type, PyObject *args, PyObject *kwds, int isSt dict->paramfunc = StructUnionType_paramfunc; - fields = PyDict_GetItemString((PyObject *)dict, "_fields_"); - if (!fields) { + fields = _PyDict_GetItemIdWithError((PyObject *)dict, &PyId__fields_); + if (fields) { + if (_PyObject_SetAttrId((PyObject *)result, &PyId__fields_, fields) < 0) { + Py_DECREF(result); + return NULL; + } + return (PyObject *)result; + } + else if (PyErr_Occurred()) { + Py_DECREF(result); + return NULL; + } + else { StgDictObject *basedict = PyType_stgdict((PyObject *)result->tp_base); if (basedict == NULL) @@ -473,12 +490,6 @@ StructUnionType_new(PyTypeObject *type, PyObject *args, PyObject *kwds, int isSt basedict->flags |= DICTFLAG_FINAL; /* set the 'final' flag in the baseclass dict */ return (PyObject *)result; } - - if (-1 == PyObject_SetAttrString((PyObject *)result, "_fields_", fields)) { - Py_DECREF(result); - return NULL; - } - return (PyObject *)result; } static PyObject * @@ -693,6 +704,7 @@ static const char from_param_doc[] = static PyObject * CDataType_from_param(PyObject *type, PyObject *value) { + _Py_IDENTIFIER(_as_parameter_); PyObject *as_parameter; int res = PyObject_IsInstance(value, type); if (res == -1) @@ -726,7 +738,9 @@ CDataType_from_param(PyObject *type, PyObject *value) return NULL; } - as_parameter = PyObject_GetAttrString(value, "_as_parameter_"); + if (_PyObject_LookupAttrId(value, &PyId__as_parameter_, &as_parameter) < 0) { + return NULL; + } if (as_parameter) { value = CDataType_from_param(type, as_parameter); Py_DECREF(as_parameter); @@ -961,6 +975,7 @@ PyCPointerType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) StgDictObject *stgdict; PyObject *proto; PyObject *typedict; + _Py_IDENTIFIER(_type_); typedict = PyTuple_GetItem(args, 2); if (!typedict) @@ -980,15 +995,15 @@ PyCPointerType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) stgdict->paramfunc = PyCPointerType_paramfunc; stgdict->flags |= TYPEFLAG_ISPOINTER; - proto = PyDict_GetItemString(typedict, "_type_"); /* Borrowed ref */ - if (proto && -1 == PyCPointerType_SetProto(stgdict, proto)) { - Py_DECREF((PyObject *)stgdict); - return NULL; - } - + proto = _PyDict_GetItemIdWithError(typedict, &PyId__type_); /* Borrowed ref */ if (proto) { - StgDictObject *itemdict = PyType_stgdict(proto); + StgDictObject *itemdict; const char *current_format; + if (-1 == PyCPointerType_SetProto(stgdict, proto)) { + Py_DECREF((PyObject *)stgdict); + return NULL; + } + itemdict = PyType_stgdict(proto); /* PyCPointerType_SetProto has verified proto has a stgdict. */ assert(itemdict); /* If itemdict->format is NULL, then this is a pointer to an @@ -1009,6 +1024,10 @@ PyCPointerType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; } } + else if (PyErr_Occurred()) { + Py_DECREF((PyObject *)stgdict); + return NULL; + } /* create the new instance (which is a class, since we are a metatype!) */ @@ -1034,6 +1053,7 @@ static PyObject * PyCPointerType_set_type(PyTypeObject *self, PyObject *type) { StgDictObject *dict; + _Py_IDENTIFIER(_type_); dict = PyType_stgdict((PyObject *)self); if (!dict) { @@ -1045,7 +1065,7 @@ PyCPointerType_set_type(PyTypeObject *self, PyObject *type) if (-1 == PyCPointerType_SetProto(dict, type)) return NULL; - if (-1 == PyDict_SetItemString((PyObject *)dict, "_type_", type)) + if (-1 == _PyDict_SetItemId((PyObject *)dict, &PyId__type_, type)) return NULL; Py_RETURN_NONE; @@ -1386,6 +1406,8 @@ PyCArrayType_paramfunc(CDataObject *self) static PyObject * PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + _Py_IDENTIFIER(_length_); + _Py_IDENTIFIER(_type_); PyTypeObject *result; StgDictObject *stgdict; StgDictObject *itemdict; @@ -1404,12 +1426,12 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) stgdict = NULL; type_attr = NULL; - length_attr = PyObject_GetAttrString((PyObject *)result, "_length_"); + if (_PyObject_LookupAttrId((PyObject *)result, &PyId__length_, &length_attr) < 0) { + goto error; + } if (!length_attr) { - if (PyErr_ExceptionMatches(PyExc_AttributeError)) { - PyErr_SetString(PyExc_AttributeError, - "class must define a '_length_' attribute"); - } + PyErr_SetString(PyExc_AttributeError, + "class must define a '_length_' attribute"); goto error; } @@ -1437,7 +1459,9 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) goto error; } - type_attr = PyObject_GetAttrString((PyObject *)result, "_type_"); + if (_PyObject_LookupAttrId((PyObject *)result, &PyId__type_, &type_attr) < 0) { + goto error; + } if (!type_attr) { PyErr_SetString(PyExc_AttributeError, "class must define a '_type_' attribute"); @@ -1580,6 +1604,7 @@ static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g"; static PyObject * c_wchar_p_from_param(PyObject *type, PyObject *value) { + _Py_IDENTIFIER(_as_parameter_); PyObject *as_parameter; int res; if (value == Py_None) { @@ -1629,7 +1654,9 @@ c_wchar_p_from_param(PyObject *type, PyObject *value) } } - as_parameter = PyObject_GetAttrString(value, "_as_parameter_"); + if (_PyObject_LookupAttrId(value, &PyId__as_parameter_, &as_parameter) < 0) { + return NULL; + } if (as_parameter) { value = c_wchar_p_from_param(type, as_parameter); Py_DECREF(as_parameter); @@ -1644,6 +1671,7 @@ c_wchar_p_from_param(PyObject *type, PyObject *value) static PyObject * c_char_p_from_param(PyObject *type, PyObject *value) { + _Py_IDENTIFIER(_as_parameter_); PyObject *as_parameter; int res; if (value == Py_None) { @@ -1693,7 +1721,9 @@ c_char_p_from_param(PyObject *type, PyObject *value) } } - as_parameter = PyObject_GetAttrString(value, "_as_parameter_"); + if (_PyObject_LookupAttrId(value, &PyId__as_parameter_, &as_parameter) < 0) { + return NULL; + } if (as_parameter) { value = c_char_p_from_param(type, as_parameter); Py_DECREF(as_parameter); @@ -1708,6 +1738,7 @@ c_char_p_from_param(PyObject *type, PyObject *value) static PyObject * c_void_p_from_param(PyObject *type, PyObject *value) { + _Py_IDENTIFIER(_as_parameter_); StgDictObject *stgd; PyObject *as_parameter; int res; @@ -1829,7 +1860,9 @@ c_void_p_from_param(PyObject *type, PyObject *value) } } - as_parameter = PyObject_GetAttrString(value, "_as_parameter_"); + if (_PyObject_LookupAttrId(value, &PyId__as_parameter_, &as_parameter) < 0) { + return NULL; + } if (as_parameter) { value = c_void_p_from_param(type, as_parameter); Py_DECREF(as_parameter); @@ -1946,6 +1979,7 @@ PyCSimpleType_paramfunc(CDataObject *self) static PyObject * PyCSimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + _Py_IDENTIFIER(_type_); PyTypeObject *result; StgDictObject *stgdict; PyObject *proto; @@ -1960,13 +1994,15 @@ PyCSimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (result == NULL) return NULL; - proto = PyObject_GetAttrString((PyObject *)result, "_type_"); /* new ref */ + if (_PyObject_LookupAttrId((PyObject *)result, &PyId__type_, &proto) < 0) { + return NULL; + } if (!proto) { PyErr_SetString(PyExc_AttributeError, "class must define a '_type_' attribute"); error: Py_XDECREF(proto); - Py_XDECREF(result); + Py_DECREF(result); return NULL; } if (PyUnicode_Check(proto)) { @@ -2128,6 +2164,7 @@ PyCSimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static PyObject * PyCSimpleType_from_param(PyObject *type, PyObject *value) { + _Py_IDENTIFIER(_as_parameter_); StgDictObject *dict; const char *fmt; PyCArgObject *parg; @@ -2171,7 +2208,9 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) PyErr_Clear(); Py_DECREF(parg); - as_parameter = PyObject_GetAttrString(value, "_as_parameter_"); + if (_PyObject_LookupAttrId(value, &PyId__as_parameter_, &as_parameter) < 0) { + return NULL; + } if (as_parameter) { if (Py_EnterRecursiveCall("while processing _as_parameter_")) { Py_DECREF(as_parameter); @@ -2246,6 +2285,7 @@ PyTypeObject PyCSimpleType_Type = { static PyObject * converters_from_argtypes(PyObject *ob) { + _Py_IDENTIFIER(from_param); PyObject *converters; Py_ssize_t i; Py_ssize_t nArgs; @@ -2270,22 +2310,22 @@ converters_from_argtypes(PyObject *ob) */ for (i = 0; i < nArgs; ++i) { + PyObject *cnv; PyObject *tp = PyTuple_GET_ITEM(ob, i); - PyObject *cnv = PyObject_GetAttrString(tp, "from_param"); - if (!cnv) - goto argtypes_error_1; + if (_PyObject_LookupAttrId(tp, &PyId_from_param, &cnv) <= 0) { + Py_DECREF(converters); + Py_DECREF(ob); + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_TypeError, + "item %zd in _argtypes_ has no from_param method", + i+1); + } + return NULL; + } PyTuple_SET_ITEM(converters, i, cnv); } Py_DECREF(ob); return converters; - - argtypes_error_1: - Py_XDECREF(converters); - Py_DECREF(ob); - PyErr_Format(PyExc_TypeError, - "item %zd in _argtypes_ has no from_param method", - i+1); - return NULL; } static int @@ -2293,6 +2333,10 @@ make_funcptrtype_dict(StgDictObject *stgdict) { PyObject *ob; PyObject *converters = NULL; + _Py_IDENTIFIER(_flags_); + _Py_IDENTIFIER(_argtypes_); + _Py_IDENTIFIER(_restype_); + _Py_IDENTIFIER(_check_retval_); stgdict->align = _ctypes_get_fielddesc("P")->pffi_type->alignment; stgdict->length = 1; @@ -2301,26 +2345,31 @@ make_funcptrtype_dict(StgDictObject *stgdict) stgdict->getfunc = NULL; stgdict->ffi_type_pointer = ffi_type_pointer; - ob = PyDict_GetItemString((PyObject *)stgdict, "_flags_"); + ob = _PyDict_GetItemIdWithError((PyObject *)stgdict, &PyId__flags_); if (!ob || !PyLong_Check(ob)) { - PyErr_SetString(PyExc_TypeError, - "class must define _flags_ which must be an integer"); + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_TypeError, + "class must define _flags_ which must be an integer"); + } return -1; } - stgdict->flags = PyLong_AS_LONG(ob) | TYPEFLAG_ISPOINTER; + stgdict->flags = PyLong_AsUnsignedLongMask(ob) | TYPEFLAG_ISPOINTER; /* _argtypes_ is optional... */ - ob = PyDict_GetItemString((PyObject *)stgdict, "_argtypes_"); + ob = _PyDict_GetItemIdWithError((PyObject *)stgdict, &PyId__argtypes_); if (ob) { converters = converters_from_argtypes(ob); if (!converters) - goto error; + return -1; Py_INCREF(ob); stgdict->argtypes = ob; stgdict->converters = converters; } + else if (PyErr_Occurred()) { + return -1; + } - ob = PyDict_GetItemString((PyObject *)stgdict, "_restype_"); + ob = _PyDict_GetItemIdWithError((PyObject *)stgdict, &PyId__restype_); if (ob) { if (ob != Py_None && !PyType_stgdict(ob) && !PyCallable_Check(ob)) { PyErr_SetString(PyExc_TypeError, @@ -2329,12 +2378,17 @@ make_funcptrtype_dict(StgDictObject *stgdict) } Py_INCREF(ob); stgdict->restype = ob; - stgdict->checker = PyObject_GetAttrString(ob, "_check_retval_"); - if (stgdict->checker == NULL) - PyErr_Clear(); + if (_PyObject_LookupAttrId(ob, &PyId__check_retval_, + &stgdict->checker) < 0) + { + return -1; + } + } + else if (PyErr_Occurred()) { + return -1; } /* XXX later, maybe. - ob = PyDict_GetItemString((PyObject *)stgdict, "_errcheck_"); + ob = _PyDict_GetItemIdWithError((PyObject *)stgdict, &PyId__errcheck_); if (ob) { if (!PyCallable_Check(ob)) { PyErr_SetString(PyExc_TypeError, @@ -2344,13 +2398,11 @@ make_funcptrtype_dict(StgDictObject *stgdict) Py_INCREF(ob); stgdict->errcheck = ob; } + else if (PyErr_Occurred()) { + return -1; + } */ return 0; - - error: - Py_XDECREF(converters); - return -1; - } static PyCArgObject * @@ -3085,9 +3137,13 @@ PyCFuncPtr_get_errcheck(PyCFuncPtrObject *self, void *Py_UNUSED(ignored)) static int PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored)) { + _Py_IDENTIFIER(_check_retval_); + PyObject *checker, *oldchecker; if (ob == NULL) { + oldchecker = self->checker; + self->checker = NULL; Py_CLEAR(self->restype); - Py_CLEAR(self->checker); + Py_XDECREF(oldchecker); return 0; } if (ob != Py_None && !PyType_stgdict(ob) && !PyCallable_Check(ob)) { @@ -3095,11 +3151,14 @@ PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ign "restype must be a type, a callable, or None"); return -1; } + if (_PyObject_LookupAttrId(ob, &PyId__check_retval_, &checker) < 0) { + return -1; + } + oldchecker = self->checker; + self->checker = checker; Py_INCREF(ob); Py_XSETREF(self->restype, ob); - Py_XSETREF(self->checker, PyObject_GetAttrString(ob, "_check_retval_")); - if (self->checker == NULL) - PyErr_Clear(); + Py_XDECREF(oldchecker); return 0; } @@ -3526,9 +3585,12 @@ PyCFuncPtr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) like that. */ /* - if (kwds && PyDict_GetItemString(kwds, "options")) { + if (kwds && _PyDict_GetItemIdWithError(kwds, &PyId_options)) { ... } + else if (PyErr_Occurred()) { + return NULL; + } */ dict = PyType_stgdict((PyObject *)type); @@ -3605,10 +3667,16 @@ _get_arg(int *pindex, PyObject *name, PyObject *defval, PyObject *inargs, PyObje Py_INCREF(v); return v; } - if (kwds && name && (v = PyDict_GetItem(kwds, name))) { - ++*pindex; - Py_INCREF(v); - return v; + if (kwds && name) { + v = PyDict_GetItemWithError(kwds, name); + if (v) { + ++*pindex; + Py_INCREF(v); + return v; + } + else if (PyErr_Occurred()) { + return NULL; + } } if (defval) { Py_INCREF(defval); @@ -3685,7 +3753,7 @@ _build_callargs(PyCFuncPtrObject *self, PyObject *argtypes, for (i = 0; i < len; ++i) { PyObject *item = PyTuple_GET_ITEM(paramflags, i); PyObject *ob; - int flag; + unsigned int flag; PyObject *name = NULL; PyObject *defval = NULL; @@ -3693,7 +3761,7 @@ _build_callargs(PyCFuncPtrObject *self, PyObject *argtypes, calls below. */ /* We HAVE already checked that the tuple can be parsed with "i|ZO", so... */ Py_ssize_t tsize = PyTuple_GET_SIZE(item); - flag = PyLong_AS_LONG(PyTuple_GET_ITEM(item, 0)); + flag = PyLong_AsUnsignedLongMask(PyTuple_GET_ITEM(item, 0)); name = tsize > 1 ? PyTuple_GET_ITEM(item, 1) : NULL; defval = tsize > 2 ? PyTuple_GET_ITEM(item, 2) : NULL; @@ -3773,7 +3841,7 @@ _build_callargs(PyCFuncPtrObject *self, PyObject *argtypes, break; default: PyErr_Format(PyExc_ValueError, - "paramflag %d not yet implemented", flag); + "paramflag %u not yet implemented", flag); goto error; break; } @@ -4136,6 +4204,7 @@ _init_pos_args(PyObject *self, PyTypeObject *type, StgDictObject *dict; PyObject *fields; Py_ssize_t i; + _Py_IDENTIFIER(_fields_); if (PyType_stgdict((PyObject *)type->tp_base)) { index = _init_pos_args(self, type->tp_base, @@ -4146,9 +4215,13 @@ _init_pos_args(PyObject *self, PyTypeObject *type, } dict = PyType_stgdict((PyObject *)type); - fields = PyDict_GetItemString((PyObject *)dict, "_fields_"); - if (fields == NULL) + fields = _PyDict_GetItemIdWithError((PyObject *)dict, &PyId__fields_); + if (fields == NULL) { + if (PyErr_Occurred()) { + return -1; + } return index; + } for (i = 0; i < dict->length && (i+index) < PyTuple_GET_SIZE(args); @@ -4164,13 +4237,20 @@ _init_pos_args(PyObject *self, PyTypeObject *type, return -1; } val = PyTuple_GET_ITEM(args, i + index); - if (kwds && PyDict_GetItem(kwds, name)) { - PyErr_Format(PyExc_TypeError, - "duplicate values for field %R", - name); - Py_DECREF(pair); - Py_DECREF(name); - return -1; + if (kwds) { + if (PyDict_GetItemWithError(kwds, name)) { + PyErr_Format(PyExc_TypeError, + "duplicate values for field %R", + name); + Py_DECREF(pair); + Py_DECREF(name); + return -1; + } + else if (PyErr_Occurred()) { + Py_DECREF(pair); + Py_DECREF(name); + return -1; + } } res = PyObject_SetAttr(self, name, val); @@ -4641,6 +4721,10 @@ PyCArrayType_from_ctype(PyObject *itemtype, Py_ssize_t length) Py_DECREF(key); return result; } + else if (PyErr_Occurred()) { + Py_DECREF(key); + return NULL; + } if (!PyType_Check(itemtype)) { PyErr_SetString(PyExc_TypeError, diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c index d579291b62fe..871bc4f49458 100644 --- a/Modules/_ctypes/callbacks.c +++ b/Modules/_ctypes/callbacks.c @@ -107,9 +107,14 @@ static void TryAddRef(StgDictObject *dict, CDataObject *obj) { IUnknown *punk; + _Py_IDENTIFIER(_needs_com_addref_); - if (NULL == PyDict_GetItemString((PyObject *)dict, "_needs_com_addref_")) + if (!_PyDict_GetItemIdWithError((PyObject *)dict, &PyId__needs_com_addref_)) { + if (PyErr_Occurred()) { + PrintError("getting _needs_com_addref_"); + } return; + } punk = *(IUnknown **)obj->b_ptr; if (punk) diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index ad40ca1c5247..1185c9156ae9 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -146,7 +146,7 @@ _ctypes_get_errobj(int **pspace) if (error_object_name == NULL) return NULL; } - errobj = PyDict_GetItem(dict, error_object_name); + errobj = PyDict_GetItemWithError(dict, error_object_name); if (errobj) { if (!PyCapsule_IsValid(errobj, CTYPES_CAPSULE_NAME_PYMEM)) { PyErr_SetString(PyExc_RuntimeError, @@ -155,7 +155,7 @@ _ctypes_get_errobj(int **pspace) } Py_INCREF(errobj); } - else { + else if (!PyErr_Occurred()) { void *space = PyMem_Malloc(sizeof(int) * 2); if (space == NULL) return NULL; @@ -171,6 +171,9 @@ _ctypes_get_errobj(int **pspace) return NULL; } } + else { + return NULL; + } *pspace = (int *)PyCapsule_GetPointer(errobj, CTYPES_CAPSULE_NAME_PYMEM); return errobj; } @@ -685,8 +688,11 @@ static int ConvParam(PyObject *obj, Py_ssize_t index, struct argument *pa) #endif { + _Py_IDENTIFIER(_as_parameter_); PyObject *arg; - arg = PyObject_GetAttrString(obj, "_as_parameter_"); + if (_PyObject_LookupAttrId(obj, &PyId__as_parameter_, &arg) < 0) { + return -1; + } /* Which types should we exactly allow here? integers are required for using Python classes as parameters (they have to expose the '_as_parameter_' @@ -1685,11 +1691,14 @@ POINTER(PyObject *self, PyObject *cls) PyObject *key; char *buf; - result = PyDict_GetItem(_ctypes_ptrtype_cache, cls); + result = PyDict_GetItemWithError(_ctypes_ptrtype_cache, cls); if (result) { Py_INCREF(result); return result; } + else if (PyErr_Occurred()) { + return NULL; + } if (PyUnicode_CheckExact(cls)) { const char *name = PyUnicode_AsUTF8(cls); if (name == NULL) @@ -1745,12 +1754,16 @@ pointer(PyObject *self, PyObject *arg) PyObject *result; PyObject *typ; - typ = PyDict_GetItem(_ctypes_ptrtype_cache, (PyObject *)Py_TYPE(arg)); - if (typ) + typ = PyDict_GetItemWithError(_ctypes_ptrtype_cache, (PyObject *)Py_TYPE(arg)); + if (typ) { return PyObject_CallFunctionObjArgs(typ, arg, NULL); + } + else if (PyErr_Occurred()) { + return NULL; + } typ = POINTER(NULL, (PyObject *)Py_TYPE(arg)); if (typ == NULL) - return NULL; + return NULL; result = PyObject_CallFunctionObjArgs(typ, arg, NULL); Py_DECREF(typ); return result; diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index e2b9aa8ed153..5f194e21550f 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -1147,7 +1147,7 @@ c_set(void *ptr, PyObject *value, Py_ssize_t size) } if (PyLong_Check(value)) { - long longval = PyLong_AS_LONG(value); + long longval = PyLong_AsLong(value); if (longval < 0 || longval >= 256) goto error; *(char *)ptr = (char)longval; diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 25656ff87875..3f8a0316616b 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -281,13 +281,15 @@ MakeFields(PyObject *type, CFieldObject *descr, static int MakeAnonFields(PyObject *type) { + _Py_IDENTIFIER(_anonymous_); PyObject *anon; PyObject *anon_names; Py_ssize_t i; - anon = PyObject_GetAttrString(type, "_anonymous_"); + if (_PyObject_LookupAttrId(type, &PyId__anonymous_, &anon) < 0) { + return -1; + } if (anon == NULL) { - PyErr_Clear(); return 0; } anon_names = PySequence_Fast(anon, "_anonymous_ must be a sequence"); @@ -335,13 +337,17 @@ MakeAnonFields(PyObject *type) int PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct) { + _Py_IDENTIFIER(_swappedbytes_); + _Py_IDENTIFIER(_use_broken_old_ctypes_structure_semantics_); + _Py_IDENTIFIER(_pack_); StgDictObject *stgdict, *basedict; Py_ssize_t len, offset, size, align, i; Py_ssize_t union_size, total_align; Py_ssize_t field_size = 0; int bitofs; - PyObject *isPacked; - int pack = 0; + PyObject *tmp; + int isPacked; + int pack; Py_ssize_t ffi_ofs; int big_endian; @@ -356,32 +362,59 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct if (fields == NULL) return 0; -#ifdef WORDS_BIGENDIAN - big_endian = PyObject_HasAttrString(type, "_swappedbytes_") ? 0 : 1; -#else - big_endian = PyObject_HasAttrString(type, "_swappedbytes_") ? 1 : 0; -#endif + if (_PyObject_LookupAttrId(type, &PyId__swappedbytes_, &tmp) < 0) { + return -1; + } + if (tmp) { + Py_DECREF(tmp); + big_endian = !PY_BIG_ENDIAN; + } + else { + big_endian = PY_BIG_ENDIAN; + } - use_broken_old_ctypes_semantics = \ - PyObject_HasAttrString(type, "_use_broken_old_ctypes_structure_semantics_"); + if (_PyObject_LookupAttrId(type, + &PyId__use_broken_old_ctypes_structure_semantics_, &tmp) < 0) + { + return -1; + } + if (tmp) { + Py_DECREF(tmp); + use_broken_old_ctypes_semantics = 1; + } + else { + use_broken_old_ctypes_semantics = 0; + } - isPacked = PyObject_GetAttrString(type, "_pack_"); - if (isPacked) { - pack = _PyLong_AsInt(isPacked); - if (pack < 0 || PyErr_Occurred()) { - Py_XDECREF(isPacked); - PyErr_SetString(PyExc_ValueError, - "_pack_ must be a non-negative integer"); + if (_PyObject_LookupAttrId(type, &PyId__pack_, &tmp) < 0) { + return -1; + } + if (tmp) { + isPacked = 1; + pack = _PyLong_AsInt(tmp); + Py_DECREF(tmp); + if (pack < 0) { + if (!PyErr_Occurred() || + PyErr_ExceptionMatches(PyExc_TypeError) || + PyErr_ExceptionMatches(PyExc_OverflowError)) + { + PyErr_SetString(PyExc_ValueError, + "_pack_ must be a non-negative integer"); + } return -1; } - Py_DECREF(isPacked); - } else - PyErr_Clear(); + } + else { + isPacked = 0; + pack = 0; + } - len = PySequence_Length(fields); + len = PySequence_Size(fields); if (len == -1) { - PyErr_SetString(PyExc_TypeError, - "'_fields_' must be a sequence of pairs"); + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_SetString(PyExc_TypeError, + "'_fields_' must be a sequence of pairs"); + } return -1; } From webhook-mailer at python.org Wed Dec 5 10:49:40 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 15:49:40 -0000 Subject: [Python-checkins] bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) Message-ID: https://github.com/python/cpython/commit/f2f4555d8287ad217a1dba7bbd93103ad4daf3a8 commit: f2f4555d8287ad217a1dba7bbd93103ad4daf3a8 branch: master author: Victor Stinner committer: GitHub date: 2018-12-05T16:49:35+01:00 summary: bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) * posixpath.expanduser() now returns the input path unchanged if the HOME environment variable is not set and pwd.getpwuid() raises KeyError (the current user identifier doesn't exist in the password database). * Add test_no_home_directory() to test_site. files: A Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst M Lib/posixpath.py M Lib/test/test_posixpath.py M Lib/test/test_site.py diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 7e3f3db4b6d0..4914a1728ab5 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -246,7 +246,12 @@ def expanduser(path): if i == 1: if 'HOME' not in os.environ: import pwd - userhome = pwd.getpwuid(os.getuid()).pw_dir + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path else: userhome = os.environ['HOME'] else: @@ -257,6 +262,8 @@ def expanduser(path): try: pwent = pwd.getpwnam(name) except KeyError: + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged return path userhome = pwent.pw_dir if isinstance(path, bytes): diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index ae59ef5927be..983e2dd6ff27 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -5,6 +5,7 @@ from posixpath import realpath, abspath, dirname, basename from test import support, test_genericpath from test.support import FakePath +from unittest import mock try: import posix @@ -242,42 +243,61 @@ def fake_lstat(path): def test_expanduser(self): self.assertEqual(posixpath.expanduser("foo"), "foo") self.assertEqual(posixpath.expanduser(b"foo"), b"foo") + + def test_expanduser_home_envvar(self): with support.EnvironmentVarGuard() as env: + env['HOME'] = '/home/victor' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + + # expanduser() strips trailing slash + env['HOME'] = '/home/victor/' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + for home in '/', '', '//', '///': with self.subTest(home=home): env['HOME'] = home self.assertEqual(posixpath.expanduser("~"), "/") self.assertEqual(posixpath.expanduser("~/"), "/") self.assertEqual(posixpath.expanduser("~/foo"), "/foo") - try: - import pwd - except ImportError: - pass - else: - self.assertIsInstance(posixpath.expanduser("~/"), str) - self.assertIsInstance(posixpath.expanduser(b"~/"), bytes) - # if home directory == root directory, this test makes no sense - if posixpath.expanduser("~") != '/': - self.assertEqual( - posixpath.expanduser("~") + "/", - posixpath.expanduser("~/") - ) - self.assertEqual( - posixpath.expanduser(b"~") + b"/", - posixpath.expanduser(b"~/") - ) - self.assertIsInstance(posixpath.expanduser("~root/"), str) - self.assertIsInstance(posixpath.expanduser("~foo/"), str) - self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes) - self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes) - - with support.EnvironmentVarGuard() as env: - # expanduser should fall back to using the password database - del env['HOME'] - home = pwd.getpwuid(os.getuid()).pw_dir - # $HOME can end with a trailing /, so strip it (see #17809) - home = home.rstrip("/") or '/' - self.assertEqual(posixpath.expanduser("~"), home) + + def test_expanduser_pwd(self): + pwd = support.import_module('pwd') + + self.assertIsInstance(posixpath.expanduser("~/"), str) + self.assertIsInstance(posixpath.expanduser(b"~/"), bytes) + + # if home directory == root directory, this test makes no sense + if posixpath.expanduser("~") != '/': + self.assertEqual( + posixpath.expanduser("~") + "/", + posixpath.expanduser("~/") + ) + self.assertEqual( + posixpath.expanduser(b"~") + b"/", + posixpath.expanduser(b"~/") + ) + self.assertIsInstance(posixpath.expanduser("~root/"), str) + self.assertIsInstance(posixpath.expanduser("~foo/"), str) + self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes) + self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes) + + with support.EnvironmentVarGuard() as env: + # expanduser should fall back to using the password database + del env['HOME'] + + home = pwd.getpwuid(os.getuid()).pw_dir + # $HOME can end with a trailing /, so strip it (see #17809) + home = home.rstrip("/") or '/' + self.assertEqual(posixpath.expanduser("~"), home) + + # bpo-10496: If the HOME environment variable is not set and the + # user (current identifier or name in the path) doesn't exist in + # the password database (pwd.getuid() or pwd.getpwnam() fail), + # expanduser() must return the path unchanged. + with mock.patch.object(pwd, 'getpwuid', side_effect=KeyError), \ + mock.patch.object(pwd, 'getpwnam', side_effect=KeyError): + for path in ('~', '~/.local', '~vstinner/'): + self.assertEqual(posixpath.expanduser(path), path) def test_normpath(self): self.assertEqual(posixpath.normpath(""), ".") diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 33a8f1a44ccc..f38e8d853ada 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -6,6 +6,7 @@ """ import unittest import test.support +from test import support from test.support import (captured_stderr, TESTFN, EnvironmentVarGuard, change_cwd) import builtins @@ -19,6 +20,7 @@ import subprocess import sysconfig import tempfile +from unittest import mock from copy import copy # These tests are not particularly useful if Python was invoked with -S. @@ -256,6 +258,7 @@ def test_getusersitepackages(self): # the call sets USER_BASE *and* USER_SITE self.assertEqual(site.USER_SITE, user_site) self.assertTrue(user_site.startswith(site.USER_BASE), user_site) + self.assertEqual(site.USER_BASE, site.getuserbase()) def test_getsitepackages(self): site.PREFIXES = ['xoxo'] @@ -274,6 +277,40 @@ def test_getsitepackages(self): wanted = os.path.join('xoxo', 'lib', 'site-packages') self.assertEqual(dirs[1], wanted) + def test_no_home_directory(self): + # bpo-10496: getuserbase() and getusersitepackages() must not fail if + # the current user has no home directory (if expanduser() returns the + # path unchanged). + site.USER_SITE = None + site.USER_BASE = None + + with EnvironmentVarGuard() as environ, \ + mock.patch('os.path.expanduser', lambda path: path): + + del environ['PYTHONUSERBASE'] + del environ['APPDATA'] + + user_base = site.getuserbase() + self.assertTrue(user_base.startswith('~' + os.sep), + user_base) + + user_site = site.getusersitepackages() + self.assertTrue(user_site.startswith(user_base), user_site) + + with mock.patch('os.path.isdir', return_value=False) as mock_isdir, \ + mock.patch.object(site, 'addsitedir') as mock_addsitedir, \ + support.swap_attr(site, 'ENABLE_USER_SITE', True): + + # addusersitepackages() must not add user_site to sys.path + # if it is not an existing directory + known_paths = set() + site.addusersitepackages(known_paths) + + mock_isdir.assert_called_once_with(user_site) + mock_addsitedir.assert_not_called() + self.assertFalse(known_paths) + + class PthFile(object): """Helper class for handling testing of .pth files""" diff --git a/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst new file mode 100644 index 000000000000..232fcc6503b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst @@ -0,0 +1,5 @@ +:func:`posixpath.expanduser` now returns the input *path* unchanged if the +``HOME`` environment variable is not set and the current user has no home +directory (if the current user identifier doesn't exist in the password +database). This change fix the :mod:`site` module if the current user doesn't +exist in the password database (if the user has no home directory). From webhook-mailer at python.org Wed Dec 5 11:08:01 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 16:08:01 -0000 Subject: [Python-checkins] bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) Message-ID: https://github.com/python/cpython/commit/983d1ab0e6f4280f954bcba87db76e11131f1c33 commit: 983d1ab0e6f4280f954bcba87db76e11131f1c33 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T08:07:57-08:00 summary: bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) * posixpath.expanduser() now returns the input path unchanged if the HOME environment variable is not set and pwd.getpwuid() raises KeyError (the current user identifier doesn't exist in the password database). * Add test_no_home_directory() to test_site. (cherry picked from commit f2f4555d8287ad217a1dba7bbd93103ad4daf3a8) Co-authored-by: Victor Stinner files: A Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst M Lib/posixpath.py M Lib/test/test_posixpath.py M Lib/test/test_site.py diff --git a/Lib/posixpath.py b/Lib/posixpath.py index e92186c64e0d..ca578a5df35c 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -246,7 +246,12 @@ def expanduser(path): if i == 1: if 'HOME' not in os.environ: import pwd - userhome = pwd.getpwuid(os.getuid()).pw_dir + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path else: userhome = os.environ['HOME'] else: @@ -257,6 +262,8 @@ def expanduser(path): try: pwent = pwd.getpwnam(name) except KeyError: + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged return path userhome = pwent.pw_dir if isinstance(path, bytes): diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 9476ede53193..e73b31cb648b 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -5,6 +5,7 @@ from posixpath import realpath, abspath, dirname, basename from test import support, test_genericpath from test.support import FakePath +from unittest import mock try: import posix @@ -230,42 +231,61 @@ def fake_lstat(path): def test_expanduser(self): self.assertEqual(posixpath.expanduser("foo"), "foo") self.assertEqual(posixpath.expanduser(b"foo"), b"foo") + + def test_expanduser_home_envvar(self): with support.EnvironmentVarGuard() as env: + env['HOME'] = '/home/victor' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + + # expanduser() strips trailing slash + env['HOME'] = '/home/victor/' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + for home in '/', '', '//', '///': with self.subTest(home=home): env['HOME'] = home self.assertEqual(posixpath.expanduser("~"), "/") self.assertEqual(posixpath.expanduser("~/"), "/") self.assertEqual(posixpath.expanduser("~/foo"), "/foo") - try: - import pwd - except ImportError: - pass - else: - self.assertIsInstance(posixpath.expanduser("~/"), str) - self.assertIsInstance(posixpath.expanduser(b"~/"), bytes) - # if home directory == root directory, this test makes no sense - if posixpath.expanduser("~") != '/': - self.assertEqual( - posixpath.expanduser("~") + "/", - posixpath.expanduser("~/") - ) - self.assertEqual( - posixpath.expanduser(b"~") + b"/", - posixpath.expanduser(b"~/") - ) - self.assertIsInstance(posixpath.expanduser("~root/"), str) - self.assertIsInstance(posixpath.expanduser("~foo/"), str) - self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes) - self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes) - - with support.EnvironmentVarGuard() as env: - # expanduser should fall back to using the password database - del env['HOME'] - home = pwd.getpwuid(os.getuid()).pw_dir - # $HOME can end with a trailing /, so strip it (see #17809) - home = home.rstrip("/") or '/' - self.assertEqual(posixpath.expanduser("~"), home) + + def test_expanduser_pwd(self): + pwd = support.import_module('pwd') + + self.assertIsInstance(posixpath.expanduser("~/"), str) + self.assertIsInstance(posixpath.expanduser(b"~/"), bytes) + + # if home directory == root directory, this test makes no sense + if posixpath.expanduser("~") != '/': + self.assertEqual( + posixpath.expanduser("~") + "/", + posixpath.expanduser("~/") + ) + self.assertEqual( + posixpath.expanduser(b"~") + b"/", + posixpath.expanduser(b"~/") + ) + self.assertIsInstance(posixpath.expanduser("~root/"), str) + self.assertIsInstance(posixpath.expanduser("~foo/"), str) + self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes) + self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes) + + with support.EnvironmentVarGuard() as env: + # expanduser should fall back to using the password database + del env['HOME'] + + home = pwd.getpwuid(os.getuid()).pw_dir + # $HOME can end with a trailing /, so strip it (see #17809) + home = home.rstrip("/") or '/' + self.assertEqual(posixpath.expanduser("~"), home) + + # bpo-10496: If the HOME environment variable is not set and the + # user (current identifier or name in the path) doesn't exist in + # the password database (pwd.getuid() or pwd.getpwnam() fail), + # expanduser() must return the path unchanged. + with mock.patch.object(pwd, 'getpwuid', side_effect=KeyError), \ + mock.patch.object(pwd, 'getpwnam', side_effect=KeyError): + for path in ('~', '~/.local', '~vstinner/'): + self.assertEqual(posixpath.expanduser(path), path) def test_normpath(self): self.assertEqual(posixpath.normpath(""), ".") diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 6cea58d934fb..a30bd2f0067f 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -6,6 +6,7 @@ """ import unittest import test.support +from test import support from test.support import (captured_stderr, TESTFN, EnvironmentVarGuard, change_cwd) import builtins @@ -19,6 +20,7 @@ import subprocess import sysconfig import tempfile +from unittest import mock from copy import copy # These tests are not particularly useful if Python was invoked with -S. @@ -258,6 +260,7 @@ def test_getusersitepackages(self): # the call sets USER_BASE *and* USER_SITE self.assertEqual(site.USER_SITE, user_site) self.assertTrue(user_site.startswith(site.USER_BASE), user_site) + self.assertEqual(site.USER_BASE, site.getuserbase()) def test_getsitepackages(self): site.PREFIXES = ['xoxo'] @@ -276,6 +279,40 @@ def test_getsitepackages(self): wanted = os.path.join('xoxo', 'lib', 'site-packages') self.assertEqual(dirs[1], wanted) + def test_no_home_directory(self): + # bpo-10496: getuserbase() and getusersitepackages() must not fail if + # the current user has no home directory (if expanduser() returns the + # path unchanged). + site.USER_SITE = None + site.USER_BASE = None + + with EnvironmentVarGuard() as environ, \ + mock.patch('os.path.expanduser', lambda path: path): + + del environ['PYTHONUSERBASE'] + del environ['APPDATA'] + + user_base = site.getuserbase() + self.assertTrue(user_base.startswith('~' + os.sep), + user_base) + + user_site = site.getusersitepackages() + self.assertTrue(user_site.startswith(user_base), user_site) + + with mock.patch('os.path.isdir', return_value=False) as mock_isdir, \ + mock.patch.object(site, 'addsitedir') as mock_addsitedir, \ + support.swap_attr(site, 'ENABLE_USER_SITE', True): + + # addusersitepackages() must not add user_site to sys.path + # if it is not an existing directory + known_paths = set() + site.addusersitepackages(known_paths) + + mock_isdir.assert_called_once_with(user_site) + mock_addsitedir.assert_not_called() + self.assertFalse(known_paths) + + class PthFile(object): """Helper class for handling testing of .pth files""" diff --git a/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst new file mode 100644 index 000000000000..232fcc6503b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst @@ -0,0 +1,5 @@ +:func:`posixpath.expanduser` now returns the input *path* unchanged if the +``HOME`` environment variable is not set and the current user has no home +directory (if the current user identifier doesn't exist in the password +database). This change fix the :mod:`site` module if the current user doesn't +exist in the password database (if the user has no home directory). From webhook-mailer at python.org Wed Dec 5 11:21:45 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 16:21:45 -0000 Subject: [Python-checkins] bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) (GH-10925) Message-ID: https://github.com/python/cpython/commit/31b635dbf0c7108f18bb3ce382b895374cff77fb commit: 31b635dbf0c7108f18bb3ce382b895374cff77fb branch: 3.6 author: Victor Stinner committer: GitHub date: 2018-12-05T17:21:37+01:00 summary: bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) (GH-10925) * posixpath.expanduser() now returns the input path unchanged if the HOME environment variable is not set and pwd.getpwuid() raises KeyError (the current user identifier doesn't exist in the password database). * Add test_no_home_directory() to test_site. (cherry picked from commit f2f4555d8287ad217a1dba7bbd93103ad4daf3a8) files: A Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst M Lib/posixpath.py M Lib/test/test_posixpath.py M Lib/test/test_site.py diff --git a/Lib/posixpath.py b/Lib/posixpath.py index e92186c64e0d..ca578a5df35c 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -246,7 +246,12 @@ def expanduser(path): if i == 1: if 'HOME' not in os.environ: import pwd - userhome = pwd.getpwuid(os.getuid()).pw_dir + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path else: userhome = os.environ['HOME'] else: @@ -257,6 +262,8 @@ def expanduser(path): try: pwent = pwd.getpwnam(name) except KeyError: + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged return path userhome = pwent.pw_dir if isinstance(path, bytes): diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 9476ede53193..e73b31cb648b 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -5,6 +5,7 @@ from posixpath import realpath, abspath, dirname, basename from test import support, test_genericpath from test.support import FakePath +from unittest import mock try: import posix @@ -230,42 +231,61 @@ def fake_lstat(path): def test_expanduser(self): self.assertEqual(posixpath.expanduser("foo"), "foo") self.assertEqual(posixpath.expanduser(b"foo"), b"foo") + + def test_expanduser_home_envvar(self): with support.EnvironmentVarGuard() as env: + env['HOME'] = '/home/victor' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + + # expanduser() strips trailing slash + env['HOME'] = '/home/victor/' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + for home in '/', '', '//', '///': with self.subTest(home=home): env['HOME'] = home self.assertEqual(posixpath.expanduser("~"), "/") self.assertEqual(posixpath.expanduser("~/"), "/") self.assertEqual(posixpath.expanduser("~/foo"), "/foo") - try: - import pwd - except ImportError: - pass - else: - self.assertIsInstance(posixpath.expanduser("~/"), str) - self.assertIsInstance(posixpath.expanduser(b"~/"), bytes) - # if home directory == root directory, this test makes no sense - if posixpath.expanduser("~") != '/': - self.assertEqual( - posixpath.expanduser("~") + "/", - posixpath.expanduser("~/") - ) - self.assertEqual( - posixpath.expanduser(b"~") + b"/", - posixpath.expanduser(b"~/") - ) - self.assertIsInstance(posixpath.expanduser("~root/"), str) - self.assertIsInstance(posixpath.expanduser("~foo/"), str) - self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes) - self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes) - - with support.EnvironmentVarGuard() as env: - # expanduser should fall back to using the password database - del env['HOME'] - home = pwd.getpwuid(os.getuid()).pw_dir - # $HOME can end with a trailing /, so strip it (see #17809) - home = home.rstrip("/") or '/' - self.assertEqual(posixpath.expanduser("~"), home) + + def test_expanduser_pwd(self): + pwd = support.import_module('pwd') + + self.assertIsInstance(posixpath.expanduser("~/"), str) + self.assertIsInstance(posixpath.expanduser(b"~/"), bytes) + + # if home directory == root directory, this test makes no sense + if posixpath.expanduser("~") != '/': + self.assertEqual( + posixpath.expanduser("~") + "/", + posixpath.expanduser("~/") + ) + self.assertEqual( + posixpath.expanduser(b"~") + b"/", + posixpath.expanduser(b"~/") + ) + self.assertIsInstance(posixpath.expanduser("~root/"), str) + self.assertIsInstance(posixpath.expanduser("~foo/"), str) + self.assertIsInstance(posixpath.expanduser(b"~root/"), bytes) + self.assertIsInstance(posixpath.expanduser(b"~foo/"), bytes) + + with support.EnvironmentVarGuard() as env: + # expanduser should fall back to using the password database + del env['HOME'] + + home = pwd.getpwuid(os.getuid()).pw_dir + # $HOME can end with a trailing /, so strip it (see #17809) + home = home.rstrip("/") or '/' + self.assertEqual(posixpath.expanduser("~"), home) + + # bpo-10496: If the HOME environment variable is not set and the + # user (current identifier or name in the path) doesn't exist in + # the password database (pwd.getuid() or pwd.getpwnam() fail), + # expanduser() must return the path unchanged. + with mock.patch.object(pwd, 'getpwuid', side_effect=KeyError), \ + mock.patch.object(pwd, 'getpwnam', side_effect=KeyError): + for path in ('~', '~/.local', '~vstinner/'): + self.assertEqual(posixpath.expanduser(path), path) def test_normpath(self): self.assertEqual(posixpath.normpath(""), ".") diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index afdcf36f394d..6964a849335a 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -6,6 +6,7 @@ """ import unittest import test.support +from test import support from test.support import (captured_stderr, TESTFN, EnvironmentVarGuard, change_cwd) import builtins @@ -18,6 +19,7 @@ import shutil import subprocess import sysconfig +from unittest import mock from copy import copy # These tests are not particularly useful if Python was invoked with -S. @@ -243,6 +245,7 @@ def test_getusersitepackages(self): # the call sets USER_BASE *and* USER_SITE self.assertEqual(site.USER_SITE, user_site) self.assertTrue(user_site.startswith(site.USER_BASE), user_site) + self.assertEqual(site.USER_BASE, site.getuserbase()) def test_getsitepackages(self): site.PREFIXES = ['xoxo'] @@ -273,6 +276,41 @@ def test_getsitepackages(self): wanted = os.path.join('xoxo', 'lib', 'site-packages') self.assertEqual(dirs[1], wanted) + def test_no_home_directory(self): + # bpo-10496: getuserbase() and getusersitepackages() must not fail if + # the current user has no home directory (if expanduser() returns the + # path unchanged). + site.USER_SITE = None + site.USER_BASE = None + sysconfig._CONFIG_VARS = None + + with EnvironmentVarGuard() as environ, \ + mock.patch('os.path.expanduser', lambda path: path): + + del environ['PYTHONUSERBASE'] + del environ['APPDATA'] + + user_base = site.getuserbase() + self.assertTrue(user_base.startswith('~' + os.sep), + user_base) + + user_site = site.getusersitepackages() + self.assertTrue(user_site.startswith(user_base), user_site) + + with mock.patch('os.path.isdir', return_value=False) as mock_isdir, \ + mock.patch.object(site, 'addsitedir') as mock_addsitedir, \ + support.swap_attr(site, 'ENABLE_USER_SITE', True): + + # addusersitepackages() must not add user_site to sys.path + # if it is not an existing directory + known_paths = set() + site.addusersitepackages(known_paths) + + mock_isdir.assert_called_once_with(user_site) + mock_addsitedir.assert_not_called() + self.assertFalse(known_paths) + + class PthFile(object): """Helper class for handling testing of .pth files""" diff --git a/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst new file mode 100644 index 000000000000..232fcc6503b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst @@ -0,0 +1,5 @@ +:func:`posixpath.expanduser` now returns the input *path* unchanged if the +``HOME`` environment variable is not set and the current user has no home +directory (if the current user identifier doesn't exist in the password +database). This change fix the :mod:`site` module if the current user doesn't +exist in the password database (if the user has no home directory). From webhook-mailer at python.org Wed Dec 5 12:50:30 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 17:50:30 -0000 Subject: [Python-checkins] bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113) Message-ID: https://github.com/python/cpython/commit/5b25f1d03100e2283c1b129d461ba68ac0169a14 commit: 5b25f1d03100e2283c1b129d461ba68ac0169a14 branch: master author: Sergey Fedoseev committer: Serhiy Storchaka date: 2018-12-05T19:50:26+02:00 summary: bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113) files: A Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst M Lib/sqlite3/test/regression.py M Modules/_sqlite/connection.c diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py index 34cd233535dc..1c59a3cd31c6 100644 --- a/Lib/sqlite3/test/regression.py +++ b/Lib/sqlite3/test/regression.py @@ -256,24 +256,6 @@ def CheckPragmaAutocommit(self): cur.execute("pragma page_size") row = cur.fetchone() - def CheckSetDict(self): - """ - See http://bugs.python.org/issue7478 - - It was possible to successfully register callbacks that could not be - hashed. Return codes of PyDict_SetItem were not checked properly. - """ - class NotHashable: - def __call__(self, *args, **kw): - pass - def __hash__(self): - raise TypeError() - var = NotHashable() - self.assertRaises(TypeError, self.con.create_function, var) - self.assertRaises(TypeError, self.con.create_aggregate, var) - self.assertRaises(TypeError, self.con.set_authorizer, var) - self.assertRaises(TypeError, self.con.set_progress_handler, var) - def CheckConnectionCall(self): """ Call a connection with a non-string SQL request: check error handling @@ -398,9 +380,72 @@ def callback(*args): support.gc_collect() +class UnhashableFunc: + __hash__ = None + + def __init__(self, return_value=None): + self.calls = 0 + self.return_value = return_value + + def __call__(self, *args, **kwargs): + self.calls += 1 + return self.return_value + + +class UnhashableCallbacksTestCase(unittest.TestCase): + """ + https://bugs.python.org/issue34052 + + Registering unhashable callbacks raises TypeError, callbacks are not + registered in SQLite after such registration attempt. + """ + def setUp(self): + self.con = sqlite.connect(':memory:') + + def tearDown(self): + self.con.close() + + def test_progress_handler(self): + f = UnhashableFunc(return_value=0) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.set_progress_handler(f, 1) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_func(self): + func_name = 'func_name' + f = UnhashableFunc() + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.create_function(func_name, 0, f) + msg = 'no such function: %s' % func_name + with self.assertRaisesRegex(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % func_name) + self.assertFalse(f.calls) + + def test_authorizer(self): + f = UnhashableFunc(return_value=sqlite.SQLITE_DENY) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.set_authorizer(f) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_aggr(self): + class UnhashableType(type): + __hash__ = None + aggr_name = 'aggr_name' + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {})) + msg = 'no such function: %s' % aggr_name + with self.assertRaisesRegex(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % aggr_name) + + def suite(): regression_suite = unittest.makeSuite(RegressionTests, "Check") - return unittest.TestSuite((regression_suite,)) + return unittest.TestSuite(( + regression_suite, + unittest.makeSuite(UnhashableCallbacksTestCase), + )) def test(): runner = unittest.TextTestRunner() diff --git a/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst new file mode 100644 index 000000000000..5aa3cc9a81d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst @@ -0,0 +1,7 @@ +:meth:`sqlite3.Connection.create_aggregate`, +:meth:`sqlite3.Connection.create_function`, +:meth:`sqlite3.Connection.set_authorizer`, +:meth:`sqlite3.Connection.set_progress_handler` methods raises TypeError +when unhashable objects are passed as callable. These methods now don't pass +such objects to SQLite API. Previous behavior could lead to segfaults. Patch +by Sergey Fedoseev. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index b59d7d28cfd5..fe0d03bb2b02 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -843,7 +843,9 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec flags |= SQLITE_DETERMINISTIC; #endif } - + if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, narg, @@ -857,12 +859,8 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating function"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -883,17 +881,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje return NULL; } + if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source) @@ -1006,17 +1003,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P return NULL; } + if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) { + return NULL; + } rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb); - if (rc != SQLITE_OK) { PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -1039,9 +1034,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s /* None clears the progress handler previously set */ sqlite3_progress_handler(self->db, 0, 0, (void*)0); } else { - sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1) return NULL; + sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); } Py_RETURN_NONE; From webhook-mailer at python.org Wed Dec 5 13:29:26 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 18:29:26 -0000 Subject: [Python-checkins] bpo-34987: Fix a possible null pointer dereference in _pickle.c's save_reduce(). (GH-9886) Message-ID: https://github.com/python/cpython/commit/25d389789c59a52a31770f7c50ce9e02a8909190 commit: 25d389789c59a52a31770f7c50ce9e02a8909190 branch: master author: Zackery Spytz committer: Serhiy Storchaka date: 2018-12-05T20:29:20+02:00 summary: bpo-34987: Fix a possible null pointer dereference in _pickle.c's save_reduce(). (GH-9886) files: M Modules/_pickle.c diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 39f8b750ed64..c4fe349187c4 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -3840,7 +3840,10 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj) if (obj != NULL) { obj_class = get_class(obj); - p = obj_class != cls; /* true iff a problem */ + if (obj_class == NULL) { + return -1; + } + p = obj_class != cls; Py_DECREF(obj_class); if (p) { PyErr_SetString(st->PicklingError, "args[0] from " From webhook-mailer at python.org Wed Dec 5 14:10:27 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 19:10:27 -0000 Subject: [Python-checkins] bpo-34604: Use %R because of invisible characters or trailing whitespaces. (GH-9165) Message-ID: https://github.com/python/cpython/commit/34c7f0c04e2b4e715b2c3df1875af8939fbe7d0b commit: 34c7f0c04e2b4e715b2c3df1875af8939fbe7d0b branch: master author: William Grzybowski committer: Serhiy Storchaka date: 2018-12-05T21:10:18+02:00 summary: bpo-34604: Use %R because of invisible characters or trailing whitespaces. (GH-9165) files: M Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst M Modules/grpmodule.c M Modules/pwdmodule.c diff --git a/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst b/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst index 562a69124b3d..958b74fd0da6 100644 --- a/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst +++ b/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst @@ -1,2 +1,3 @@ Fix possible mojibake in the error message of `pwd.getpwnam` and -`grp.getgrnam`. Patch by William Grzybowski. +`grp.getgrnam` using string representation because of invisible characters +or trailing whitespaces. Patch by William Grzybowski. diff --git a/Modules/grpmodule.c b/Modules/grpmodule.c index d426f083111e..ab766b985093 100644 --- a/Modules/grpmodule.c +++ b/Modules/grpmodule.c @@ -247,7 +247,7 @@ grp_getgrnam_impl(PyObject *module, PyObject *name) PyErr_NoMemory(); } else { - PyErr_Format(PyExc_KeyError, "getgrnam(): name not found: %S", name); + PyErr_Format(PyExc_KeyError, "getgrnam(): name not found: %R", name); } goto out; } diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index fd11f848b2ce..e0232b8d589b 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -257,7 +257,7 @@ pwd_getpwnam_impl(PyObject *module, PyObject *name) } else { PyErr_Format(PyExc_KeyError, - "getpwnam(): name not found: %S", name); + "getpwnam(): name not found: %R", name); } goto out; } From webhook-mailer at python.org Wed Dec 5 14:32:26 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 19:32:26 -0000 Subject: [Python-checkins] bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) Message-ID: https://github.com/python/cpython/commit/54fd45505b3a365e6d53441e6dd7e0d1ec13b46f commit: 54fd45505b3a365e6d53441e6dd7e0d1ec13b46f branch: master author: Alex H <1884912+lajarre at users.noreply.github.com> committer: Serhiy Storchaka date: 2018-12-05T21:32:16+02:00 summary: bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) When running test_bdb.py as a script, `import test_module` would be importing the existing Lib/test/test_modules.py instead of the tempcwd/test_module.py module which was dynamically created by test_bdb.py itself. files: M Lib/test/test_bdb.py diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index 8a9c4dd4376c..751dd9f69597 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -549,11 +549,11 @@ def create_modules(modules): def break_in_func(funcname, fname=__file__, temporary=False, cond=None): return 'break', (fname, None, temporary, cond, funcname) -TEST_MODULE = 'test_module' +TEST_MODULE = 'test_module_for_bdb' TEST_MODULE_FNAME = TEST_MODULE + '.py' def tfunc_import(): - import test_module - test_module.main() + import test_module_for_bdb + test_module_for_bdb.main() def tfunc_main(): lno = 2 @@ -971,9 +971,9 @@ def main(): ('return', 3, 'main'), ('step', ), ('return', 1, ''), ('quit', ), ] - import test_module + import test_module_for_bdb with TracerRun(self) as tracer: - tracer.runeval('test_module.main()', globals(), locals()) + tracer.runeval('test_module_for_bdb.main()', globals(), locals()) class IssuesTestCase(BaseTestCase): """Test fixed bdb issues.""" @@ -983,7 +983,7 @@ def test_step_at_return_with_no_trace_in_caller(self): # Check that the tracer does step into the caller frame when the # trace function is not set in that frame. code_1 = """ - from test_module_2 import func + from test_module_for_bdb_2 import func def main(): func() lno = 5 @@ -994,12 +994,12 @@ def func(): """ modules = { TEST_MODULE: code_1, - 'test_module_2': code_2, + 'test_module_for_bdb_2': code_2, } with create_modules(modules): self.expect_set = [ ('line', 2, 'tfunc_import'), - break_in_func('func', 'test_module_2.py'), + break_in_func('func', 'test_module_for_bdb_2.py'), ('None', 2, 'tfunc_import'), ('continue', ), ('line', 3, 'func', ({1:1}, [])), ('step', ), ('return', 3, 'func'), ('step', ), From webhook-mailer at python.org Wed Dec 5 14:35:46 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 19:35:46 -0000 Subject: [Python-checkins] bpo-34987: Fix a possible null pointer dereference in _pickle.c's save_reduce(). (GH-9886) Message-ID: https://github.com/python/cpython/commit/e2f376f284b7bf1388d85e99dce646cabc507016 commit: e2f376f284b7bf1388d85e99dce646cabc507016 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T11:35:41-08:00 summary: bpo-34987: Fix a possible null pointer dereference in _pickle.c's save_reduce(). (GH-9886) (cherry picked from commit 25d389789c59a52a31770f7c50ce9e02a8909190) Co-authored-by: Zackery Spytz files: M Modules/_pickle.c diff --git a/Modules/_pickle.c b/Modules/_pickle.c index a1a90bd95cae..57bb771addde 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -3838,7 +3838,10 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj) if (obj != NULL) { obj_class = get_class(obj); - p = obj_class != cls; /* true iff a problem */ + if (obj_class == NULL) { + return -1; + } + p = obj_class != cls; Py_DECREF(obj_class); if (p) { PyErr_SetString(st->PicklingError, "args[0] from " From webhook-mailer at python.org Wed Dec 5 14:35:51 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 19:35:51 -0000 Subject: [Python-checkins] bpo-34987: Fix a possible null pointer dereference in _pickle.c's save_reduce(). (GH-9886) Message-ID: https://github.com/python/cpython/commit/92d912c344e6c21de46da29f0dc45b7e476fa79d commit: 92d912c344e6c21de46da29f0dc45b7e476fa79d branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T11:35:47-08:00 summary: bpo-34987: Fix a possible null pointer dereference in _pickle.c's save_reduce(). (GH-9886) (cherry picked from commit 25d389789c59a52a31770f7c50ce9e02a8909190) Co-authored-by: Zackery Spytz files: M Modules/_pickle.c diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 389bb6701c5f..8c8163560f11 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -3743,7 +3743,10 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj) if (obj != NULL) { obj_class = get_class(obj); - p = obj_class != cls; /* true iff a problem */ + if (obj_class == NULL) { + return -1; + } + p = obj_class != cls; Py_DECREF(obj_class); if (p) { PyErr_SetString(st->PicklingError, "args[0] from " From webhook-mailer at python.org Wed Dec 5 14:42:50 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 19:42:50 -0000 Subject: [Python-checkins] Move __missing__ after __delitem__ in Data model. (GH-10923) Message-ID: https://github.com/python/cpython/commit/1ce853f37783575e2b3aaa159ddcebc8660830ef commit: 1ce853f37783575e2b3aaa159ddcebc8660830ef branch: master author: Andre Delfino committer: Serhiy Storchaka date: 2018-12-05T21:42:44+02:00 summary: Move __missing__ after __delitem__ in Data model. (GH-10923) files: M Doc/reference/datamodel.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 7e2d2e66cac8..b20b708e9575 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2146,6 +2146,8 @@ through the container; for mappings, :meth:`__iter__` should be the same as .. versionadded:: 3.4 +.. index:: object: slice + .. note:: Slicing is done exclusively with the following three methods. A call like :: @@ -2161,8 +2163,6 @@ through the container; for mappings, :meth:`__iter__` should be the same as .. method:: object.__getitem__(self, key) - .. index:: object: slice - Called to implement evaluation of ``self[key]``. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence @@ -2178,12 +2178,6 @@ through the container; for mappings, :meth:`__iter__` should be the same as indexes to allow proper detection of the end of the sequence. -.. method:: object.__missing__(self, key) - - Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses - when key is not in the dictionary. - - .. method:: object.__setitem__(self, key, value) Called to implement assignment to ``self[key]``. Same note as for @@ -2202,6 +2196,12 @@ through the container; for mappings, :meth:`__iter__` should be the same as values as for the :meth:`__getitem__` method. +.. method:: object.__missing__(self, key) + + Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses + when key is not in the dictionary. + + .. method:: object.__iter__(self) This method is called when an iterator is required for a container. This method From webhook-mailer at python.org Wed Dec 5 14:45:34 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 19:45:34 -0000 Subject: [Python-checkins] Correct a couple of unbalanced parenthesis. (GH-10779) Message-ID: https://github.com/python/cpython/commit/55f41e45b4318cbe19209f5144641344d0049fb8 commit: 55f41e45b4318cbe19209f5144641344d0049fb8 branch: master author: Andre Delfino committer: Serhiy Storchaka date: 2018-12-05T21:45:30+02:00 summary: Correct a couple of unbalanced parenthesis. (GH-10779) files: M Doc/c-api/buffer.rst M Doc/faq/extending.rst M Doc/library/crypt.rst M Doc/library/email.compat32-message.rst M Doc/library/email.message.rst M Doc/library/email.parser.rst M Doc/library/exceptions.rst M Doc/library/stdtypes.rst M Doc/library/sysconfig.rst M Doc/reference/expressions.rst M Doc/using/windows.rst diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index 5a9a46fc67e8..33abb5bb94d9 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -198,7 +198,7 @@ a buffer, see :c:func:`PyObject_GetBuffer`. indicates that no de-referencing should occur (striding in a contiguous memory block). - If all suboffsets are negative (i.e. no de-referencing is needed, then + If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value). This type of array representation is used by the Python Imaging Library diff --git a/Doc/faq/extending.rst b/Doc/faq/extending.rst index b611bb48012a..74e1af6ef24d 100644 --- a/Doc/faq/extending.rst +++ b/Doc/faq/extending.rst @@ -277,7 +277,7 @@ However sometimes you have to run the embedded Python interpreter in the same thread as your rest application and you can't allow the :c:func:`PyRun_InteractiveLoop` to stop while waiting for user input. The one solution then is to call :c:func:`PyParser_ParseString` and test for ``e.error`` -equal to ``E_EOF``, which means the input is incomplete). Here's a sample code +equal to ``E_EOF``, which means the input is incomplete. Here's a sample code fragment, untested, inspired by code from Alex Farber:: #include diff --git a/Doc/library/crypt.rst b/Doc/library/crypt.rst index dd62cb32b9d1..43d4b5b749e4 100644 --- a/Doc/library/crypt.rst +++ b/Doc/library/crypt.rst @@ -91,7 +91,7 @@ The :mod:`crypt` module defines the following functions: may be available on all platforms), or a full encrypted password including salt, as returned by this function. If *salt* is not provided, the strongest method will be used (as returned by - :func:`methods`. + :func:`methods`). Checking a password is usually done by passing the plain-text password as *word* and the full results of a previous :func:`crypt` call, diff --git a/Doc/library/email.compat32-message.rst b/Doc/library/email.compat32-message.rst index d88495089482..f02b35b910a0 100644 --- a/Doc/library/email.compat32-message.rst +++ b/Doc/library/email.compat32-message.rst @@ -152,7 +152,7 @@ Here are the methods of the :class:`Message` class: Return ``True`` if the message's payload is a list of sub-\ :class:`Message` objects, otherwise return ``False``. When :meth:`is_multipart` returns ``False``, the payload should be a string - object (which might be a CTE encoded binary payload. (Note that + object (which might be a CTE encoded binary payload). (Note that :meth:`is_multipart` returning ``True`` does not necessarily mean that "msg.get_content_maintype() == 'multipart'" will return the ``True``. For example, ``is_multipart`` will return ``True`` when the diff --git a/Doc/library/email.message.rst b/Doc/library/email.message.rst index ff5404553e96..77b8099a7d4c 100644 --- a/Doc/library/email.message.rst +++ b/Doc/library/email.message.rst @@ -92,7 +92,7 @@ message objects. .. method:: __str__() - Equivalent to `as_string(policy=self.policy.clone(utf8=True)`. Allows + Equivalent to ``as_string(policy=self.policy.clone(utf8=True))``. Allows ``str(msg)`` to produce a string containing the serialized message in a readable format. @@ -379,7 +379,7 @@ message objects. Note that existing parameter values of headers may be accessed through the :attr:`~email.headerregistry.BaseHeader.params` attribute of the - header value (for example, ``msg['Content-Type'].params['charset']``. + header value (for example, ``msg['Content-Type'].params['charset']``). .. versionchanged:: 3.4 ``replace`` keyword was added. @@ -679,7 +679,7 @@ message objects. specified by the current :mod:`~email.policy`. If the added part has no :mailheader:`Content-Disposition` header, add one with the value ``attachment``. This method can be used both for explicit attachments - (:mailheader:`Content-Disposition: attachment` and ``inline`` attachments + (:mailheader:`Content-Disposition: attachment`) and ``inline`` attachments (:mailheader:`Content-Disposition: inline`), by passing appropriate options to the ``content_manager``. diff --git a/Doc/library/email.parser.rst b/Doc/library/email.parser.rst index 49b4e992708a..d9a61616bbbd 100644 --- a/Doc/library/email.parser.rst +++ b/Doc/library/email.parser.rst @@ -164,7 +164,7 @@ message body, instead setting the payload to the raw body. envelope header. The header block is terminated either by the end of the data or by a blank line. Following the header block is the body of the message (which may contain MIME-encoded subparts, including subparts - with a :mailheader:`Content-Transfer-Encoding` of ``8bit``. + with a :mailheader:`Content-Transfer-Encoding` of ``8bit``). Optional *headersonly* is a flag specifying whether to stop parsing after reading the headers or not. The default is ``False``, meaning it parses diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index e33b88673f75..57ed29145816 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -52,7 +52,7 @@ will be set as :attr:`__cause__` on the raised exception. Setting :attr:`__cause__` also implicitly sets the :attr:`__suppress_context__` attribute to ``True``, so that using ``raise new_exc from None`` effectively replaces the old exception with the new one for display -purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`, while +purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`), while leaving the old exception available in :attr:`__context__` for introspection when debugging. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 3375f64d94d2..6ddf41a74a4c 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -443,7 +443,7 @@ Notes: (4) Performing these calculations with at least one extra sign extension bit in a finite two's complement representation (a working bit-width of - ``1 + max(x.bit_length(), y.bit_length()`` or more) is sufficient to get the + ``1 + max(x.bit_length(), y.bit_length())`` or more) is sufficient to get the same result as if there were an infinite number of sign bits. @@ -3222,7 +3222,7 @@ place, and instead produce new objects. Return a copy of the sequence left filled with ASCII ``b'0'`` digits to make a sequence of length *width*. A leading sign prefix (``b'+'``/ - ``b'-'`` is handled by inserting the padding *after* the sign character + ``b'-'``) is handled by inserting the padding *after* the sign character rather than before. For :class:`bytes` objects, the original sequence is returned if *width* is less than or equal to ``len(seq)``. diff --git a/Doc/library/sysconfig.rst b/Doc/library/sysconfig.rst index 9f549fbc5130..b5a1da80c686 100644 --- a/Doc/library/sysconfig.rst +++ b/Doc/library/sysconfig.rst @@ -185,7 +185,7 @@ Other functions Windows will return one of: - - win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + - win-amd64 (64bit Windows on AMD64, aka x86_64, Intel64, and EM64T) - win32 (all others - specifically, sys.platform is returned) Mac OS X can return: diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index fb27857a6f42..571e6fa191c8 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1639,7 +1639,7 @@ returned; otherwise, *y* is evaluated and the resulting value is returned. The expression ``x or y`` first evaluates *x*; if *x* is true, its value is returned; otherwise, *y* is evaluated and the resulting value is returned. -(Note that neither :keyword:`and` nor :keyword:`or` restrict the value and type +Note that neither :keyword:`and` nor :keyword:`or` restrict the value and type they return to ``False`` and ``True``, but rather return the last evaluated argument. This is sometimes useful, e.g., if ``s`` is a string that should be replaced by a default value if it is empty, the expression ``s or 'foo'`` yields diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index e93fa0a78d1f..296d51b5a587 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -613,11 +613,11 @@ user's "application data" directory (i.e. the directory returned by calling the Windows function ``SHGetFolderPath`` with ``CSIDL_LOCAL_APPDATA``) and ``py.ini`` in the same directory as the launcher. The same .ini files are used for both the 'console' version of the launcher (i.e. py.exe) and for the 'windows' version -(i.e. pyw.exe) +(i.e. pyw.exe). Customization specified in the "application directory" will have precedence over the one next to the executable, so a user, who may not have write access to the -.ini file next to the launcher, can override commands in that global .ini file) +.ini file next to the launcher, can override commands in that global .ini file. Customizing default Python versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From webhook-mailer at python.org Wed Dec 5 14:46:29 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 19:46:29 -0000 Subject: [Python-checkins] bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419) Message-ID: https://github.com/python/cpython/commit/67a93b3a0b3814e97ef9d077b21325fc8ce351b2 commit: 67a93b3a0b3814e97ef9d077b21325fc8ce351b2 branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-05T21:46:25+02:00 summary: bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419) files: A Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst M Lib/distutils/archive_util.py M Lib/distutils/tests/test_archive_util.py M Lib/distutils/tests/test_bdist_dumb.py M Lib/distutils/tests/test_sdist.py diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py index 78ae5757c36d..b002dc3b8456 100644 --- a/Lib/distutils/archive_util.py +++ b/Lib/distutils/archive_util.py @@ -166,7 +166,15 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): zip = zipfile.ZipFile(zip_filename, "w", compression=zipfile.ZIP_STORED) + if base_dir != os.curdir: + path = os.path.normpath(os.path.join(base_dir, '')) + zip.write(path, path) + log.info("adding '%s'", path) for dirpath, dirnames, filenames in os.walk(base_dir): + for name in dirnames: + path = os.path.normpath(os.path.join(dirpath, name, '')) + zip.write(path, path) + log.info("adding '%s'", path) for name in filenames: path = os.path.normpath(os.path.join(dirpath, name)) if os.path.isfile(path): diff --git a/Lib/distutils/tests/test_archive_util.py b/Lib/distutils/tests/test_archive_util.py index 14ba4ca34b4a..e9aad0e40fd1 100644 --- a/Lib/distutils/tests/test_archive_util.py +++ b/Lib/distutils/tests/test_archive_util.py @@ -122,12 +122,13 @@ def _tarinfo(self, path): try: names = tar.getnames() names.sort() - return tuple(names) + return names finally: tar.close() - _created_files = ('dist', 'dist/file1', 'dist/file2', - 'dist/sub', 'dist/sub/file3', 'dist/sub2') + _zip_created_files = ['dist/', 'dist/file1', 'dist/file2', + 'dist/sub/', 'dist/sub/file3', 'dist/sub2/'] + _created_files = [p.rstrip('/') for p in _zip_created_files] def _create_files(self): # creating something to tar @@ -244,8 +245,7 @@ def test_make_zipfile(self): tarball = base_name + '.zip' self.assertTrue(os.path.exists(tarball)) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), - ['dist/file1', 'dist/file2', 'dist/sub/file3']) + self.assertEqual(sorted(zf.namelist()), self._zip_created_files) @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') def test_make_zipfile_no_zlib(self): @@ -271,8 +271,7 @@ def fake_zipfile(*a, **kw): [((tarball, "w"), {'compression': zipfile.ZIP_STORED})]) self.assertTrue(os.path.exists(tarball)) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), - ['dist/file1', 'dist/file2', 'dist/sub/file3']) + self.assertEqual(sorted(zf.namelist()), self._zip_created_files) def test_check_archive_formats(self): self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']), diff --git a/Lib/distutils/tests/test_bdist_dumb.py b/Lib/distutils/tests/test_bdist_dumb.py index c8ccdc2383de..01a233bce37f 100644 --- a/Lib/distutils/tests/test_bdist_dumb.py +++ b/Lib/distutils/tests/test_bdist_dumb.py @@ -84,7 +84,7 @@ def test_simple_built(self): finally: fp.close() - contents = sorted(os.path.basename(fn) for fn in contents) + contents = sorted(filter(None, map(os.path.basename, contents))) wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py'] if not sys.dont_write_bytecode: wanted.append('foo.%s.pyc' % sys.implementation.cache_tag) diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py index 5444b815a8b2..23db1269591d 100644 --- a/Lib/distutils/tests/test_sdist.py +++ b/Lib/distutils/tests/test_sdist.py @@ -128,7 +128,9 @@ def test_prune_file_list(self): zip_file.close() # making sure everything has been pruned correctly - self.assertEqual(len(content), 4) + expected = ['', 'PKG-INFO', 'README', 'setup.py', + 'somecode/', 'somecode/__init__.py'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') @unittest.skipIf(find_executable('tar') is None, @@ -226,7 +228,13 @@ def test_add_defaults(self): zip_file.close() # making sure everything was added - self.assertEqual(len(content), 12) + expected = ['', 'PKG-INFO', 'README', 'buildout.cfg', + 'data/', 'data/data.dt', 'inroot.txt', + 'scripts/', 'scripts/script.py', 'setup.py', + 'some/', 'some/file.txt', 'some/other_file.txt', + 'somecode/', 'somecode/__init__.py', 'somecode/doc.dat', + 'somecode/doc.txt'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) # checking the MANIFEST f = open(join(self.tmp_dir, 'MANIFEST')) diff --git a/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst new file mode 100644 index 000000000000..c3f402d39ae0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst @@ -0,0 +1,2 @@ +ZIP files created by :mod:`distutils` will now include entries for +directories. From webhook-mailer at python.org Wed Dec 5 14:54:47 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 19:54:47 -0000 Subject: [Python-checkins] bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) Message-ID: https://github.com/python/cpython/commit/c7c4e938b98068e8e4e5fe56d441db696d47de78 commit: c7c4e938b98068e8e4e5fe56d441db696d47de78 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T11:54:42-08:00 summary: bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) When running test_bdb.py as a script, `import test_module` would be importing the existing Lib/test/test_modules.py instead of the tempcwd/test_module.py module which was dynamically created by test_bdb.py itself. (cherry picked from commit 54fd45505b3a365e6d53441e6dd7e0d1ec13b46f) Co-authored-by: Alex H <1884912+lajarre at users.noreply.github.com> files: M Lib/test/test_bdb.py diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index 616c3a864984..55f42e2535b7 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -549,11 +549,11 @@ def create_modules(modules): def break_in_func(funcname, fname=__file__, temporary=False, cond=None): return 'break', (fname, None, temporary, cond, funcname) -TEST_MODULE = 'test_module' +TEST_MODULE = 'test_module_for_bdb' TEST_MODULE_FNAME = TEST_MODULE + '.py' def tfunc_import(): - import test_module - test_module.main() + import test_module_for_bdb + test_module_for_bdb.main() def tfunc_main(): lno = 2 @@ -971,9 +971,9 @@ def main(): ('return', 3, 'main'), ('step', ), ('return', 1, ''), ('quit', ), ] - import test_module + import test_module_for_bdb with TracerRun(self) as tracer: - tracer.runeval('test_module.main()', globals(), locals()) + tracer.runeval('test_module_for_bdb.main()', globals(), locals()) class IssuesTestCase(BaseTestCase): """Test fixed bdb issues.""" @@ -983,7 +983,7 @@ def test_step_at_return_with_no_trace_in_caller(self): # Check that the tracer does step into the caller frame when the # trace function is not set in that frame. code_1 = """ - from test_module_2 import func + from test_module_for_bdb_2 import func def main(): func() lno = 5 @@ -994,12 +994,12 @@ def func(): """ modules = { TEST_MODULE: code_1, - 'test_module_2': code_2, + 'test_module_for_bdb_2': code_2, } with create_modules(modules): self.expect_set = [ ('line', 2, 'tfunc_import'), - break_in_func('func', 'test_module_2.py'), + break_in_func('func', 'test_module_for_bdb_2.py'), ('None', 2, 'tfunc_import'), ('continue', ), ('line', 3, 'func', ({1:1}, [])), ('step', ), ('return', 3, 'func'), ('step', ), From webhook-mailer at python.org Wed Dec 5 15:05:01 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 20:05:01 -0000 Subject: [Python-checkins] bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) Message-ID: https://github.com/python/cpython/commit/bacc272afc165df21c607aae4ff7bfa21ae1979d commit: bacc272afc165df21c607aae4ff7bfa21ae1979d branch: 2.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T12:04:57-08:00 summary: bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) When running test_bdb.py as a script, `import test_module` would be importing the existing Lib/test/test_modules.py instead of the tempcwd/test_module.py module which was dynamically created by test_bdb.py itself. (cherry picked from commit 54fd45505b3a365e6d53441e6dd7e0d1ec13b46f) Co-authored-by: Alex H <1884912+lajarre at users.noreply.github.com> files: M Lib/test/test_bdb.py diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index d3ff8fc76860..3ad70e21c878 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -569,11 +569,11 @@ def create_modules(modules): def break_in_func(funcname, fname=__file__, temporary=False, cond=None): return 'break', (fname, None, temporary, cond, funcname) -TEST_MODULE = 'test_module' +TEST_MODULE = 'test_module_for_bdb' TEST_MODULE_FNAME = TEST_MODULE + '.py' def tfunc_import(): - import test_module - test_module.main() + import test_module_for_bdb + test_module_for_bdb.main() def tfunc_main(): lno = 2 @@ -985,9 +985,9 @@ def main(): ('return', 3, 'main'), ('step', ), ('return', 1, ''), ('quit', ), ] - import test_module + import test_module_for_bdb with TracerRun(self) as tracer: - tracer.runeval('test_module.main()', globals(), locals()) + tracer.runeval('test_module_for_bdb.main()', globals(), locals()) class IssuesTestCase(BaseTestCase): """Test fixed bdb issues.""" @@ -997,7 +997,7 @@ def test_step_at_return_with_no_trace_in_caller(self): # Check that the tracer does step into the caller frame when the # trace function is not set in that frame. code_1 = """ - from test_module_2 import func + from test_module_for_bdb_2 import func def main(): func() lno = 5 @@ -1008,12 +1008,12 @@ def func(): """ modules = { TEST_MODULE: code_1, - 'test_module_2': code_2, + 'test_module_for_bdb_2': code_2, } with create_modules(modules): self.expect_set = [ ('line', 2, 'tfunc_import'), - break_in_func('func', 'test_module_2.py'), + break_in_func('func', 'test_module_for_bdb_2.py'), ('None', 2, 'tfunc_import'), ('continue', ), ('line', 3, 'func', ({1:1}, [])), ('step', ), ('return', 3, 'func'), ('step', ), From webhook-mailer at python.org Wed Dec 5 15:11:19 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 20:11:19 -0000 Subject: [Python-checkins] Move __missing__ after __delitem__ in Data model. (GH-10923) Message-ID: https://github.com/python/cpython/commit/d2c7c1f7681bee77b221fb645a15a548bb3dd745 commit: d2c7c1f7681bee77b221fb645a15a548bb3dd745 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T12:11:16-08:00 summary: Move __missing__ after __delitem__ in Data model. (GH-10923) (cherry picked from commit 1ce853f37783575e2b3aaa159ddcebc8660830ef) Co-authored-by: Andre Delfino files: M Doc/reference/datamodel.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 02319509ca37..e6fa8a0d2a6f 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2147,6 +2147,8 @@ through the container; for mappings, :meth:`__iter__` should be the same as .. versionadded:: 3.4 +.. index:: object: slice + .. note:: Slicing is done exclusively with the following three methods. A call like :: @@ -2162,8 +2164,6 @@ through the container; for mappings, :meth:`__iter__` should be the same as .. method:: object.__getitem__(self, key) - .. index:: object: slice - Called to implement evaluation of ``self[key]``. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence @@ -2179,12 +2179,6 @@ through the container; for mappings, :meth:`__iter__` should be the same as indexes to allow proper detection of the end of the sequence. -.. method:: object.__missing__(self, key) - - Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses - when key is not in the dictionary. - - .. method:: object.__setitem__(self, key, value) Called to implement assignment to ``self[key]``. Same note as for @@ -2203,6 +2197,12 @@ through the container; for mappings, :meth:`__iter__` should be the same as values as for the :meth:`__getitem__` method. +.. method:: object.__missing__(self, key) + + Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses + when key is not in the dictionary. + + .. method:: object.__iter__(self) This method is called when an iterator is required for a container. This method From webhook-mailer at python.org Wed Dec 5 15:13:37 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 20:13:37 -0000 Subject: [Python-checkins] Move __missing__ after __delitem__ in Data model. (GH-10923) Message-ID: https://github.com/python/cpython/commit/9b3d6a1f9772e810afaa0dca106bd0e4d5f4be9f commit: 9b3d6a1f9772e810afaa0dca106bd0e4d5f4be9f branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T12:13:34-08:00 summary: Move __missing__ after __delitem__ in Data model. (GH-10923) (cherry picked from commit 1ce853f37783575e2b3aaa159ddcebc8660830ef) Co-authored-by: Andre Delfino files: M Doc/reference/datamodel.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index abf06357e7f9..143376bc5ce0 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2045,6 +2045,8 @@ through the container; for mappings, :meth:`__iter__` should be the same as .. versionadded:: 3.4 +.. index:: object: slice + .. note:: Slicing is done exclusively with the following three methods. A call like :: @@ -2060,8 +2062,6 @@ through the container; for mappings, :meth:`__iter__` should be the same as .. method:: object.__getitem__(self, key) - .. index:: object: slice - Called to implement evaluation of ``self[key]``. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence @@ -2077,12 +2077,6 @@ through the container; for mappings, :meth:`__iter__` should be the same as indexes to allow proper detection of the end of the sequence. -.. method:: object.__missing__(self, key) - - Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses - when key is not in the dictionary. - - .. method:: object.__setitem__(self, key, value) Called to implement assignment to ``self[key]``. Same note as for @@ -2101,6 +2095,12 @@ through the container; for mappings, :meth:`__iter__` should be the same as values as for the :meth:`__getitem__` method. +.. method:: object.__missing__(self, key) + + Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses + when key is not in the dictionary. + + .. method:: object.__iter__(self) This method is called when an iterator is required for a container. This method From webhook-mailer at python.org Wed Dec 5 15:15:57 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 20:15:57 -0000 Subject: [Python-checkins] Correct a couple of unbalanced parenthesis. (GH-10779) Message-ID: https://github.com/python/cpython/commit/349d9910b225f2425391b754f36579b6988e2882 commit: 349d9910b225f2425391b754f36579b6988e2882 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T12:15:54-08:00 summary: Correct a couple of unbalanced parenthesis. (GH-10779) (cherry picked from commit 55f41e45b4318cbe19209f5144641344d0049fb8) Co-authored-by: Andre Delfino files: M Doc/c-api/buffer.rst M Doc/faq/extending.rst M Doc/library/crypt.rst M Doc/library/email.compat32-message.rst M Doc/library/email.message.rst M Doc/library/email.parser.rst M Doc/library/exceptions.rst M Doc/library/stdtypes.rst M Doc/library/sysconfig.rst M Doc/reference/expressions.rst M Doc/using/windows.rst diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index 5a9a46fc67e8..33abb5bb94d9 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -198,7 +198,7 @@ a buffer, see :c:func:`PyObject_GetBuffer`. indicates that no de-referencing should occur (striding in a contiguous memory block). - If all suboffsets are negative (i.e. no de-referencing is needed, then + If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value). This type of array representation is used by the Python Imaging Library diff --git a/Doc/faq/extending.rst b/Doc/faq/extending.rst index b611bb48012a..74e1af6ef24d 100644 --- a/Doc/faq/extending.rst +++ b/Doc/faq/extending.rst @@ -277,7 +277,7 @@ However sometimes you have to run the embedded Python interpreter in the same thread as your rest application and you can't allow the :c:func:`PyRun_InteractiveLoop` to stop while waiting for user input. The one solution then is to call :c:func:`PyParser_ParseString` and test for ``e.error`` -equal to ``E_EOF``, which means the input is incomplete). Here's a sample code +equal to ``E_EOF``, which means the input is incomplete. Here's a sample code fragment, untested, inspired by code from Alex Farber:: #include diff --git a/Doc/library/crypt.rst b/Doc/library/crypt.rst index dd62cb32b9d1..43d4b5b749e4 100644 --- a/Doc/library/crypt.rst +++ b/Doc/library/crypt.rst @@ -91,7 +91,7 @@ The :mod:`crypt` module defines the following functions: may be available on all platforms), or a full encrypted password including salt, as returned by this function. If *salt* is not provided, the strongest method will be used (as returned by - :func:`methods`. + :func:`methods`). Checking a password is usually done by passing the plain-text password as *word* and the full results of a previous :func:`crypt` call, diff --git a/Doc/library/email.compat32-message.rst b/Doc/library/email.compat32-message.rst index d88495089482..f02b35b910a0 100644 --- a/Doc/library/email.compat32-message.rst +++ b/Doc/library/email.compat32-message.rst @@ -152,7 +152,7 @@ Here are the methods of the :class:`Message` class: Return ``True`` if the message's payload is a list of sub-\ :class:`Message` objects, otherwise return ``False``. When :meth:`is_multipart` returns ``False``, the payload should be a string - object (which might be a CTE encoded binary payload. (Note that + object (which might be a CTE encoded binary payload). (Note that :meth:`is_multipart` returning ``True`` does not necessarily mean that "msg.get_content_maintype() == 'multipart'" will return the ``True``. For example, ``is_multipart`` will return ``True`` when the diff --git a/Doc/library/email.message.rst b/Doc/library/email.message.rst index ff5404553e96..77b8099a7d4c 100644 --- a/Doc/library/email.message.rst +++ b/Doc/library/email.message.rst @@ -92,7 +92,7 @@ message objects. .. method:: __str__() - Equivalent to `as_string(policy=self.policy.clone(utf8=True)`. Allows + Equivalent to ``as_string(policy=self.policy.clone(utf8=True))``. Allows ``str(msg)`` to produce a string containing the serialized message in a readable format. @@ -379,7 +379,7 @@ message objects. Note that existing parameter values of headers may be accessed through the :attr:`~email.headerregistry.BaseHeader.params` attribute of the - header value (for example, ``msg['Content-Type'].params['charset']``. + header value (for example, ``msg['Content-Type'].params['charset']``). .. versionchanged:: 3.4 ``replace`` keyword was added. @@ -679,7 +679,7 @@ message objects. specified by the current :mod:`~email.policy`. If the added part has no :mailheader:`Content-Disposition` header, add one with the value ``attachment``. This method can be used both for explicit attachments - (:mailheader:`Content-Disposition: attachment` and ``inline`` attachments + (:mailheader:`Content-Disposition: attachment`) and ``inline`` attachments (:mailheader:`Content-Disposition: inline`), by passing appropriate options to the ``content_manager``. diff --git a/Doc/library/email.parser.rst b/Doc/library/email.parser.rst index 49b4e992708a..d9a61616bbbd 100644 --- a/Doc/library/email.parser.rst +++ b/Doc/library/email.parser.rst @@ -164,7 +164,7 @@ message body, instead setting the payload to the raw body. envelope header. The header block is terminated either by the end of the data or by a blank line. Following the header block is the body of the message (which may contain MIME-encoded subparts, including subparts - with a :mailheader:`Content-Transfer-Encoding` of ``8bit``. + with a :mailheader:`Content-Transfer-Encoding` of ``8bit``). Optional *headersonly* is a flag specifying whether to stop parsing after reading the headers or not. The default is ``False``, meaning it parses diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index e33b88673f75..57ed29145816 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -52,7 +52,7 @@ will be set as :attr:`__cause__` on the raised exception. Setting :attr:`__cause__` also implicitly sets the :attr:`__suppress_context__` attribute to ``True``, so that using ``raise new_exc from None`` effectively replaces the old exception with the new one for display -purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`, while +purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`), while leaving the old exception available in :attr:`__context__` for introspection when debugging. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 885e765673d1..416790b796f5 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -445,7 +445,7 @@ Notes: (4) Performing these calculations with at least one extra sign extension bit in a finite two's complement representation (a working bit-width of - ``1 + max(x.bit_length(), y.bit_length()`` or more) is sufficient to get the + ``1 + max(x.bit_length(), y.bit_length())`` or more) is sufficient to get the same result as if there were an infinite number of sign bits. @@ -3203,7 +3203,7 @@ place, and instead produce new objects. Return a copy of the sequence left filled with ASCII ``b'0'`` digits to make a sequence of length *width*. A leading sign prefix (``b'+'``/ - ``b'-'`` is handled by inserting the padding *after* the sign character + ``b'-'``) is handled by inserting the padding *after* the sign character rather than before. For :class:`bytes` objects, the original sequence is returned if *width* is less than or equal to ``len(seq)``. diff --git a/Doc/library/sysconfig.rst b/Doc/library/sysconfig.rst index 9f549fbc5130..b5a1da80c686 100644 --- a/Doc/library/sysconfig.rst +++ b/Doc/library/sysconfig.rst @@ -185,7 +185,7 @@ Other functions Windows will return one of: - - win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + - win-amd64 (64bit Windows on AMD64, aka x86_64, Intel64, and EM64T) - win32 (all others - specifically, sys.platform is returned) Mac OS X can return: diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 3b50efe5e277..3e7db901b849 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1644,7 +1644,7 @@ returned; otherwise, *y* is evaluated and the resulting value is returned. The expression ``x or y`` first evaluates *x*; if *x* is true, its value is returned; otherwise, *y* is evaluated and the resulting value is returned. -(Note that neither :keyword:`and` nor :keyword:`or` restrict the value and type +Note that neither :keyword:`and` nor :keyword:`or` restrict the value and type they return to ``False`` and ``True``, but rather return the last evaluated argument. This is sometimes useful, e.g., if ``s`` is a string that should be replaced by a default value if it is empty, the expression ``s or 'foo'`` yields diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index 3ed39e64ad9b..087ab6f4877a 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -613,11 +613,11 @@ user's "application data" directory (i.e. the directory returned by calling the Windows function ``SHGetFolderPath`` with ``CSIDL_LOCAL_APPDATA``) and ``py.ini`` in the same directory as the launcher. The same .ini files are used for both the 'console' version of the launcher (i.e. py.exe) and for the 'windows' version -(i.e. pyw.exe) +(i.e. pyw.exe). Customization specified in the "application directory" will have precedence over the one next to the executable, so a user, who may not have write access to the -.ini file next to the launcher, can override commands in that global .ini file) +.ini file next to the launcher, can override commands in that global .ini file. Customizing default Python versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From webhook-mailer at python.org Wed Dec 5 15:20:06 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 20:20:06 -0000 Subject: [Python-checkins] bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) (GH-10936) Message-ID: https://github.com/python/cpython/commit/49d995fd6f2a703d19d93baf06fc9f911cb2ce06 commit: 49d995fd6f2a703d19d93baf06fc9f911cb2ce06 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Serhiy Storchaka date: 2018-12-05T22:20:03+02:00 summary: bpo-34185: Fix test module collision in test_bdb when ran as script. (GH-8537) (GH-10936) When running test_bdb.py as a script, `import test_module` would be importing the existing Lib/test/test_modules.py instead of the tempcwd/test_module.py module which was dynamically created by test_bdb.py itself. (cherry picked from commit 54fd45505b3a365e6d53441e6dd7e0d1ec13b46f) Co-authored-by: Alex H <1884912+lajarre at users.noreply.github.com> files: M Lib/test/test_bdb.py diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index 616c3a864984..55f42e2535b7 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -549,11 +549,11 @@ def create_modules(modules): def break_in_func(funcname, fname=__file__, temporary=False, cond=None): return 'break', (fname, None, temporary, cond, funcname) -TEST_MODULE = 'test_module' +TEST_MODULE = 'test_module_for_bdb' TEST_MODULE_FNAME = TEST_MODULE + '.py' def tfunc_import(): - import test_module - test_module.main() + import test_module_for_bdb + test_module_for_bdb.main() def tfunc_main(): lno = 2 @@ -971,9 +971,9 @@ def main(): ('return', 3, 'main'), ('step', ), ('return', 1, ''), ('quit', ), ] - import test_module + import test_module_for_bdb with TracerRun(self) as tracer: - tracer.runeval('test_module.main()', globals(), locals()) + tracer.runeval('test_module_for_bdb.main()', globals(), locals()) class IssuesTestCase(BaseTestCase): """Test fixed bdb issues.""" @@ -983,7 +983,7 @@ def test_step_at_return_with_no_trace_in_caller(self): # Check that the tracer does step into the caller frame when the # trace function is not set in that frame. code_1 = """ - from test_module_2 import func + from test_module_for_bdb_2 import func def main(): func() lno = 5 @@ -994,12 +994,12 @@ def func(): """ modules = { TEST_MODULE: code_1, - 'test_module_2': code_2, + 'test_module_for_bdb_2': code_2, } with create_modules(modules): self.expect_set = [ ('line', 2, 'tfunc_import'), - break_in_func('func', 'test_module_2.py'), + break_in_func('func', 'test_module_for_bdb_2.py'), ('None', 2, 'tfunc_import'), ('continue', ), ('line', 3, 'func', ({1:1}, [])), ('step', ), ('return', 3, 'func'), ('step', ), From webhook-mailer at python.org Wed Dec 5 15:29:34 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 20:29:34 -0000 Subject: [Python-checkins] bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419) Message-ID: https://github.com/python/cpython/commit/53bed18d93233b030bb5b2637daf1b5e87548ef1 commit: 53bed18d93233b030bb5b2637daf1b5e87548ef1 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T12:29:31-08:00 summary: bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419) (cherry picked from commit 67a93b3a0b3814e97ef9d077b21325fc8ce351b2) Co-authored-by: Serhiy Storchaka files: A Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst M Lib/distutils/archive_util.py M Lib/distutils/tests/test_archive_util.py M Lib/distutils/tests/test_bdist_dumb.py M Lib/distutils/tests/test_sdist.py diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py index 78ae5757c36d..b002dc3b8456 100644 --- a/Lib/distutils/archive_util.py +++ b/Lib/distutils/archive_util.py @@ -166,7 +166,15 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): zip = zipfile.ZipFile(zip_filename, "w", compression=zipfile.ZIP_STORED) + if base_dir != os.curdir: + path = os.path.normpath(os.path.join(base_dir, '')) + zip.write(path, path) + log.info("adding '%s'", path) for dirpath, dirnames, filenames in os.walk(base_dir): + for name in dirnames: + path = os.path.normpath(os.path.join(dirpath, name, '')) + zip.write(path, path) + log.info("adding '%s'", path) for name in filenames: path = os.path.normpath(os.path.join(dirpath, name)) if os.path.isfile(path): diff --git a/Lib/distutils/tests/test_archive_util.py b/Lib/distutils/tests/test_archive_util.py index 14ba4ca34b4a..e9aad0e40fd1 100644 --- a/Lib/distutils/tests/test_archive_util.py +++ b/Lib/distutils/tests/test_archive_util.py @@ -122,12 +122,13 @@ def _tarinfo(self, path): try: names = tar.getnames() names.sort() - return tuple(names) + return names finally: tar.close() - _created_files = ('dist', 'dist/file1', 'dist/file2', - 'dist/sub', 'dist/sub/file3', 'dist/sub2') + _zip_created_files = ['dist/', 'dist/file1', 'dist/file2', + 'dist/sub/', 'dist/sub/file3', 'dist/sub2/'] + _created_files = [p.rstrip('/') for p in _zip_created_files] def _create_files(self): # creating something to tar @@ -244,8 +245,7 @@ def test_make_zipfile(self): tarball = base_name + '.zip' self.assertTrue(os.path.exists(tarball)) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), - ['dist/file1', 'dist/file2', 'dist/sub/file3']) + self.assertEqual(sorted(zf.namelist()), self._zip_created_files) @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') def test_make_zipfile_no_zlib(self): @@ -271,8 +271,7 @@ def fake_zipfile(*a, **kw): [((tarball, "w"), {'compression': zipfile.ZIP_STORED})]) self.assertTrue(os.path.exists(tarball)) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), - ['dist/file1', 'dist/file2', 'dist/sub/file3']) + self.assertEqual(sorted(zf.namelist()), self._zip_created_files) def test_check_archive_formats(self): self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']), diff --git a/Lib/distutils/tests/test_bdist_dumb.py b/Lib/distutils/tests/test_bdist_dumb.py index c8ccdc2383de..01a233bce37f 100644 --- a/Lib/distutils/tests/test_bdist_dumb.py +++ b/Lib/distutils/tests/test_bdist_dumb.py @@ -84,7 +84,7 @@ def test_simple_built(self): finally: fp.close() - contents = sorted(os.path.basename(fn) for fn in contents) + contents = sorted(filter(None, map(os.path.basename, contents))) wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py'] if not sys.dont_write_bytecode: wanted.append('foo.%s.pyc' % sys.implementation.cache_tag) diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py index 5444b815a8b2..23db1269591d 100644 --- a/Lib/distutils/tests/test_sdist.py +++ b/Lib/distutils/tests/test_sdist.py @@ -128,7 +128,9 @@ def test_prune_file_list(self): zip_file.close() # making sure everything has been pruned correctly - self.assertEqual(len(content), 4) + expected = ['', 'PKG-INFO', 'README', 'setup.py', + 'somecode/', 'somecode/__init__.py'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') @unittest.skipIf(find_executable('tar') is None, @@ -226,7 +228,13 @@ def test_add_defaults(self): zip_file.close() # making sure everything was added - self.assertEqual(len(content), 12) + expected = ['', 'PKG-INFO', 'README', 'buildout.cfg', + 'data/', 'data/data.dt', 'inroot.txt', + 'scripts/', 'scripts/script.py', 'setup.py', + 'some/', 'some/file.txt', 'some/other_file.txt', + 'somecode/', 'somecode/__init__.py', 'somecode/doc.dat', + 'somecode/doc.txt'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) # checking the MANIFEST f = open(join(self.tmp_dir, 'MANIFEST')) diff --git a/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst new file mode 100644 index 000000000000..c3f402d39ae0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst @@ -0,0 +1,2 @@ +ZIP files created by :mod:`distutils` will now include entries for +directories. From webhook-mailer at python.org Wed Dec 5 15:36:06 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 20:36:06 -0000 Subject: [Python-checkins] bpo-35310: Clear select() lists before returning upon EINTR (GH-10877) Message-ID: https://github.com/python/cpython/commit/7f52415a6d4841d77d3b7853e83b25a22e0048dc commit: 7f52415a6d4841d77d3b7853e83b25a22e0048dc branch: master author: Oran Avraham <252748+oranav at users.noreply.github.com> committer: Victor Stinner date: 2018-12-05T21:36:03+01:00 summary: bpo-35310: Clear select() lists before returning upon EINTR (GH-10877) select() calls are retried on EINTR (per PEP 475). However, if a timeout was provided and the deadline has passed after running the signal handlers, rlist, wlist and xlist should be cleared since select(2) left them unmodified. files: A Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst M Modules/selectmodule.c diff --git a/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst b/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst new file mode 100644 index 000000000000..1ab2e168c86a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst @@ -0,0 +1,4 @@ +Fix a bug in :func:`select.select` where, in some cases, the file descriptor +sequences were returned unmodified after a signal interruption, even though the +file descriptors might not be ready yet. :func:`select.select` will now always +return empty lists if a timeout has occurred. Patch by Oran Avraham. diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 5a6b13466c80..fe69cd58dc6a 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -335,6 +335,10 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, if (tvp) { timeout = deadline - _PyTime_GetMonotonicClock(); if (timeout < 0) { + /* bpo-35310: lists were unmodified -- clear them explicitly */ + FD_ZERO(&ifdset); + FD_ZERO(&ofdset); + FD_ZERO(&efdset); n = 0; break; } From webhook-mailer at python.org Wed Dec 5 15:44:44 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 20:44:44 -0000 Subject: [Python-checkins] bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419) (GH-10942) Message-ID: https://github.com/python/cpython/commit/e0c2046d82f6db45a7f3d3ee5f555c3e2487a74d commit: e0c2046d82f6db45a7f3d3ee5f555c3e2487a74d branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Serhiy Storchaka date: 2018-12-05T22:44:38+02:00 summary: bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419) (GH-10942) (cherry picked from commit 67a93b3a0b3814e97ef9d077b21325fc8ce351b2) Co-authored-by: Serhiy Storchaka files: A Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst M Lib/distutils/archive_util.py M Lib/distutils/tests/test_archive_util.py M Lib/distutils/tests/test_bdist_dumb.py M Lib/distutils/tests/test_sdist.py diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py index 78ae5757c36d..b002dc3b8456 100644 --- a/Lib/distutils/archive_util.py +++ b/Lib/distutils/archive_util.py @@ -166,7 +166,15 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): zip = zipfile.ZipFile(zip_filename, "w", compression=zipfile.ZIP_STORED) + if base_dir != os.curdir: + path = os.path.normpath(os.path.join(base_dir, '')) + zip.write(path, path) + log.info("adding '%s'", path) for dirpath, dirnames, filenames in os.walk(base_dir): + for name in dirnames: + path = os.path.normpath(os.path.join(dirpath, name, '')) + zip.write(path, path) + log.info("adding '%s'", path) for name in filenames: path = os.path.normpath(os.path.join(dirpath, name)) if os.path.isfile(path): diff --git a/Lib/distutils/tests/test_archive_util.py b/Lib/distutils/tests/test_archive_util.py index 02fa1e27a47f..18a0313eb606 100644 --- a/Lib/distutils/tests/test_archive_util.py +++ b/Lib/distutils/tests/test_archive_util.py @@ -122,12 +122,13 @@ def _tarinfo(self, path): try: names = tar.getnames() names.sort() - return tuple(names) + return names finally: tar.close() - _created_files = ('dist', 'dist/file1', 'dist/file2', - 'dist/sub', 'dist/sub/file3', 'dist/sub2') + _zip_created_files = ['dist/', 'dist/file1', 'dist/file2', + 'dist/sub/', 'dist/sub/file3', 'dist/sub2/'] + _created_files = [p.rstrip('/') for p in _zip_created_files] def _create_files(self): # creating something to tar @@ -244,8 +245,7 @@ def test_make_zipfile(self): tarball = base_name + '.zip' self.assertTrue(os.path.exists(tarball)) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), - ['dist/file1', 'dist/file2', 'dist/sub/file3']) + self.assertEqual(sorted(zf.namelist()), self._zip_created_files) @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') def test_make_zipfile_no_zlib(self): @@ -271,8 +271,7 @@ def fake_zipfile(*a, **kw): [((tarball, "w"), {'compression': zipfile.ZIP_STORED})]) self.assertTrue(os.path.exists(tarball)) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), - ['dist/file1', 'dist/file2', 'dist/sub/file3']) + self.assertEqual(sorted(zf.namelist()), self._zip_created_files) def test_check_archive_formats(self): self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']), diff --git a/Lib/distutils/tests/test_bdist_dumb.py b/Lib/distutils/tests/test_bdist_dumb.py index c8ccdc2383de..01a233bce37f 100644 --- a/Lib/distutils/tests/test_bdist_dumb.py +++ b/Lib/distutils/tests/test_bdist_dumb.py @@ -84,7 +84,7 @@ def test_simple_built(self): finally: fp.close() - contents = sorted(os.path.basename(fn) for fn in contents) + contents = sorted(filter(None, map(os.path.basename, contents))) wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py'] if not sys.dont_write_bytecode: wanted.append('foo.%s.pyc' % sys.implementation.cache_tag) diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py index 5444b815a8b2..23db1269591d 100644 --- a/Lib/distutils/tests/test_sdist.py +++ b/Lib/distutils/tests/test_sdist.py @@ -128,7 +128,9 @@ def test_prune_file_list(self): zip_file.close() # making sure everything has been pruned correctly - self.assertEqual(len(content), 4) + expected = ['', 'PKG-INFO', 'README', 'setup.py', + 'somecode/', 'somecode/__init__.py'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') @unittest.skipIf(find_executable('tar') is None, @@ -226,7 +228,13 @@ def test_add_defaults(self): zip_file.close() # making sure everything was added - self.assertEqual(len(content), 12) + expected = ['', 'PKG-INFO', 'README', 'buildout.cfg', + 'data/', 'data/data.dt', 'inroot.txt', + 'scripts/', 'scripts/script.py', 'setup.py', + 'some/', 'some/file.txt', 'some/other_file.txt', + 'somecode/', 'somecode/__init__.py', 'somecode/doc.dat', + 'somecode/doc.txt'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) # checking the MANIFEST f = open(join(self.tmp_dir, 'MANIFEST')) diff --git a/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst new file mode 100644 index 000000000000..c3f402d39ae0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst @@ -0,0 +1,2 @@ +ZIP files created by :mod:`distutils` will now include entries for +directories. From webhook-mailer at python.org Wed Dec 5 15:56:29 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 20:56:29 -0000 Subject: [Python-checkins] bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) (GH-10930) Message-ID: https://github.com/python/cpython/commit/b50b33b4ac62c8798199682e511b2028f2d3ec42 commit: b50b33b4ac62c8798199682e511b2028f2d3ec42 branch: 2.7 author: Victor Stinner committer: GitHub date: 2018-12-05T21:56:24+01:00 summary: bpo-10496: posixpath.expanduser() catchs pwd.getpwuid() error (GH-10919) (GH-10930) * posixpath.expanduser() now returns the input path unchanged if the HOME environment variable is not set and pwd.getpwuid() raises KeyError (the current user identifier doesn't exist in the password database). * Add test_no_home_directory() to test_site. (cherry picked from commit f2f4555d8287ad217a1dba7bbd93103ad4daf3a8) files: A Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst M Lib/posixpath.py M Lib/test/test_posixpath.py M Lib/test/test_site.py diff --git a/Lib/posixpath.py b/Lib/posixpath.py index f5c2260f1e5f..bbc2369ce7be 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -259,7 +259,12 @@ def expanduser(path): if i == 1: if 'HOME' not in os.environ: import pwd - userhome = pwd.getpwuid(os.getuid()).pw_dir + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path else: userhome = os.environ['HOME'] else: @@ -267,6 +272,8 @@ def expanduser(path): try: pwent = pwd.getpwnam(path[1:i]) except KeyError: + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged return path userhome = pwent.pw_dir userhome = userhome.rstrip('/') diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index deaa5772835b..0663a21ff043 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -262,34 +262,56 @@ def fake_lstat(path): def test_expanduser(self): self.assertEqual(posixpath.expanduser("foo"), "foo") - with test_support.EnvironmentVarGuard() as env: + + def test_expanduser_home_envvar(self): + with support.EnvironmentVarGuard() as env: + env['HOME'] = '/home/victor' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + + # expanduser() strips trailing slash + env['HOME'] = '/home/victor/' + self.assertEqual(posixpath.expanduser("~"), "/home/victor") + for home in '/', '', '//', '///': env['HOME'] = home self.assertEqual(posixpath.expanduser("~"), "/") self.assertEqual(posixpath.expanduser("~/"), "/") self.assertEqual(posixpath.expanduser("~/foo"), "/foo") - try: - import pwd - except ImportError: - pass - else: - self.assertIsInstance(posixpath.expanduser("~/"), basestring) - # if home directory == root directory, this test makes no sense - if posixpath.expanduser("~") != '/': - self.assertEqual( - posixpath.expanduser("~") + "/", - posixpath.expanduser("~/") - ) - self.assertIsInstance(posixpath.expanduser("~root/"), basestring) - self.assertIsInstance(posixpath.expanduser("~foo/"), basestring) - - with test_support.EnvironmentVarGuard() as env: - # expanduser should fall back to using the password database - del env['HOME'] - home = pwd.getpwuid(os.getuid()).pw_dir - # $HOME can end with a trailing /, so strip it (see #17809) - home = home.rstrip("/") or '/' - self.assertEqual(posixpath.expanduser("~"), home) + + def test_expanduser_pwd(self): + pwd = support.import_module('pwd') + + self.assertIsInstance(posixpath.expanduser("~/"), str) + + # if home directory == root directory, this test makes no sense + if posixpath.expanduser("~") != '/': + self.assertEqual( + posixpath.expanduser("~") + "/", + posixpath.expanduser("~/") + ) + self.assertIsInstance(posixpath.expanduser("~root/"), str) + self.assertIsInstance(posixpath.expanduser("~foo/"), str) + + with support.EnvironmentVarGuard() as env: + # expanduser should fall back to using the password database + del env['HOME'] + + home = pwd.getpwuid(os.getuid()).pw_dir + # $HOME can end with a trailing /, so strip it (see #17809) + home = home.rstrip("/") or '/' + self.assertEqual(posixpath.expanduser("~"), home) + + # bpo-10496: If the HOME environment variable is not set and the + # user (current identifier or name in the path) doesn't exist in + # the password database (pwd.getuid() or pwd.getpwnam() fail), + # expanduser() must return the path unchanged. + def raise_keyerror(*args): + raise KeyError + + with support.swap_attr(pwd, 'getpwuid', raise_keyerror), \ + support.swap_attr(pwd, 'getpwnam', raise_keyerror): + for path in ('~', '~/.local', '~vstinner/'): + self.assertEqual(posixpath.expanduser(path), path) def test_normpath(self): self.assertEqual(posixpath.normpath(""), ".") diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 9569135c2ca2..b4384ee2cf83 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -7,6 +7,7 @@ import unittest from test.test_support import run_unittest, TESTFN, EnvironmentVarGuard from test.test_support import captured_output +from test import support import __builtin__ import errno import os @@ -241,6 +242,7 @@ def test_getusersitepackages(self): # the call sets USER_BASE *and* USER_SITE self.assertEqual(site.USER_SITE, user_site) self.assertTrue(user_site.startswith(site.USER_BASE), user_site) + self.assertEqual(site.USER_BASE, site.getuserbase()) def test_getsitepackages(self): site.PREFIXES = ['xoxo'] @@ -265,6 +267,48 @@ def test_getsitepackages(self): wanted = os.path.join('xoxo', 'lib', 'site-packages') self.assertEqual(dirs[1], wanted) + def test_no_home_directory(self): + # bpo-10496: getuserbase() and getusersitepackages() must not fail if + # the current user has no home directory (if expanduser() returns the + # path unchanged). + site.USER_SITE = None + site.USER_BASE = None + sysconfig._CONFIG_VARS = None + + with EnvironmentVarGuard() as environ, \ + support.swap_attr(os.path, 'expanduser', lambda path: path): + + del environ['PYTHONUSERBASE'] + del environ['APPDATA'] + + user_base = site.getuserbase() + self.assertTrue(user_base.startswith('~' + os.sep), + user_base) + + user_site = site.getusersitepackages() + self.assertTrue(user_site.startswith(user_base), user_site) + + def fake_isdir(path): + fake_isdir.arg = path + return False + fake_isdir.arg = None + + def must_not_be_called(*args): + raise AssertionError + + with support.swap_attr(os.path, 'isdir', fake_isdir), \ + support.swap_attr(site, 'addsitedir', must_not_be_called), \ + support.swap_attr(site, 'ENABLE_USER_SITE', True): + + # addusersitepackages() must not add user_site to sys.path + # if it is not an existing directory + known_paths = set() + site.addusersitepackages(known_paths) + + self.assertEqual(fake_isdir.arg, user_site) + self.assertFalse(known_paths) + + class PthFile(object): """Helper class for handling testing of .pth files""" diff --git a/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst new file mode 100644 index 000000000000..232fcc6503b0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-05-13-37-39.bpo-10496.VH-1Lp.rst @@ -0,0 +1,5 @@ +:func:`posixpath.expanduser` now returns the input *path* unchanged if the +``HOME`` environment variable is not set and the current user has no home +directory (if the current user identifier doesn't exist in the password +database). This change fix the :mod:`site` module if the current user doesn't +exist in the password database (if the user has no home directory). From webhook-mailer at python.org Wed Dec 5 16:08:54 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 21:08:54 -0000 Subject: [Python-checkins] [3.6] Correct a couple of unbalanced parenthesis. (GH-10779). (GH-10945) Message-ID: https://github.com/python/cpython/commit/62af9e6583b0b5f5069232c2a6bfd0ceca7fbf95 commit: 62af9e6583b0b5f5069232c2a6bfd0ceca7fbf95 branch: 3.6 author: Andre Delfino committer: Serhiy Storchaka date: 2018-12-05T23:08:50+02:00 summary: [3.6] Correct a couple of unbalanced parenthesis. (GH-10779). (GH-10945) (cherry picked from commit 55f41e45b4318cbe19209f5144641344d0049fb8) Co-authored-by: Andre Delfino files: M Doc/c-api/buffer.rst M Doc/faq/extending.rst M Doc/library/crypt.rst M Doc/library/email.compat32-message.rst M Doc/library/email.message.rst M Doc/library/email.parser.rst M Doc/library/exceptions.rst M Doc/library/stdtypes.rst M Doc/library/sysconfig.rst M Doc/reference/expressions.rst M Doc/using/windows.rst diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index 5a9a46fc67e8..33abb5bb94d9 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -198,7 +198,7 @@ a buffer, see :c:func:`PyObject_GetBuffer`. indicates that no de-referencing should occur (striding in a contiguous memory block). - If all suboffsets are negative (i.e. no de-referencing is needed, then + If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value). This type of array representation is used by the Python Imaging Library diff --git a/Doc/faq/extending.rst b/Doc/faq/extending.rst index fd04a83df33c..a0828172d48e 100644 --- a/Doc/faq/extending.rst +++ b/Doc/faq/extending.rst @@ -277,7 +277,7 @@ However sometimes you have to run the embedded Python interpreter in the same thread as your rest application and you can't allow the :c:func:`PyRun_InteractiveLoop` to stop while waiting for user input. The one solution then is to call :c:func:`PyParser_ParseString` and test for ``e.error`` -equal to ``E_EOF``, which means the input is incomplete). Here's a sample code +equal to ``E_EOF``, which means the input is incomplete. Here's a sample code fragment, untested, inspired by code from Alex Farber:: #include diff --git a/Doc/library/crypt.rst b/Doc/library/crypt.rst index dbd427403847..bccc76b82621 100644 --- a/Doc/library/crypt.rst +++ b/Doc/library/crypt.rst @@ -84,7 +84,7 @@ The :mod:`crypt` module defines the following functions: may be available on all platforms), or a full encrypted password including salt, as returned by this function. If *salt* is not provided, the strongest method will be used (as returned by - :func:`methods`. + :func:`methods`). Checking a password is usually done by passing the plain-text password as *word* and the full results of a previous :func:`crypt` call, diff --git a/Doc/library/email.compat32-message.rst b/Doc/library/email.compat32-message.rst index 9f85c5921c8f..ed380151769a 100644 --- a/Doc/library/email.compat32-message.rst +++ b/Doc/library/email.compat32-message.rst @@ -152,7 +152,7 @@ Here are the methods of the :class:`Message` class: Return ``True`` if the message's payload is a list of sub-\ :class:`Message` objects, otherwise return ``False``. When :meth:`is_multipart` returns ``False``, the payload should be a string - object (which might be a CTE encoded binary payload. (Note that + object (which might be a CTE encoded binary payload). (Note that :meth:`is_multipart` returning ``True`` does not necessarily mean that "msg.get_content_maintype() == 'multipart'" will return the ``True``. For example, ``is_multipart`` will return ``True`` when the diff --git a/Doc/library/email.message.rst b/Doc/library/email.message.rst index ff5404553e96..77b8099a7d4c 100644 --- a/Doc/library/email.message.rst +++ b/Doc/library/email.message.rst @@ -92,7 +92,7 @@ message objects. .. method:: __str__() - Equivalent to `as_string(policy=self.policy.clone(utf8=True)`. Allows + Equivalent to ``as_string(policy=self.policy.clone(utf8=True))``. Allows ``str(msg)`` to produce a string containing the serialized message in a readable format. @@ -379,7 +379,7 @@ message objects. Note that existing parameter values of headers may be accessed through the :attr:`~email.headerregistry.BaseHeader.params` attribute of the - header value (for example, ``msg['Content-Type'].params['charset']``. + header value (for example, ``msg['Content-Type'].params['charset']``). .. versionchanged:: 3.4 ``replace`` keyword was added. @@ -679,7 +679,7 @@ message objects. specified by the current :mod:`~email.policy`. If the added part has no :mailheader:`Content-Disposition` header, add one with the value ``attachment``. This method can be used both for explicit attachments - (:mailheader:`Content-Disposition: attachment` and ``inline`` attachments + (:mailheader:`Content-Disposition: attachment`) and ``inline`` attachments (:mailheader:`Content-Disposition: inline`), by passing appropriate options to the ``content_manager``. diff --git a/Doc/library/email.parser.rst b/Doc/library/email.parser.rst index 49b4e992708a..d9a61616bbbd 100644 --- a/Doc/library/email.parser.rst +++ b/Doc/library/email.parser.rst @@ -164,7 +164,7 @@ message body, instead setting the payload to the raw body. envelope header. The header block is terminated either by the end of the data or by a blank line. Following the header block is the body of the message (which may contain MIME-encoded subparts, including subparts - with a :mailheader:`Content-Transfer-Encoding` of ``8bit``. + with a :mailheader:`Content-Transfer-Encoding` of ``8bit``). Optional *headersonly* is a flag specifying whether to stop parsing after reading the headers or not. The default is ``False``, meaning it parses diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index cfa1649d4d1f..5c0cac8997c6 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -52,7 +52,7 @@ will be set as :attr:`__cause__` on the raised exception. Setting :attr:`__cause__` also implicitly sets the :attr:`__suppress_context__` attribute to ``True``, so that using ``raise new_exc from None`` effectively replaces the old exception with the new one for display -purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`, while +purposes (e.g. converting :exc:`KeyError` to :exc:`AttributeError`), while leaving the old exception available in :attr:`__context__` for introspection when debugging. diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index a5fca9d46900..eed37454bc35 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -445,7 +445,7 @@ Notes: (4) Performing these calculations with at least one extra sign extension bit in a finite two's complement representation (a working bit-width of - ``1 + max(x.bit_length(), y.bit_length()`` or more) is sufficient to get the + ``1 + max(x.bit_length(), y.bit_length())`` or more) is sufficient to get the same result as if there were an infinite number of sign bits. @@ -3175,7 +3175,7 @@ place, and instead produce new objects. Return a copy of the sequence left filled with ASCII ``b'0'`` digits to make a sequence of length *width*. A leading sign prefix (``b'+'``/ - ``b'-'`` is handled by inserting the padding *after* the sign character + ``b'-'``) is handled by inserting the padding *after* the sign character rather than before. For :class:`bytes` objects, the original sequence is returned if *width* is less than or equal to ``len(seq)``. diff --git a/Doc/library/sysconfig.rst b/Doc/library/sysconfig.rst index f066a765d00e..d29396e01f25 100644 --- a/Doc/library/sysconfig.rst +++ b/Doc/library/sysconfig.rst @@ -188,7 +188,7 @@ Other functions Windows will return one of: - - win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + - win-amd64 (64bit Windows on AMD64, aka x86_64, Intel64, and EM64T) - win-ia64 (64bit Windows on Itanium) - win32 (all others - specifically, sys.platform is returned) diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 05f62cd38ba9..e13072feebc1 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -1603,7 +1603,7 @@ returned; otherwise, *y* is evaluated and the resulting value is returned. The expression ``x or y`` first evaluates *x*; if *x* is true, its value is returned; otherwise, *y* is evaluated and the resulting value is returned. -(Note that neither :keyword:`and` nor :keyword:`or` restrict the value and type +Note that neither :keyword:`and` nor :keyword:`or` restrict the value and type they return to ``False`` and ``True``, but rather return the last evaluated argument. This is sometimes useful, e.g., if ``s`` is a string that should be replaced by a default value if it is empty, the expression ``s or 'foo'`` yields diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index a4294686fafe..0ee66561d2cf 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -613,11 +613,11 @@ user's "application data" directory (i.e. the directory returned by calling the Windows function ``SHGetFolderPath`` with ``CSIDL_LOCAL_APPDATA``) and ``py.ini`` in the same directory as the launcher. The same .ini files are used for both the 'console' version of the launcher (i.e. py.exe) and for the 'windows' version -(i.e. pyw.exe) +(i.e. pyw.exe). Customization specified in the "application directory" will have precedence over the one next to the executable, so a user, who may not have write access to the -.ini file next to the launcher, can override commands in that global .ini file) +.ini file next to the launcher, can override commands in that global .ini file. Customizing default Python versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From webhook-mailer at python.org Wed Dec 5 16:09:28 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 21:09:28 -0000 Subject: [Python-checkins] [2.7] Move __missing__ after __delitem__ in Data model. (GH-10923). (GH-10944) Message-ID: https://github.com/python/cpython/commit/c6639cda512d32b016dce713ac2c737a9056eac0 commit: c6639cda512d32b016dce713ac2c737a9056eac0 branch: 2.7 author: Andre Delfino committer: Serhiy Storchaka date: 2018-12-05T23:09:25+02:00 summary: [2.7] Move __missing__ after __delitem__ in Data model. (GH-10923). (GH-10944) (cherry picked from commit 1ce853f37783575e2b3aaa159ddcebc8660830ef) Co-authored-by: Andre Delfino files: M Doc/reference/datamodel.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 7bdb141ae55e..92f95c2db9b9 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1919,12 +1919,6 @@ sequences, it should iterate through the values. indexes to allow proper detection of the end of the sequence. -.. method:: object.__missing__(self, key) - - Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses - when key is not in the dictionary. - - .. method:: object.__setitem__(self, key, value) Called to implement assignment to ``self[key]``. Same note as for @@ -1943,6 +1937,12 @@ sequences, it should iterate through the values. values as for the :meth:`__getitem__` method. +.. method:: object.__missing__(self, key) + + Called by :class:`dict`\ .\ :meth:`__getitem__` to implement ``self[key]`` for dict subclasses + when key is not in the dictionary. + + .. method:: object.__iter__(self) This method is called when an iterator is required for a container. This method From webhook-mailer at python.org Wed Dec 5 16:10:00 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 21:10:00 -0000 Subject: [Python-checkins] bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) Message-ID: https://github.com/python/cpython/commit/1de91a0032fed500ddd3d8c4fb7a38c0b8719f67 commit: 1de91a0032fed500ddd3d8c4fb7a38c0b8719f67 branch: 3.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-05T23:09:56+02:00 summary: bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (cherry picked from commit 5b25f1d03100e2283c1b129d461ba68ac0169a14) Co-authored-by: Sergey Fedoseev files: A Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst M Lib/sqlite3/test/regression.py M Modules/_sqlite/connection.c diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py index 34cd233535dc..1c59a3cd31c6 100644 --- a/Lib/sqlite3/test/regression.py +++ b/Lib/sqlite3/test/regression.py @@ -256,24 +256,6 @@ def CheckPragmaAutocommit(self): cur.execute("pragma page_size") row = cur.fetchone() - def CheckSetDict(self): - """ - See http://bugs.python.org/issue7478 - - It was possible to successfully register callbacks that could not be - hashed. Return codes of PyDict_SetItem were not checked properly. - """ - class NotHashable: - def __call__(self, *args, **kw): - pass - def __hash__(self): - raise TypeError() - var = NotHashable() - self.assertRaises(TypeError, self.con.create_function, var) - self.assertRaises(TypeError, self.con.create_aggregate, var) - self.assertRaises(TypeError, self.con.set_authorizer, var) - self.assertRaises(TypeError, self.con.set_progress_handler, var) - def CheckConnectionCall(self): """ Call a connection with a non-string SQL request: check error handling @@ -398,9 +380,72 @@ def callback(*args): support.gc_collect() +class UnhashableFunc: + __hash__ = None + + def __init__(self, return_value=None): + self.calls = 0 + self.return_value = return_value + + def __call__(self, *args, **kwargs): + self.calls += 1 + return self.return_value + + +class UnhashableCallbacksTestCase(unittest.TestCase): + """ + https://bugs.python.org/issue34052 + + Registering unhashable callbacks raises TypeError, callbacks are not + registered in SQLite after such registration attempt. + """ + def setUp(self): + self.con = sqlite.connect(':memory:') + + def tearDown(self): + self.con.close() + + def test_progress_handler(self): + f = UnhashableFunc(return_value=0) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.set_progress_handler(f, 1) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_func(self): + func_name = 'func_name' + f = UnhashableFunc() + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.create_function(func_name, 0, f) + msg = 'no such function: %s' % func_name + with self.assertRaisesRegex(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % func_name) + self.assertFalse(f.calls) + + def test_authorizer(self): + f = UnhashableFunc(return_value=sqlite.SQLITE_DENY) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.set_authorizer(f) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_aggr(self): + class UnhashableType(type): + __hash__ = None + aggr_name = 'aggr_name' + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {})) + msg = 'no such function: %s' % aggr_name + with self.assertRaisesRegex(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % aggr_name) + + def suite(): regression_suite = unittest.makeSuite(RegressionTests, "Check") - return unittest.TestSuite((regression_suite,)) + return unittest.TestSuite(( + regression_suite, + unittest.makeSuite(UnhashableCallbacksTestCase), + )) def test(): runner = unittest.TextTestRunner() diff --git a/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst new file mode 100644 index 000000000000..5aa3cc9a81d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst @@ -0,0 +1,7 @@ +:meth:`sqlite3.Connection.create_aggregate`, +:meth:`sqlite3.Connection.create_function`, +:meth:`sqlite3.Connection.set_authorizer`, +:meth:`sqlite3.Connection.set_progress_handler` methods raises TypeError +when unhashable objects are passed as callable. These methods now don't pass +such objects to SQLite API. Previous behavior could lead to segfaults. Patch +by Sergey Fedoseev. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 3d14865315eb..351317e78db4 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -827,18 +827,17 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec return NULL; } + if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating function"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -859,17 +858,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje return NULL; } + if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source) @@ -982,17 +980,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P return NULL; } + if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) { + return NULL; + } rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb); - if (rc != SQLITE_OK) { PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -1015,9 +1011,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s /* None clears the progress handler previously set */ sqlite3_progress_handler(self->db, 0, 0, (void*)0); } else { - sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1) return NULL; + sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); } Py_RETURN_NONE; From webhook-mailer at python.org Wed Dec 5 16:10:39 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 21:10:39 -0000 Subject: [Python-checkins] [2.7] bpo-35250: Correct argument name "num" -> "btn" in turtle docs. (GH-10565). (GH-10943) Message-ID: https://github.com/python/cpython/commit/abe74feb912292aa4df4e70d39e85d0bcd425436 commit: abe74feb912292aa4df4e70d39e85d0bcd425436 branch: 2.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-05T23:10:36+02:00 summary: [2.7] bpo-35250: Correct argument name "num" -> "btn" in turtle docs. (GH-10565). (GH-10943) (cherry picked from commit 4edeaeac4c194ba5d09187640b5cfca5e03be617) Co-authored-by: Srinivas Thatiparthy (?????????? ?????????) files: M Doc/library/turtle.rst M Lib/lib-tk/turtle.py diff --git a/Doc/library/turtle.rst b/Doc/library/turtle.rst index 1c8a9675b70c..63c0bee6adba 100644 --- a/Doc/library/turtle.rst +++ b/Doc/library/turtle.rst @@ -1229,7 +1229,7 @@ Using events :param fun: a function with two arguments which will be called with the coordinates of the clicked point on the canvas - :param num: number of the mouse-button, defaults to 1 (left mouse button) + :param btn: number of the mouse-button, defaults to 1 (left mouse button) :param add: ``True`` or ``False`` -- if ``True``, a new binding will be added, otherwise it will replace a former binding @@ -1250,7 +1250,7 @@ Using events :param fun: a function with two arguments which will be called with the coordinates of the clicked point on the canvas - :param num: number of the mouse-button, defaults to 1 (left mouse button) + :param btn: number of the mouse-button, defaults to 1 (left mouse button) :param add: ``True`` or ``False`` -- if ``True``, a new binding will be added, otherwise it will replace a former binding @@ -1274,7 +1274,7 @@ Using events :param fun: a function with two arguments which will be called with the coordinates of the clicked point on the canvas - :param num: number of the mouse-button, defaults to 1 (left mouse button) + :param btn: number of the mouse-button, defaults to 1 (left mouse button) :param add: ``True`` or ``False`` -- if ``True``, a new binding will be added, otherwise it will replace a former binding @@ -1656,7 +1656,7 @@ Using screen events :param fun: a function with two arguments which will be called with the coordinates of the clicked point on the canvas - :param num: number of the mouse-button, defaults to 1 (left mouse button) + :param btn: number of the mouse-button, defaults to 1 (left mouse button) :param add: ``True`` or ``False`` -- if ``True``, a new binding will be added, otherwise it will replace a former binding diff --git a/Lib/lib-tk/turtle.py b/Lib/lib-tk/turtle.py index 52e669b4824c..ae921ce2e5a5 100644 --- a/Lib/lib-tk/turtle.py +++ b/Lib/lib-tk/turtle.py @@ -1300,7 +1300,7 @@ def onclick(self, fun, btn=1, add=None): Arguments: fun -- a function with two arguments, the coordinates of the clicked point on the canvas. - num -- the number of the mouse-button, defaults to 1 + btn -- the number of the mouse-button, defaults to 1 Example (for a TurtleScreen instance named screen and a Turtle instance named turtle): @@ -3418,7 +3418,7 @@ def onclick(self, fun, btn=1, add=None): Arguments: fun -- a function with two arguments, to which will be assigned the coordinates of the clicked point on the canvas. - num -- number of the mouse-button defaults to 1 (left mouse button). + btn -- number of the mouse-button defaults to 1 (left mouse button). add -- True or False. If True, new binding will be added, otherwise it will replace a former binding. @@ -3439,7 +3439,7 @@ def onrelease(self, fun, btn=1, add=None): Arguments: fun -- a function with two arguments, to which will be assigned the coordinates of the clicked point on the canvas. - num -- number of the mouse-button defaults to 1 (left mouse button). + btn -- number of the mouse-button defaults to 1 (left mouse button). Example (for a MyTurtle instance named joe): >>> class MyTurtle(Turtle): @@ -3464,7 +3464,7 @@ def ondrag(self, fun, btn=1, add=None): Arguments: fun -- a function with two arguments, to which will be assigned the coordinates of the clicked point on the canvas. - num -- number of the mouse-button defaults to 1 (left mouse button). + btn -- number of the mouse-button defaults to 1 (left mouse button). Every sequence of mouse-move-events on a turtle is preceded by a mouse-click event on that turtle. From webhook-mailer at python.org Wed Dec 5 16:23:09 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 21:23:09 -0000 Subject: [Python-checkins] bpo-34604: Use %R because of invisible characters or trailing whitespaces. (GH-9165). (GH-10947) Message-ID: https://github.com/python/cpython/commit/ac8b47c8b4edd59aaee857717d434df52ec49e6c commit: ac8b47c8b4edd59aaee857717d434df52ec49e6c branch: 3.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-05T23:23:06+02:00 summary: bpo-34604: Use %R because of invisible characters or trailing whitespaces. (GH-9165). (GH-10947) (cherry picked from commit 34c7f0c04e2b4e715b2c3df1875af8939fbe7d0b) Co-authored-by: William Grzybowski files: A Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst M Modules/grpmodule.c M Modules/pwdmodule.c diff --git a/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst b/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst new file mode 100644 index 000000000000..958b74fd0da6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst @@ -0,0 +1,3 @@ +Fix possible mojibake in the error message of `pwd.getpwnam` and +`grp.getgrnam` using string representation because of invisible characters +or trailing whitespaces. Patch by William Grzybowski. diff --git a/Modules/grpmodule.c b/Modules/grpmodule.c index 43e45ef7aad5..8a724b6b438f 100644 --- a/Modules/grpmodule.c +++ b/Modules/grpmodule.c @@ -156,7 +156,7 @@ grp_getgrnam_impl(PyObject *module, PyObject *name) goto out; if ((p = getgrnam(name_chars)) == NULL) { - PyErr_Format(PyExc_KeyError, "getgrnam(): name not found: %S", name); + PyErr_Format(PyExc_KeyError, "getgrnam(): name not found: %R", name); goto out; } retval = mkgrent(p); diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index 21c2b546f6dd..810427a229b7 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -163,7 +163,7 @@ pwd_getpwnam_impl(PyObject *module, PyObject *arg) goto out; if ((p = getpwnam(name)) == NULL) { PyErr_Format(PyExc_KeyError, - "getpwnam(): name not found: %S", arg); + "getpwnam(): name not found: %R", arg); goto out; } retval = mkpwent(p); From webhook-mailer at python.org Wed Dec 5 16:29:11 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 21:29:11 -0000 Subject: [Python-checkins] bpo-35310: Clear select() lists before returning upon EINTR (GH-10877) Message-ID: https://github.com/python/cpython/commit/b2e0649dd9a36d54478d0edb623a18d7379e6f19 commit: b2e0649dd9a36d54478d0edb623a18d7379e6f19 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T13:29:08-08:00 summary: bpo-35310: Clear select() lists before returning upon EINTR (GH-10877) select() calls are retried on EINTR (per PEP 475). However, if a timeout was provided and the deadline has passed after running the signal handlers, rlist, wlist and xlist should be cleared since select(2) left them unmodified. (cherry picked from commit 7f52415a6d4841d77d3b7853e83b25a22e0048dc) Co-authored-by: Oran Avraham <252748+oranav at users.noreply.github.com> files: A Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst M Modules/selectmodule.c diff --git a/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst b/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst new file mode 100644 index 000000000000..1ab2e168c86a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst @@ -0,0 +1,4 @@ +Fix a bug in :func:`select.select` where, in some cases, the file descriptor +sequences were returned unmodified after a signal interruption, even though the +file descriptors might not be ready yet. :func:`select.select` will now always +return empty lists if a timeout has occurred. Patch by Oran Avraham. diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index f02f5ae4e10c..4b9965724915 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -279,6 +279,10 @@ select_select(PyObject *self, PyObject *args) if (tvp) { timeout = deadline - _PyTime_GetMonotonicClock(); if (timeout < 0) { + /* bpo-35310: lists were unmodified -- clear them explicitly */ + FD_ZERO(&ifdset); + FD_ZERO(&ofdset); + FD_ZERO(&efdset); n = 0; break; } From webhook-mailer at python.org Wed Dec 5 16:31:12 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 21:31:12 -0000 Subject: [Python-checkins] bpo-35310: Clear select() lists before returning upon EINTR (GH-10877) Message-ID: https://github.com/python/cpython/commit/34510781901b75c9aeca79db41ce0fa92c67878f commit: 34510781901b75c9aeca79db41ce0fa92c67878f branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T13:31:07-08:00 summary: bpo-35310: Clear select() lists before returning upon EINTR (GH-10877) select() calls are retried on EINTR (per PEP 475). However, if a timeout was provided and the deadline has passed after running the signal handlers, rlist, wlist and xlist should be cleared since select(2) left them unmodified. (cherry picked from commit 7f52415a6d4841d77d3b7853e83b25a22e0048dc) Co-authored-by: Oran Avraham <252748+oranav at users.noreply.github.com> files: A Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst M Modules/selectmodule.c diff --git a/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst b/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst new file mode 100644 index 000000000000..1ab2e168c86a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-03-19-45-00.bpo-35310.9k28gR.rst @@ -0,0 +1,4 @@ +Fix a bug in :func:`select.select` where, in some cases, the file descriptor +sequences were returned unmodified after a signal interruption, even though the +file descriptors might not be ready yet. :func:`select.select` will now always +return empty lists if a timeout has occurred. Patch by Oran Avraham. diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index 63266af4dce9..88679e814495 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -279,6 +279,10 @@ select_select(PyObject *self, PyObject *args) if (tvp) { timeout = deadline - _PyTime_GetMonotonicClock(); if (timeout < 0) { + /* bpo-35310: lists were unmodified -- clear them explicitly */ + FD_ZERO(&ifdset); + FD_ZERO(&ofdset); + FD_ZERO(&efdset); n = 0; break; } From webhook-mailer at python.org Wed Dec 5 16:41:25 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 21:41:25 -0000 Subject: [Python-checkins] Fix typos in concurrent.Futures documentation (GH-10920) Message-ID: https://github.com/python/cpython/commit/40a61da40d252626f8b9ff524d76c1f0ccb3a4f7 commit: 40a61da40d252626f8b9ff524d76c1f0ccb3a4f7 branch: master author: Matt Wheeler committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-05T13:41:20-08:00 summary: Fix typos in concurrent.Futures documentation (GH-10920) Add a missing word `as` in `as well as an`. Linkify `threading.Thread`. files: M Doc/library/concurrent.futures.rst diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 3890e49f597b..8d6b1e8d71ff 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -141,7 +141,7 @@ And:: each worker thread; *initargs* is a tuple of arguments passed to the initializer. Should *initializer* raise an exception, all currently pending jobs will raise a :exc:`~concurrent.futures.thread.BrokenThreadPool`, - as well any attempt to submit more jobs to the pool. + as well as any attempt to submit more jobs to the pool. .. versionchanged:: 3.5 If *max_workers* is ``None`` or @@ -153,7 +153,7 @@ And:: .. versionadded:: 3.6 The *thread_name_prefix* argument was added to allow users to - control the threading.Thread names for worker threads created by + control the :class:`threading.Thread` names for worker threads created by the pool for easier debugging. .. versionchanged:: 3.7 From webhook-mailer at python.org Wed Dec 5 16:41:55 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 21:41:55 -0000 Subject: [Python-checkins] bpo-35344: platform.platform() uses mac_ver() on macOS (GH-10780) Message-ID: https://github.com/python/cpython/commit/ea0ca218b0c28b2af2b1f6a5d3383569de7fc2c1 commit: ea0ca218b0c28b2af2b1f6a5d3383569de7fc2c1 branch: master author: Victor Stinner committer: GitHub date: 2018-12-05T22:41:52+01:00 summary: bpo-35344: platform.platform() uses mac_ver() on macOS (GH-10780) On macOS, platform.platform() now uses mac_ver(), if it returns a non-empty release string, to get the macOS version rather than darwin version. files: A Misc/NEWS.d/next/Library/2018-11-29-00-23-25.bpo-35344.4QOPJQ.rst M Doc/library/platform.rst M Lib/platform.py M Lib/test/test_platform.py diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 7ac4b027418e..60c6089ad3cc 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -79,6 +79,11 @@ Cross Platform Setting *terse* to true causes the function to return only the absolute minimum information needed to identify the platform. + .. versionchanged:: 3.8 + On macOS, the function now uses :func:`mac_ver`, if it returns a + non-empty release string, to get the macOS version rather than the darwin + version. + .. function:: processor() diff --git a/Lib/platform.py b/Lib/platform.py index f089a463ef9f..d8455256bb9a 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1182,6 +1182,14 @@ def platform(aliased=0, terse=0): if aliased: system, release, version = system_alias(system, release, version) + if system == 'Darwin': + # macOS (darwin kernel) + macos_release = mac_ver()[0] + if macos_release: + # note: 'macOS' is different than 'MacOS' used below + system = 'macOS' + release = macos_release + if system == 'Windows': # MS platforms rel, vers, csd, ptype = win32_ver(version) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 978d2f76ab68..c1a7e3407934 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -10,6 +10,11 @@ from test import support class PlatformTest(unittest.TestCase): + def clear_caches(self): + platform._platform_cache.clear() + platform._sys_version_cache.clear() + platform._uname_cache = None + def test_architecture(self): res = platform.architecture() @@ -344,5 +349,33 @@ def test__comparable_version(self): self.assertLess(V('0.960923'), V('2.2beta29')) + def test_macos(self): + self.addCleanup(self.clear_caches) + + uname = ('Darwin', 'hostname', '17.7.0', + ('Darwin Kernel Version 17.7.0: ' + 'Thu Jun 21 22:53:14 PDT 2018; ' + 'root:xnu-4570.71.2~1/RELEASE_X86_64'), + 'x86_64', 'i386') + arch = ('64bit', '') + with mock.patch.object(platform, 'uname', return_value=uname), \ + mock.patch.object(platform, 'architecture', return_value=arch): + for mac_ver, expected_terse, expected in [ + # darwin: mac_ver() returns empty strings + (('', '', ''), + 'Darwin-17.7.0', + 'Darwin-17.7.0-x86_64-i386-64bit'), + # macOS: mac_ver() returns macOS version + (('10.13.6', ('', '', ''), 'x86_64'), + 'macOS-10.13.6', + 'macOS-10.13.6-x86_64-i386-64bit'), + ]: + with mock.patch.object(platform, 'mac_ver', + return_value=mac_ver): + self.clear_caches() + self.assertEqual(platform.platform(terse=1), expected_terse) + self.assertEqual(platform.platform(), expected) + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2018-11-29-00-23-25.bpo-35344.4QOPJQ.rst b/Misc/NEWS.d/next/Library/2018-11-29-00-23-25.bpo-35344.4QOPJQ.rst new file mode 100644 index 000000000000..a999cbe2a72e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-29-00-23-25.bpo-35344.4QOPJQ.rst @@ -0,0 +1,3 @@ +On macOS, :func:`platform.platform` now uses :func:`platform.mac_ver`, if it +returns a non-empty release string, to get the macOS version rather than the +darwin version. From webhook-mailer at python.org Wed Dec 5 17:02:16 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 22:02:16 -0000 Subject: [Python-checkins] [2.7] bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419). (GH-10950) Message-ID: https://github.com/python/cpython/commit/b2742ba5f9ce8a6108202e0645662f2b58da423b commit: b2742ba5f9ce8a6108202e0645662f2b58da423b branch: 2.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-06T00:02:10+02:00 summary: [2.7] bpo-34738: Add directory entries in ZIP files created by distutils. (GH-9419). (GH-10950) (cherry picked from commit 67a93b3a0b3814e97ef9d077b21325fc8ce351b2) files: A Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst M Lib/distutils/archive_util.py M Lib/distutils/tests/test_archive_util.py M Lib/distutils/tests/test_bdist_dumb.py M Lib/distutils/tests/test_sdist.py diff --git a/Lib/distutils/archive_util.py b/Lib/distutils/archive_util.py index 834b722ed3f1..19a3bc466894 100644 --- a/Lib/distutils/archive_util.py +++ b/Lib/distutils/archive_util.py @@ -162,7 +162,15 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): zip = zipfile.ZipFile(zip_filename, "w", compression=zipfile.ZIP_DEFLATED) + if base_dir != os.curdir: + path = os.path.normpath(os.path.join(base_dir, '')) + zip.write(path, path) + log.info("adding '%s'", path) for dirpath, dirnames, filenames in os.walk(base_dir): + for name in dirnames: + path = os.path.normpath(os.path.join(dirpath, name, '')) + zip.write(path, path) + log.info("adding '%s'", path) for name in filenames: path = os.path.normpath(os.path.join(dirpath, name)) if os.path.isfile(path): diff --git a/Lib/distutils/tests/test_archive_util.py b/Lib/distutils/tests/test_archive_util.py index ed7c2cea69cb..137100cca83d 100644 --- a/Lib/distutils/tests/test_archive_util.py +++ b/Lib/distutils/tests/test_archive_util.py @@ -98,7 +98,7 @@ def _tarinfo(self, path): try: names = tar.getnames() names.sort() - return tuple(names) + return names finally: tar.close() diff --git a/Lib/distutils/tests/test_bdist_dumb.py b/Lib/distutils/tests/test_bdist_dumb.py index 5db3a850f8e4..ef9e68131b1a 100644 --- a/Lib/distutils/tests/test_bdist_dumb.py +++ b/Lib/distutils/tests/test_bdist_dumb.py @@ -86,7 +86,7 @@ def test_simple_built(self): finally: fp.close() - contents = sorted(os.path.basename(fn) for fn in contents) + contents = sorted(filter(None, map(os.path.basename, contents))) wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py'] if not sys.dont_write_bytecode: wanted.append('foo.pyc') diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py index 02c1d12e20cc..c503bd62b7a4 100644 --- a/Lib/distutils/tests/test_sdist.py +++ b/Lib/distutils/tests/test_sdist.py @@ -130,7 +130,9 @@ def test_prune_file_list(self): zip_file.close() # making sure everything has been pruned correctly - self.assertEqual(len(content), 4) + expected = ['', 'PKG-INFO', 'README', 'setup.py', + 'somecode/', 'somecode/__init__.py'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) @unittest.skipUnless(zlib, "requires zlib") def test_make_distribution(self): @@ -246,7 +248,13 @@ def test_add_defaults(self): zip_file.close() # making sure everything was added - self.assertEqual(len(content), 12) + expected = ['', 'PKG-INFO', 'README', 'buildout.cfg', + 'data/', 'data/data.dt', 'inroot.txt', + 'scripts/', 'scripts/script.py', 'setup.py', + 'some/', 'some/file.txt', 'some/other_file.txt', + 'somecode/', 'somecode/__init__.py', 'somecode/doc.dat', + 'somecode/doc.txt'] + self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) # checking the MANIFEST f = open(join(self.tmp_dir, 'MANIFEST')) diff --git a/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst new file mode 100644 index 000000000000..c3f402d39ae0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-19-16-51-04.bpo-34738.Pr3-iG.rst @@ -0,0 +1,2 @@ +ZIP files created by :mod:`distutils` will now include entries for +directories. From webhook-mailer at python.org Wed Dec 5 17:03:18 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 22:03:18 -0000 Subject: [Python-checkins] bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (GH-10952) Message-ID: https://github.com/python/cpython/commit/fdf505000f135df3bdae08697b2a324d8f046768 commit: fdf505000f135df3bdae08697b2a324d8f046768 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Serhiy Storchaka date: 2018-12-06T00:03:13+02:00 summary: bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (GH-10952) (cherry picked from commit 5b25f1d03100e2283c1b129d461ba68ac0169a14) (cherry picked from commit 1de91a0032fed500ddd3d8c4fb7a38c0b8719f67) Co-authored-by: Sergey Fedoseev files: A Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst M Lib/sqlite3/test/regression.py M Modules/_sqlite/connection.c diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py index 34cd233535dc..1c59a3cd31c6 100644 --- a/Lib/sqlite3/test/regression.py +++ b/Lib/sqlite3/test/regression.py @@ -256,24 +256,6 @@ def CheckPragmaAutocommit(self): cur.execute("pragma page_size") row = cur.fetchone() - def CheckSetDict(self): - """ - See http://bugs.python.org/issue7478 - - It was possible to successfully register callbacks that could not be - hashed. Return codes of PyDict_SetItem were not checked properly. - """ - class NotHashable: - def __call__(self, *args, **kw): - pass - def __hash__(self): - raise TypeError() - var = NotHashable() - self.assertRaises(TypeError, self.con.create_function, var) - self.assertRaises(TypeError, self.con.create_aggregate, var) - self.assertRaises(TypeError, self.con.set_authorizer, var) - self.assertRaises(TypeError, self.con.set_progress_handler, var) - def CheckConnectionCall(self): """ Call a connection with a non-string SQL request: check error handling @@ -398,9 +380,72 @@ def callback(*args): support.gc_collect() +class UnhashableFunc: + __hash__ = None + + def __init__(self, return_value=None): + self.calls = 0 + self.return_value = return_value + + def __call__(self, *args, **kwargs): + self.calls += 1 + return self.return_value + + +class UnhashableCallbacksTestCase(unittest.TestCase): + """ + https://bugs.python.org/issue34052 + + Registering unhashable callbacks raises TypeError, callbacks are not + registered in SQLite after such registration attempt. + """ + def setUp(self): + self.con = sqlite.connect(':memory:') + + def tearDown(self): + self.con.close() + + def test_progress_handler(self): + f = UnhashableFunc(return_value=0) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.set_progress_handler(f, 1) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_func(self): + func_name = 'func_name' + f = UnhashableFunc() + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.create_function(func_name, 0, f) + msg = 'no such function: %s' % func_name + with self.assertRaisesRegex(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % func_name) + self.assertFalse(f.calls) + + def test_authorizer(self): + f = UnhashableFunc(return_value=sqlite.SQLITE_DENY) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.set_authorizer(f) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_aggr(self): + class UnhashableType(type): + __hash__ = None + aggr_name = 'aggr_name' + with self.assertRaisesRegex(TypeError, 'unhashable type'): + self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {})) + msg = 'no such function: %s' % aggr_name + with self.assertRaisesRegex(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % aggr_name) + + def suite(): regression_suite = unittest.makeSuite(RegressionTests, "Check") - return unittest.TestSuite((regression_suite,)) + return unittest.TestSuite(( + regression_suite, + unittest.makeSuite(UnhashableCallbacksTestCase), + )) def test(): runner = unittest.TextTestRunner() diff --git a/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst new file mode 100644 index 000000000000..5aa3cc9a81d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst @@ -0,0 +1,7 @@ +:meth:`sqlite3.Connection.create_aggregate`, +:meth:`sqlite3.Connection.create_function`, +:meth:`sqlite3.Connection.set_authorizer`, +:meth:`sqlite3.Connection.set_progress_handler` methods raises TypeError +when unhashable objects are passed as callable. These methods now don't pass +such objects to SQLite API. Previous behavior could lead to segfaults. Patch +by Sergey Fedoseev. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index d86caef7ab60..326d268a7a4e 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -836,18 +836,17 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec return NULL; } + if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating function"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -868,17 +867,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje return NULL; } + if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source) @@ -1003,17 +1001,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P return NULL; } + if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) { + return NULL; + } rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb); - if (rc != SQLITE_OK) { PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) - return NULL; - - Py_RETURN_NONE; } + Py_RETURN_NONE; } static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -1036,9 +1032,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s /* None clears the progress handler previously set */ sqlite3_progress_handler(self->db, 0, 0, (void*)0); } else { - sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1) return NULL; + sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); } Py_RETURN_NONE; From webhook-mailer at python.org Wed Dec 5 17:04:24 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 22:04:24 -0000 Subject: [Python-checkins] bpo-34604: Use %R because of invisible characters or trailing whitespaces. (GH-9165). (GH-10947) (GH-10954) Message-ID: https://github.com/python/cpython/commit/3a9b3346b03796d8573c063ab4c2407043609459 commit: 3a9b3346b03796d8573c063ab4c2407043609459 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: Serhiy Storchaka date: 2018-12-06T00:04:19+02:00 summary: bpo-34604: Use %R because of invisible characters or trailing whitespaces. (GH-9165). (GH-10947) (GH-10954) (cherry picked from commit 34c7f0c04e2b4e715b2c3df1875af8939fbe7d0b) (cherry picked from commit ac8b47c8b4edd59aaee857717d434df52ec49e6c) Co-authored-by: William Grzybowski files: A Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst M Modules/grpmodule.c M Modules/pwdmodule.c diff --git a/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst b/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst new file mode 100644 index 000000000000..958b74fd0da6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-09-07-10-16-34.bpo-34604.xL7-kG.rst @@ -0,0 +1,3 @@ +Fix possible mojibake in the error message of `pwd.getpwnam` and +`grp.getgrnam` using string representation because of invisible characters +or trailing whitespaces. Patch by William Grzybowski. diff --git a/Modules/grpmodule.c b/Modules/grpmodule.c index 43e45ef7aad5..8a724b6b438f 100644 --- a/Modules/grpmodule.c +++ b/Modules/grpmodule.c @@ -156,7 +156,7 @@ grp_getgrnam_impl(PyObject *module, PyObject *name) goto out; if ((p = getgrnam(name_chars)) == NULL) { - PyErr_Format(PyExc_KeyError, "getgrnam(): name not found: %S", name); + PyErr_Format(PyExc_KeyError, "getgrnam(): name not found: %R", name); goto out; } retval = mkgrent(p); diff --git a/Modules/pwdmodule.c b/Modules/pwdmodule.c index 21c2b546f6dd..810427a229b7 100644 --- a/Modules/pwdmodule.c +++ b/Modules/pwdmodule.c @@ -163,7 +163,7 @@ pwd_getpwnam_impl(PyObject *module, PyObject *arg) goto out; if ((p = getpwnam(name)) == NULL) { PyErr_Format(PyExc_KeyError, - "getpwnam(): name not found: %S", arg); + "getpwnam(): name not found: %R", arg); goto out; } retval = mkpwent(p); From webhook-mailer at python.org Wed Dec 5 17:15:47 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 22:15:47 -0000 Subject: [Python-checkins] Fix typo in xml.dom.minidom documentation (GH-10956) Message-ID: https://github.com/python/cpython/commit/2d8f976cde4794d174b44ff7d5fd970aa89c65e8 commit: 2d8f976cde4794d174b44ff7d5fd970aa89c65e8 branch: master author: E Kawashima committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-05T14:15:42-08:00 summary: Fix typo in xml.dom.minidom documentation (GH-10956) Escape the \t and \n. Follow up from https://github.com/python/cpython/pull/10814. files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index 42340802f193..fb6fe6e66143 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -163,7 +163,7 @@ module documentation. This section lists the differences between the API and The :meth:`toxml` method now preserves the attribute order specified by the user. -.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) +.. method:: Node.toprettyxml(indent="\\t", newl="\\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Wed Dec 5 17:17:31 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 22:17:31 -0000 Subject: [Python-checkins] Fix typos in concurrent.Futures documentation (GH-10920) Message-ID: https://github.com/python/cpython/commit/022d7bc7ccad5b3b3c62450731142e250f1e547d commit: 022d7bc7ccad5b3b3c62450731142e250f1e547d branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T14:17:28-08:00 summary: Fix typos in concurrent.Futures documentation (GH-10920) Add a missing word `as` in `as well as an`. Linkify `threading.Thread`. (cherry picked from commit 40a61da40d252626f8b9ff524d76c1f0ccb3a4f7) Co-authored-by: Matt Wheeler files: M Doc/library/concurrent.futures.rst diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index b1b086b442de..a57491543bf4 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -141,7 +141,7 @@ And:: each worker thread; *initargs* is a tuple of arguments passed to the initializer. Should *initializer* raise an exception, all currently pending jobs will raise a :exc:`~concurrent.futures.thread.BrokenThreadPool`, - as well any attempt to submit more jobs to the pool. + as well as any attempt to submit more jobs to the pool. .. versionchanged:: 3.5 If *max_workers* is ``None`` or @@ -153,7 +153,7 @@ And:: .. versionadded:: 3.6 The *thread_name_prefix* argument was added to allow users to - control the threading.Thread names for worker threads created by + control the :class:`threading.Thread` names for worker threads created by the pool for easier debugging. .. versionchanged:: 3.7 From webhook-mailer at python.org Wed Dec 5 17:19:21 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 22:19:21 -0000 Subject: [Python-checkins] [3.6] Fix typos in concurrent.Futures documentation (GH-10920) (GH-10958) Message-ID: https://github.com/python/cpython/commit/2997fa42bbd7a079a316b9acb134c5d9d12f05ee commit: 2997fa42bbd7a079a316b9acb134c5d9d12f05ee branch: 3.6 author: Mariatta committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-05T14:19:17-08:00 summary: [3.6] Fix typos in concurrent.Futures documentation (GH-10920) (GH-10958) Add a missing word `as` in `as well as an`. Linkify `threading.Thread`.. (cherry picked from commit 40a61da40d252626f8b9ff524d76c1f0ccb3a4f7) Co-authored-by: Matt Wheeler files: M Doc/library/concurrent.futures.rst diff --git a/Doc/library/concurrent.futures.rst b/Doc/library/concurrent.futures.rst index 9794e735d422..69dfd0f47c1c 100644 --- a/Doc/library/concurrent.futures.rst +++ b/Doc/library/concurrent.futures.rst @@ -137,6 +137,12 @@ And:: An :class:`Executor` subclass that uses a pool of at most *max_workers* threads to execute calls asynchronously. + *initializer* is an optional callable that is called at the start of + each worker thread; *initargs* is a tuple of arguments passed to the + initializer. Should *initializer* raise an exception, all currently + pending jobs will raise a :exc:`~concurrent.futures.thread.BrokenThreadPool`, + as well any attempt to submit more jobs to the pool. + .. versionchanged:: 3.5 If *max_workers* is ``None`` or not given, it will default to the number of processors on the machine, @@ -147,7 +153,7 @@ And:: .. versionadded:: 3.6 The *thread_name_prefix* argument was added to allow users to - control the threading.Thread names for worker threads created by + control the :class:`threading.Thread` names for worker threads created by the pool for easier debugging. .. _threadpoolexecutor-example: From webhook-mailer at python.org Wed Dec 5 17:21:44 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Wed, 05 Dec 2018 22:21:44 -0000 Subject: [Python-checkins] [2.7] bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (GH-10955) Message-ID: https://github.com/python/cpython/commit/fff8fab1ce4af208cd9c6cd84a8be626a1b744d8 commit: fff8fab1ce4af208cd9c6cd84a8be626a1b744d8 branch: 2.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-06T00:21:40+02:00 summary: [2.7] bpo-34052: Prevent SQLite functions from setting callbacks on exceptions. (GH-8113). (GH-10946) (GH-10955) (cherry picked from commit 5b25f1d03100e2283c1b129d461ba68ac0169a14) (cherry picked from commit 1de91a0032fed500ddd3d8c4fb7a38c0b8719f67) Co-authored-by: Sergey Fedoseev . files: A Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst M Lib/sqlite3/test/regression.py M Modules/_sqlite/connection.c diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py index 7eeac324d272..271afb0a7f03 100644 --- a/Lib/sqlite3/test/regression.py +++ b/Lib/sqlite3/test/regression.py @@ -246,24 +246,6 @@ def CheckPragmaAutocommit(self): cur.execute("pragma page_size") row = cur.fetchone() - def CheckSetDict(self): - """ - See http://bugs.python.org/issue7478 - - It was possible to successfully register callbacks that could not be - hashed. Return codes of PyDict_SetItem were not checked properly. - """ - class NotHashable: - def __call__(self, *args, **kw): - pass - def __hash__(self): - raise TypeError() - var = NotHashable() - self.assertRaises(TypeError, self.con.create_function, var) - self.assertRaises(TypeError, self.con.create_aggregate, var) - self.assertRaises(TypeError, self.con.set_authorizer, var) - self.assertRaises(TypeError, self.con.set_progress_handler, var) - def CheckConnectionCall(self): """ Call a connection with a non-string SQL request: check error handling @@ -380,9 +362,73 @@ def callback(*args): support.gc_collect() +class UnhashableFunc: + def __hash__(self): + raise TypeError('unhashable type') + + def __init__(self, return_value=None): + self.calls = 0 + self.return_value = return_value + + def __call__(self, *args, **kwargs): + self.calls += 1 + return self.return_value + + +class UnhashableCallbacksTestCase(unittest.TestCase): + """ + https://bugs.python.org/issue34052 + + Registering unhashable callbacks raises TypeError, callbacks are not + registered in SQLite after such registration attempt. + """ + def setUp(self): + self.con = sqlite.connect(':memory:') + + def tearDown(self): + self.con.close() + + def test_progress_handler(self): + f = UnhashableFunc(return_value=0) + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + self.con.set_progress_handler(f, 1) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_func(self): + func_name = 'func_name' + f = UnhashableFunc() + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + self.con.create_function(func_name, 0, f) + msg = 'no such function: %s' % func_name + with self.assertRaisesRegexp(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % func_name) + self.assertFalse(f.calls) + + def test_authorizer(self): + f = UnhashableFunc(return_value=sqlite.SQLITE_DENY) + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + self.con.set_authorizer(f) + self.con.execute('SELECT 1') + self.assertFalse(f.calls) + + def test_aggr(self): + class UnhashableType(type): + __hash__ = None + aggr_name = 'aggr_name' + with self.assertRaisesRegexp(TypeError, 'unhashable type'): + self.con.create_aggregate(aggr_name, 0, UnhashableType('Aggr', (), {})) + msg = 'no such function: %s' % aggr_name + with self.assertRaisesRegexp(sqlite.OperationalError, msg): + self.con.execute('SELECT %s()' % aggr_name) + + def suite(): regression_suite = unittest.makeSuite(RegressionTests, "Check") - return unittest.TestSuite((regression_suite,)) + return unittest.TestSuite(( + regression_suite, + unittest.makeSuite(UnhashableCallbacksTestCase), + )) def test(): runner = unittest.TextTestRunner() diff --git a/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst new file mode 100644 index 000000000000..5aa3cc9a81d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-07-24-16-37-40.bpo-34052.VbbFAE.rst @@ -0,0 +1,7 @@ +:meth:`sqlite3.Connection.create_aggregate`, +:meth:`sqlite3.Connection.create_function`, +:meth:`sqlite3.Connection.set_authorizer`, +:meth:`sqlite3.Connection.set_progress_handler` methods raises TypeError +when unhashable objects are passed as callable. These methods now don't pass +such objects to SQLite API. Previous behavior could lead to segfaults. Patch +by Sergey Fedoseev. diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index c3f39fd78a2b..585453a28207 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -877,19 +877,17 @@ PyObject* pysqlite_connection_create_function(pysqlite_Connection* self, PyObjec return NULL; } + if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, narg, SQLITE_UTF8, (void*)func, _pysqlite_func_callback, NULL, NULL); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating function"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, func, Py_None) == -1) - return NULL; - - Py_INCREF(Py_None); - return Py_None; } + Py_RETURN_NONE; } PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -910,18 +908,16 @@ PyObject* pysqlite_connection_create_aggregate(pysqlite_Connection* self, PyObje return NULL; } + if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) { + return NULL; + } rc = sqlite3_create_function(self->db, name, n_arg, SQLITE_UTF8, (void*)aggregate_class, 0, &_pysqlite_step_callback, &_pysqlite_final_callback); if (rc != SQLITE_OK) { /* Workaround for SQLite bug: no error code or string is available here */ PyErr_SetString(pysqlite_OperationalError, "Error creating aggregate"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, aggregate_class, Py_None) == -1) - return NULL; - - Py_INCREF(Py_None); - return Py_None; } + Py_RETURN_NONE; } static int _authorizer_callback(void* user_arg, int action, const char* arg1, const char* arg2 , const char* dbname, const char* access_attempt_source) @@ -1007,18 +1003,15 @@ static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, P return NULL; } + if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) { + return NULL; + } rc = sqlite3_set_authorizer(self->db, _authorizer_callback, (void*)authorizer_cb); - if (rc != SQLITE_OK) { PyErr_SetString(pysqlite_OperationalError, "Error setting authorizer callback"); return NULL; - } else { - if (PyDict_SetItem(self->function_pinboard, authorizer_cb, Py_None) == -1) - return NULL; - - Py_INCREF(Py_None); - return Py_None; } + Py_RETURN_NONE; } static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* self, PyObject* args, PyObject* kwargs) @@ -1041,9 +1034,9 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s /* None clears the progress handler previously set */ sqlite3_progress_handler(self->db, 0, 0, (void*)0); } else { - sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); if (PyDict_SetItem(self->function_pinboard, progress_handler, Py_None) == -1) return NULL; + sqlite3_progress_handler(self->db, n, _progress_handler, progress_handler); } Py_INCREF(Py_None); From webhook-mailer at python.org Wed Dec 5 17:21:57 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 22:21:57 -0000 Subject: [Python-checkins] bpo-35389: test.pythoninfo logs platform.libc_ver (GH-10951) Message-ID: https://github.com/python/cpython/commit/848acf7249b5669d73d70a7cb6e5ab60689cf825 commit: 848acf7249b5669d73d70a7cb6e5ab60689cf825 branch: master author: Victor Stinner committer: GitHub date: 2018-12-05T23:21:54+01:00 summary: bpo-35389: test.pythoninfo logs platform.libc_ver (GH-10951) files: M Lib/test/pythoninfo.py diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 9befd12e4095..81fca10eaeb1 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -144,6 +144,10 @@ def collect_platform(info_add): info_add('platform.platform', platform.platform(aliased=True)) + libc_ver = ('%s %s' % platform.libc_ver()).strip() + if libc_ver: + info_add('platform.libc_ver', libc_ver) + def collect_locale(info_add): import locale From webhook-mailer at python.org Wed Dec 5 17:31:15 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 22:31:15 -0000 Subject: [Python-checkins] Fix typo in xml.dom.minidom documentation (GH-10956) Message-ID: https://github.com/python/cpython/commit/40ef5b73c240ad0a73b734919b9e4f7062ae5b1f commit: 40ef5b73c240ad0a73b734919b9e4f7062ae5b1f branch: 2.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T14:31:11-08:00 summary: Fix typo in xml.dom.minidom documentation (GH-10956) Escape the \t and \n. Follow up from https://github.com/python/cpython/pull/10814. (cherry picked from commit 2d8f976cde4794d174b44ff7d5fd970aa89c65e8) Co-authored-by: E Kawashima files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index 2f4022428db0..2df5bdd5cf3b 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -169,7 +169,7 @@ module documentation. This section lists the differences between the API and the *encoding* argument was introduced; see :meth:`writexml`. -.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) +.. method:: Node.toprettyxml(indent="\\t", newl="\\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Wed Dec 5 18:18:36 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Wed, 05 Dec 2018 23:18:36 -0000 Subject: [Python-checkins] bpo-35363: test_eintr runs eintr_tester.py in verbose mode (GH-10965) Message-ID: https://github.com/python/cpython/commit/aa8ae904ad2f576f8e7b38a9a6542d3e9a569be9 commit: aa8ae904ad2f576f8e7b38a9a6542d3e9a569be9 branch: master author: Victor Stinner committer: GitHub date: 2018-12-06T00:18:30+01:00 summary: bpo-35363: test_eintr runs eintr_tester.py in verbose mode (GH-10965) Moreover, "python3 -m test test_eintr -v" now avoids redirecting stdout/stderr to a pipe, the child process inherits stdout/stderr from the parent. files: M Lib/test/test_eintr.py diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index 25f86d31de35..c2e8deadbab7 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -1,5 +1,7 @@ import os import signal +import subprocess +import sys import unittest from test import support @@ -15,7 +17,19 @@ def test_all(self): # thread (for reliable signal delivery). tester = support.findfile("eintr_tester.py", subdir="eintrdata") # use -u to try to get the full output if the test hangs or crash - script_helper.assert_python_ok("-u", tester) + args = ["-u", tester, "-v"] + if support.verbose: + print() + print("--- run eintr_tester.py ---") + # In verbose mode, the child process inherit stdout and stdout, + # to see output in realtime and reduce the risk of loosing output. + args = [sys.executable, "-E", "-X", "faulthandler", *args] + proc = subprocess.run(args) + print(f"--- eintr_tester.py completed: exit code {proc.returncode} ---") + if proc.returncode: + self.fail("eintr_tester.py failed") + else: + script_helper.assert_python_ok("-u", tester, "-v") if __name__ == "__main__": From webhook-mailer at python.org Wed Dec 5 18:35:47 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 23:35:47 -0000 Subject: [Python-checkins] bpo-35363: test_eintr runs eintr_tester.py in verbose mode (GH-10965) Message-ID: https://github.com/python/cpython/commit/0fc3b2fe010e42a8c146fb84924e9fd33c6f4e29 commit: 0fc3b2fe010e42a8c146fb84924e9fd33c6f4e29 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T15:35:43-08:00 summary: bpo-35363: test_eintr runs eintr_tester.py in verbose mode (GH-10965) Moreover, "python3 -m test test_eintr -v" now avoids redirecting stdout/stderr to a pipe, the child process inherits stdout/stderr from the parent. (cherry picked from commit aa8ae904ad2f576f8e7b38a9a6542d3e9a569be9) Co-authored-by: Victor Stinner files: M Lib/test/test_eintr.py diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index 25f86d31de35..c2e8deadbab7 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -1,5 +1,7 @@ import os import signal +import subprocess +import sys import unittest from test import support @@ -15,7 +17,19 @@ def test_all(self): # thread (for reliable signal delivery). tester = support.findfile("eintr_tester.py", subdir="eintrdata") # use -u to try to get the full output if the test hangs or crash - script_helper.assert_python_ok("-u", tester) + args = ["-u", tester, "-v"] + if support.verbose: + print() + print("--- run eintr_tester.py ---") + # In verbose mode, the child process inherit stdout and stdout, + # to see output in realtime and reduce the risk of loosing output. + args = [sys.executable, "-E", "-X", "faulthandler", *args] + proc = subprocess.run(args) + print(f"--- eintr_tester.py completed: exit code {proc.returncode} ---") + if proc.returncode: + self.fail("eintr_tester.py failed") + else: + script_helper.assert_python_ok("-u", tester, "-v") if __name__ == "__main__": From webhook-mailer at python.org Wed Dec 5 18:43:42 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 23:43:42 -0000 Subject: [Python-checkins] bpo-35363: test_eintr runs eintr_tester.py in verbose mode (GH-10965) Message-ID: https://github.com/python/cpython/commit/4699f2aa26b2f8befa77852e0c6fba0b474a2748 commit: 4699f2aa26b2f8befa77852e0c6fba0b474a2748 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T15:43:38-08:00 summary: bpo-35363: test_eintr runs eintr_tester.py in verbose mode (GH-10965) Moreover, "python3 -m test test_eintr -v" now avoids redirecting stdout/stderr to a pipe, the child process inherits stdout/stderr from the parent. (cherry picked from commit aa8ae904ad2f576f8e7b38a9a6542d3e9a569be9) Co-authored-by: Victor Stinner files: M Lib/test/test_eintr.py diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index 25f86d31de35..c2e8deadbab7 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -1,5 +1,7 @@ import os import signal +import subprocess +import sys import unittest from test import support @@ -15,7 +17,19 @@ def test_all(self): # thread (for reliable signal delivery). tester = support.findfile("eintr_tester.py", subdir="eintrdata") # use -u to try to get the full output if the test hangs or crash - script_helper.assert_python_ok("-u", tester) + args = ["-u", tester, "-v"] + if support.verbose: + print() + print("--- run eintr_tester.py ---") + # In verbose mode, the child process inherit stdout and stdout, + # to see output in realtime and reduce the risk of loosing output. + args = [sys.executable, "-E", "-X", "faulthandler", *args] + proc = subprocess.run(args) + print(f"--- eintr_tester.py completed: exit code {proc.returncode} ---") + if proc.returncode: + self.fail("eintr_tester.py failed") + else: + script_helper.assert_python_ok("-u", tester, "-v") if __name__ == "__main__": From webhook-mailer at python.org Wed Dec 5 18:53:24 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Wed, 05 Dec 2018 23:53:24 -0000 Subject: [Python-checkins] [3.6] Fix typo in xml.dom.minidom documentation (GH-10956) (GH-10962) Message-ID: https://github.com/python/cpython/commit/f7fe18a9254cb1a868801968f315467547d33996 commit: f7fe18a9254cb1a868801968f315467547d33996 branch: 3.6 author: Mariatta committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-05T15:53:21-08:00 summary: [3.6] Fix typo in xml.dom.minidom documentation (GH-10956) (GH-10962) Escape the \t and \n. Follow up from https://github.com/python/cpython/pull/10814.. (cherry picked from commit 2d8f976cde4794d174b44ff7d5fd970aa89c65e8) Co-authored-by: E Kawashima files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index d5d7b20efe60..15b1cb0cbf78 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -156,7 +156,7 @@ module documentation. This section lists the differences between the API and encoding. Encoding this string in an encoding other than UTF-8 is likely incorrect, since UTF-8 is the default encoding of XML. -.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) +.. method:: Node.toprettyxml(indent="\\t", newl="\\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Wed Dec 5 19:24:48 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 00:24:48 -0000 Subject: [Python-checkins] [3.7] Fix typo in xml.dom.minidom documentation (GH-10956) (GH-10961) Message-ID: https://github.com/python/cpython/commit/c28317e4c2520f6df7a281f1664d63a6447d1ebb commit: c28317e4c2520f6df7a281f1664d63a6447d1ebb branch: 3.7 author: Mariatta committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-05T16:24:44-08:00 summary: [3.7] Fix typo in xml.dom.minidom documentation (GH-10956) (GH-10961) Escape the \t and \n. Follow up from https://github.com/python/cpython/pull/10814.. (cherry picked from commit 2d8f976cde4794d174b44ff7d5fd970aa89c65e8) Co-authored-by: E Kawashima files: M Doc/library/xml.dom.minidom.rst diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst index d5d7b20efe60..15b1cb0cbf78 100644 --- a/Doc/library/xml.dom.minidom.rst +++ b/Doc/library/xml.dom.minidom.rst @@ -156,7 +156,7 @@ module documentation. This section lists the differences between the API and encoding. Encoding this string in an encoding other than UTF-8 is likely incorrect, since UTF-8 is the default encoding of XML. -.. method:: Node.toprettyxml(indent="\t", newl="\n", encoding=None) +.. method:: Node.toprettyxml(indent="\\t", newl="\\n", encoding=None) Return a pretty-printed version of the document. *indent* specifies the indentation string and defaults to a tabulator; *newl* specifies the string From webhook-mailer at python.org Wed Dec 5 19:49:08 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 00:49:08 -0000 Subject: [Python-checkins] [3.7] Revert "bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450) (GH-9676)" (#10968) Message-ID: https://github.com/python/cpython/commit/3c6b0d967eb4c95e06c4f1beddfca4f6300d92ce commit: 3c6b0d967eb4c95e06c4f1beddfca4f6300d92ce branch: 3.7 author: Victor Stinner committer: GitHub date: 2018-12-06T01:49:05+01:00 summary: [3.7] Revert "bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450) (GH-9676)" (#10968) This reverts commit 97f998a4dfd6db6d867f446daa62445d0782bf39. files: A Misc/NEWS.d/next/Library/2018-12-06-00-29-28.bpo-34172.l7CIYt.rst M Lib/multiprocessing/pool.py M Lib/test/_test_multiprocessing.py diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py index 574b5db5afb6..3e9a0d6b4867 100644 --- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -149,9 +149,8 @@ class Pool(object): ''' _wrap_exception = True - @staticmethod - def Process(ctx, *args, **kwds): - return ctx.Process(*args, **kwds) + def Process(self, *args, **kwds): + return self._ctx.Process(*args, **kwds) def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): @@ -178,15 +177,13 @@ def __init__(self, processes=None, initializer=None, initargs=(), self._worker_handler = threading.Thread( target=Pool._handle_workers, - args=(self._cache, self._taskqueue, self._ctx, self.Process, - self._processes, self._pool, self._inqueue, self._outqueue, - self._initializer, self._initargs, self._maxtasksperchild, - self._wrap_exception) + args=(self, ) ) self._worker_handler.daemon = True self._worker_handler._state = RUN self._worker_handler.start() + self._task_handler = threading.Thread( target=Pool._handle_tasks, args=(self._taskqueue, self._quick_put, self._outqueue, @@ -212,62 +209,43 @@ def __init__(self, processes=None, initializer=None, initargs=(), exitpriority=15 ) - @staticmethod - def _join_exited_workers(pool): + def _join_exited_workers(self): """Cleanup after any worker processes which have exited due to reaching their specified lifetime. Returns True if any workers were cleaned up. """ cleaned = False - for i in reversed(range(len(pool))): - worker = pool[i] + for i in reversed(range(len(self._pool))): + worker = self._pool[i] if worker.exitcode is not None: # worker exited util.debug('cleaning up worker %d' % i) worker.join() cleaned = True - del pool[i] + del self._pool[i] return cleaned def _repopulate_pool(self): - return self._repopulate_pool_static(self._ctx, self.Process, - self._processes, - self._pool, self._inqueue, - self._outqueue, self._initializer, - self._initargs, - self._maxtasksperchild, - self._wrap_exception) - - @staticmethod - def _repopulate_pool_static(ctx, Process, processes, pool, inqueue, - outqueue, initializer, initargs, - maxtasksperchild, wrap_exception): """Bring the number of pool processes up to the specified number, for use after reaping workers which have exited. """ - for i in range(processes - len(pool)): - w = Process(ctx, target=worker, - args=(inqueue, outqueue, - initializer, - initargs, maxtasksperchild, - wrap_exception) - ) - pool.append(w) + for i in range(self._processes - len(self._pool)): + w = self.Process(target=worker, + args=(self._inqueue, self._outqueue, + self._initializer, + self._initargs, self._maxtasksperchild, + self._wrap_exception) + ) + self._pool.append(w) w.name = w.name.replace('Process', 'PoolWorker') w.daemon = True w.start() util.debug('added worker') - @staticmethod - def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue, - initializer, initargs, maxtasksperchild, - wrap_exception): + def _maintain_pool(self): """Clean up any exited workers and start replacements for them. """ - if Pool._join_exited_workers(pool): - Pool._repopulate_pool_static(ctx, Process, processes, pool, - inqueue, outqueue, initializer, - initargs, maxtasksperchild, - wrap_exception) + if self._join_exited_workers(): + self._repopulate_pool() def _setup_queues(self): self._inqueue = self._ctx.SimpleQueue() @@ -425,20 +403,16 @@ def _map_async(self, func, iterable, mapper, chunksize=None, callback=None, return result @staticmethod - def _handle_workers(cache, taskqueue, ctx, Process, processes, pool, - inqueue, outqueue, initializer, initargs, - maxtasksperchild, wrap_exception): + def _handle_workers(pool): thread = threading.current_thread() # Keep maintaining workers until the cache gets drained, unless the pool # is terminated. - while thread._state == RUN or (cache and thread._state != TERMINATE): - Pool._maintain_pool(ctx, Process, processes, pool, inqueue, - outqueue, initializer, initargs, - maxtasksperchild, wrap_exception) + while thread._state == RUN or (pool._cache and thread._state != TERMINATE): + pool._maintain_pool() time.sleep(0.1) # send sentinel to stop workers - taskqueue.put(None) + pool._taskqueue.put(None) util.debug('worker handler exiting') @staticmethod @@ -820,7 +794,7 @@ class ThreadPool(Pool): _wrap_exception = False @staticmethod - def Process(ctx, *args, **kwds): + def Process(*args, **kwds): from .dummy import Process return Process(*args, **kwds) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 7a657d9d120c..a0daa43a55a1 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2552,13 +2552,6 @@ def test_release_task_refs(self): # they were released too. self.assertEqual(CountedObject.n_instances, 0) - @support.reap_threads - def test_del_pool(self): - p = self.Pool(1) - wr = weakref.ref(p) - del p - gc.collect() - self.assertIsNone(wr()) def raising(): raise KeyError("key") diff --git a/Misc/NEWS.d/next/Library/2018-12-06-00-29-28.bpo-34172.l7CIYt.rst b/Misc/NEWS.d/next/Library/2018-12-06-00-29-28.bpo-34172.l7CIYt.rst new file mode 100644 index 000000000000..e467cc967825 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-06-00-29-28.bpo-34172.l7CIYt.rst @@ -0,0 +1,3 @@ +REVERT: Fix a reference issue inside multiprocessing.Pool that caused the +pool to remain alive if it was deleted without being closed or terminated +explicitly. From webhook-mailer at python.org Wed Dec 5 19:49:38 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 00:49:38 -0000 Subject: [Python-checkins] [3.6] Revert "bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450) (GH-9677)" (GH-10969) Message-ID: https://github.com/python/cpython/commit/eb38ee052e2273568d0041e969aa851ee44e43ce commit: eb38ee052e2273568d0041e969aa851ee44e43ce branch: 3.6 author: Victor Stinner committer: GitHub date: 2018-12-06T01:49:34+01:00 summary: [3.6] Revert "bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450) (GH-9677)" (GH-10969) This reverts commit 07b96a95db78eff3557d1bfed1df9ebecc40815b. files: A Misc/NEWS.d/next/Library/2018-12-06-00-31-25.bpo-34172.l7CIYt.rst M Lib/multiprocessing/pool.py M Lib/test/_test_multiprocessing.py diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py index 32254d8ea6cf..a545f3c1a189 100644 --- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -147,9 +147,8 @@ class Pool(object): ''' _wrap_exception = True - @staticmethod - def Process(ctx, *args, **kwds): - return ctx.Process(*args, **kwds) + def Process(self, *args, **kwds): + return self._ctx.Process(*args, **kwds) def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): @@ -176,15 +175,13 @@ def __init__(self, processes=None, initializer=None, initargs=(), self._worker_handler = threading.Thread( target=Pool._handle_workers, - args=(self._cache, self._taskqueue, self._ctx, self.Process, - self._processes, self._pool, self._inqueue, self._outqueue, - self._initializer, self._initargs, self._maxtasksperchild, - self._wrap_exception) + args=(self, ) ) self._worker_handler.daemon = True self._worker_handler._state = RUN self._worker_handler.start() + self._task_handler = threading.Thread( target=Pool._handle_tasks, args=(self._taskqueue, self._quick_put, self._outqueue, @@ -210,62 +207,43 @@ def __init__(self, processes=None, initializer=None, initargs=(), exitpriority=15 ) - @staticmethod - def _join_exited_workers(pool): + def _join_exited_workers(self): """Cleanup after any worker processes which have exited due to reaching their specified lifetime. Returns True if any workers were cleaned up. """ cleaned = False - for i in reversed(range(len(pool))): - worker = pool[i] + for i in reversed(range(len(self._pool))): + worker = self._pool[i] if worker.exitcode is not None: # worker exited util.debug('cleaning up worker %d' % i) worker.join() cleaned = True - del pool[i] + del self._pool[i] return cleaned def _repopulate_pool(self): - return self._repopulate_pool_static(self._ctx, self.Process, - self._processes, - self._pool, self._inqueue, - self._outqueue, self._initializer, - self._initargs, - self._maxtasksperchild, - self._wrap_exception) - - @staticmethod - def _repopulate_pool_static(ctx, Process, processes, pool, inqueue, - outqueue, initializer, initargs, - maxtasksperchild, wrap_exception): """Bring the number of pool processes up to the specified number, for use after reaping workers which have exited. """ - for i in range(processes - len(pool)): - w = Process(ctx, target=worker, - args=(inqueue, outqueue, - initializer, - initargs, maxtasksperchild, - wrap_exception) - ) - pool.append(w) + for i in range(self._processes - len(self._pool)): + w = self.Process(target=worker, + args=(self._inqueue, self._outqueue, + self._initializer, + self._initargs, self._maxtasksperchild, + self._wrap_exception) + ) + self._pool.append(w) w.name = w.name.replace('Process', 'PoolWorker') w.daemon = True w.start() util.debug('added worker') - @staticmethod - def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue, - initializer, initargs, maxtasksperchild, - wrap_exception): + def _maintain_pool(self): """Clean up any exited workers and start replacements for them. """ - if Pool._join_exited_workers(pool): - Pool._repopulate_pool_static(ctx, Process, processes, pool, - inqueue, outqueue, initializer, - initargs, maxtasksperchild, - wrap_exception) + if self._join_exited_workers(): + self._repopulate_pool() def _setup_queues(self): self._inqueue = self._ctx.SimpleQueue() @@ -418,20 +396,16 @@ def _map_async(self, func, iterable, mapper, chunksize=None, callback=None, return result @staticmethod - def _handle_workers(cache, taskqueue, ctx, Process, processes, pool, - inqueue, outqueue, initializer, initargs, - maxtasksperchild, wrap_exception): + def _handle_workers(pool): thread = threading.current_thread() # Keep maintaining workers until the cache gets drained, unless the pool # is terminated. - while thread._state == RUN or (cache and thread._state != TERMINATE): - Pool._maintain_pool(ctx, Process, processes, pool, inqueue, - outqueue, initializer, initargs, - maxtasksperchild, wrap_exception) + while thread._state == RUN or (pool._cache and thread._state != TERMINATE): + pool._maintain_pool() time.sleep(0.1) # send sentinel to stop workers - taskqueue.put(None) + pool._taskqueue.put(None) util.debug('worker handler exiting') @staticmethod @@ -807,7 +781,7 @@ class ThreadPool(Pool): _wrap_exception = False @staticmethod - def Process(ctx, *args, **kwds): + def Process(*args, **kwds): from .dummy import Process return Process(*args, **kwds) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 59f9a2e1e2eb..6cafc2e9cbc0 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2286,13 +2286,6 @@ def test_release_task_refs(self): # they were released too. self.assertEqual(CountedObject.n_instances, 0) - @support.reap_threads - def test_del_pool(self): - p = self.Pool(1) - wr = weakref.ref(p) - del p - gc.collect() - self.assertIsNone(wr()) def raising(): raise KeyError("key") diff --git a/Misc/NEWS.d/next/Library/2018-12-06-00-31-25.bpo-34172.l7CIYt.rst b/Misc/NEWS.d/next/Library/2018-12-06-00-31-25.bpo-34172.l7CIYt.rst new file mode 100644 index 000000000000..e467cc967825 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-06-00-31-25.bpo-34172.l7CIYt.rst @@ -0,0 +1,3 @@ +REVERT: Fix a reference issue inside multiprocessing.Pool that caused the +pool to remain alive if it was deleted without being closed or terminated +explicitly. From webhook-mailer at python.org Wed Dec 5 19:49:44 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 00:49:44 -0000 Subject: [Python-checkins] Revert "[2.7] bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-9686)" (GH-10970) Message-ID: https://github.com/python/cpython/commit/358fc87f53cf97a1768d5b1ded08f2a564f9fd85 commit: 358fc87f53cf97a1768d5b1ded08f2a564f9fd85 branch: 2.7 author: Victor Stinner committer: GitHub date: 2018-12-06T01:49:41+01:00 summary: Revert "[2.7] bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-9686)" (GH-10970) This reverts commit 4a7dd30f5810e8861a3834159a222ab32d5c97d0. files: D Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst M Lib/multiprocessing/pool.py M Lib/test/test_multiprocessing.py diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py index 489c7d67cf34..a47cd0f58a05 100644 --- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -162,9 +162,7 @@ def __init__(self, processes=None, initializer=None, initargs=(), self._worker_handler = threading.Thread( target=Pool._handle_workers, - args=(self._cache, self._processes, self._pool, self.Process, - self._inqueue, self._outqueue, self._initializer, - self._initargs, self._maxtasksperchild, self._taskqueue) + args=(self, ) ) self._worker_handler.daemon = True self._worker_handler._state = RUN @@ -196,56 +194,42 @@ def __init__(self, processes=None, initializer=None, initargs=(), exitpriority=15 ) - @staticmethod - def _join_exited_workers(pool): + def _join_exited_workers(self): """Cleanup after any worker processes which have exited due to reaching their specified lifetime. Returns True if any workers were cleaned up. """ cleaned = False - for i in reversed(range(len(pool))): - worker = pool[i] + for i in reversed(range(len(self._pool))): + worker = self._pool[i] if worker.exitcode is not None: # worker exited debug('cleaning up worker %d' % i) worker.join() cleaned = True - del pool[i] + del self._pool[i] return cleaned def _repopulate_pool(self): - return self._repopulate_pool_static(self._processes, self._pool, - self.Process, self._inqueue, - self._outqueue, self._initializer, - self._initargs, - self._maxtasksperchild) - - @staticmethod - def _repopulate_pool_static(processes, pool, Process, inqueue, outqueue, - initializer, initargs, maxtasksperchild): """Bring the number of pool processes up to the specified number, for use after reaping workers which have exited. """ - for i in range(processes - len(pool)): - w = Process(target=worker, - args=(inqueue, outqueue, - initializer, - initargs, maxtasksperchild) - ) - pool.append(w) + for i in range(self._processes - len(self._pool)): + w = self.Process(target=worker, + args=(self._inqueue, self._outqueue, + self._initializer, + self._initargs, self._maxtasksperchild) + ) + self._pool.append(w) w.name = w.name.replace('Process', 'PoolWorker') w.daemon = True w.start() debug('added worker') - @staticmethod - def _maintain_pool(processes, pool, Process, inqueue, outqueue, - initializer, initargs, maxtasksperchild): + def _maintain_pool(self): """Clean up any exited workers and start replacements for them. """ - if Pool._join_exited_workers(pool): - Pool._repopulate_pool_static(processes, pool, Process, inqueue, - outqueue, initializer, initargs, - maxtasksperchild) + if self._join_exited_workers(): + self._repopulate_pool() def _setup_queues(self): from .queues import SimpleQueue @@ -335,18 +319,16 @@ def map_async(self, func, iterable, chunksize=None, callback=None): return result @staticmethod - def _handle_workers(cache, processes, pool, Process, inqueue, outqueue, - initializer, initargs, maxtasksperchild, taskqueue): + def _handle_workers(pool): thread = threading.current_thread() # Keep maintaining workers until the cache gets drained, unless the pool # is terminated. - while thread._state == RUN or (cache and thread._state != TERMINATE): - Pool._maintain_pool(processes, pool, Process, inqueue, outqueue, - initializer, initargs, maxtasksperchild) + while thread._state == RUN or (pool._cache and thread._state != TERMINATE): + pool._maintain_pool() time.sleep(0.1) # send sentinel to stop workers - taskqueue.put(None) + pool._taskqueue.put(None) debug('worker handler exiting') @staticmethod diff --git a/Lib/test/test_multiprocessing.py b/Lib/test/test_multiprocessing.py index d3192181e5ad..ff299feed894 100644 --- a/Lib/test/test_multiprocessing.py +++ b/Lib/test/test_multiprocessing.py @@ -1359,13 +1359,6 @@ def test_release_task_refs(self): # they were released too. self.assertEqual(CountedObject.n_instances, 0) - def test_del_pool(self): - p = self.Pool(1) - wr = weakref.ref(p) - del p - gc.collect() - self.assertIsNone(wr()) - def unpickleable_result(): return lambda: 42 diff --git a/Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst b/Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst deleted file mode 100644 index d1c5a7721019..000000000000 --- a/Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a reference issue inside multiprocessing.Pool that caused the pool to remain alive if it was deleted without being closed or terminated explicitly. From webhook-mailer at python.org Thu Dec 6 00:46:26 2018 From: webhook-mailer at python.org (Terry Jan Reedy) Date: Thu, 06 Dec 2018 05:46:26 -0000 Subject: [Python-checkins] bpo-34162: Update idlelib NEWS to 2018-12-05. (GH-10964) Message-ID: https://github.com/python/cpython/commit/6ea9d54dea9f2f8be7fe6d284064c579331388a9 commit: 6ea9d54dea9f2f8be7fe6d284064c579331388a9 branch: master author: Terry Jan Reedy committer: GitHub date: 2018-12-06T00:46:22-05:00 summary: bpo-34162: Update idlelib NEWS to 2018-12-05. (GH-10964) files: M Lib/idlelib/NEWS.txt diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index f59095cda261..9a16ece3bbe3 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,38 @@ Released on 2019-10-20? ====================================== +bpo-35213: Where appropriate, use 'macOS' in idlelib. + +bpo-34864: Document two IDLE on MacOS issues. The System Preferences +Dock "prefer tabs always" setting disables some IDLE features. +Menus are a bit different than as described for Windows and Linux. + +bpo-35202: Remove unused imports in idlelib. + +bpo-33000: Document that IDLE's shell has no line limit. +A program that runs indefinitely can overfill memory. + +bpo-23220: Explain how IDLE's Shell displays output. +Add new subsection "User output in Shell". + +bpo-35099: Improve the doc about IDLE running user code. +"IDLE -- console differences" is renamed "Running user code". +It mostly covers the implications of using custom sys.stdxxx objects. + +bpo-35097: Add IDLE doc subsection explaining editor windows. +Topics include opening, title and status bars, .py* extension, and running. + +Issue 35093: Document the IDLE document viewer in the IDLE doc. +Add a paragraph in "Help and preferences", "Help sources" subsection. + +bpo-1529353: Explain Shell text squeezing in the IDLE doc. + +bpo-35088: Update idlelib.help.copy_string docstring. +We now use git and backporting instead of hg and forward merging. + +bpo-35087: Update idlelib help files for the current doc build. +The main change is the elimination of chapter-section numbers. + bpo-1529353: Output over N lines (50 by default) is squeezed down to a button. N can be changed in the PyShell section of the General page of the Settings dialog. Fewer, but possibly extra long, lines can be squeezed by From webhook-mailer at python.org Thu Dec 6 02:04:39 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Thu, 06 Dec 2018 07:04:39 -0000 Subject: [Python-checkins] bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) Message-ID: https://github.com/python/cpython/commit/8752dfbd1f0c96ca09cdacabaf0d0f8c3895b6ce commit: 8752dfbd1f0c96ca09cdacabaf0d0f8c3895b6ce branch: master author: native-api committer: Serhiy Storchaka date: 2018-12-06T09:04:35+02:00 summary: bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) files: M Lib/test/support/__init__.py diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index f90212cd7ecf..05e8593f9825 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -871,7 +871,11 @@ def dec(*args, **kwargs): '\u20AC', ): try: - os.fsdecode(os.fsencode(character)) + # If Python is set up to use the legacy 'mbcs' in Windows, + # 'replace' error mode is used, and encode() returns b'?' + # for characters missing in the ANSI codepage + if os.fsdecode(os.fsencode(character)) != character: + raise UnicodeError except UnicodeError: pass else: From webhook-mailer at python.org Thu Dec 6 02:22:21 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 07:22:21 -0000 Subject: [Python-checkins] bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) Message-ID: https://github.com/python/cpython/commit/b1438c0d376e1d438a11927e2698e3317da0d854 commit: b1438c0d376e1d438a11927e2698e3317da0d854 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T23:22:17-08:00 summary: bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) (cherry picked from commit 8752dfbd1f0c96ca09cdacabaf0d0f8c3895b6ce) Co-authored-by: native-api files: M Lib/test/support/__init__.py diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 2768e1147946..512e354fabc8 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -852,7 +852,11 @@ def dec(*args, **kwargs): '\u20AC', ): try: - os.fsdecode(os.fsencode(character)) + # If Python is set up to use the legacy 'mbcs' in Windows, + # 'replace' error mode is used, and encode() returns b'?' + # for characters missing in the ANSI codepage + if os.fsdecode(os.fsencode(character)) != character: + raise UnicodeError except UnicodeError: pass else: From webhook-mailer at python.org Thu Dec 6 02:26:54 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 07:26:54 -0000 Subject: [Python-checkins] bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) Message-ID: https://github.com/python/cpython/commit/af31228650d30f02a283d291ba106e84275a04c1 commit: af31228650d30f02a283d291ba106e84275a04c1 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-05T23:26:51-08:00 summary: bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) (cherry picked from commit 8752dfbd1f0c96ca09cdacabaf0d0f8c3895b6ce) Co-authored-by: native-api files: M Lib/test/support/__init__.py diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index c0627dc14ef0..66c0fed8411c 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -857,7 +857,11 @@ def dec(*args, **kwargs): '\u20AC', ): try: - os.fsdecode(os.fsencode(character)) + # If Python is set up to use the legacy 'mbcs' in Windows, + # 'replace' error mode is used, and encode() returns b'?' + # for characters missing in the ANSI codepage + if os.fsdecode(os.fsencode(character)) != character: + raise UnicodeError except UnicodeError: pass else: From webhook-mailer at python.org Thu Dec 6 02:51:54 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 07:51:54 -0000 Subject: [Python-checkins] Revert "bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450)" (GH-10971) Message-ID: https://github.com/python/cpython/commit/9dfc754d61c55a526304e10a328bad448efa9ee9 commit: 9dfc754d61c55a526304e10a328bad448efa9ee9 branch: master author: Victor Stinner committer: GitHub date: 2018-12-06T08:51:47+01:00 summary: Revert "bpo-34172: multiprocessing.Pool leaks resources after being deleted (GH-8450)" (GH-10971) This reverts commit 97bfe8d3ebb0a54c8798f57555cb4152f9b2e1d0. files: D Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst M Lib/multiprocessing/pool.py M Lib/test/_test_multiprocessing.py diff --git a/Lib/multiprocessing/pool.py b/Lib/multiprocessing/pool.py index 7a6d01490146..2b3cc59a9ff8 100644 --- a/Lib/multiprocessing/pool.py +++ b/Lib/multiprocessing/pool.py @@ -149,9 +149,8 @@ class Pool(object): ''' _wrap_exception = True - @staticmethod - def Process(ctx, *args, **kwds): - return ctx.Process(*args, **kwds) + def Process(self, *args, **kwds): + return self._ctx.Process(*args, **kwds) def __init__(self, processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None): @@ -186,15 +185,13 @@ def __init__(self, processes=None, initializer=None, initargs=(), self._worker_handler = threading.Thread( target=Pool._handle_workers, - args=(self._cache, self._taskqueue, self._ctx, self.Process, - self._processes, self._pool, self._inqueue, self._outqueue, - self._initializer, self._initargs, self._maxtasksperchild, - self._wrap_exception) + args=(self, ) ) self._worker_handler.daemon = True self._worker_handler._state = RUN self._worker_handler.start() + self._task_handler = threading.Thread( target=Pool._handle_tasks, args=(self._taskqueue, self._quick_put, self._outqueue, @@ -220,62 +217,43 @@ def __init__(self, processes=None, initializer=None, initargs=(), exitpriority=15 ) - @staticmethod - def _join_exited_workers(pool): + def _join_exited_workers(self): """Cleanup after any worker processes which have exited due to reaching their specified lifetime. Returns True if any workers were cleaned up. """ cleaned = False - for i in reversed(range(len(pool))): - worker = pool[i] + for i in reversed(range(len(self._pool))): + worker = self._pool[i] if worker.exitcode is not None: # worker exited util.debug('cleaning up worker %d' % i) worker.join() cleaned = True - del pool[i] + del self._pool[i] return cleaned def _repopulate_pool(self): - return self._repopulate_pool_static(self._ctx, self.Process, - self._processes, - self._pool, self._inqueue, - self._outqueue, self._initializer, - self._initargs, - self._maxtasksperchild, - self._wrap_exception) - - @staticmethod - def _repopulate_pool_static(ctx, Process, processes, pool, inqueue, - outqueue, initializer, initargs, - maxtasksperchild, wrap_exception): """Bring the number of pool processes up to the specified number, for use after reaping workers which have exited. """ - for i in range(processes - len(pool)): - w = Process(ctx, target=worker, - args=(inqueue, outqueue, - initializer, - initargs, maxtasksperchild, - wrap_exception) - ) + for i in range(self._processes - len(self._pool)): + w = self.Process(target=worker, + args=(self._inqueue, self._outqueue, + self._initializer, + self._initargs, self._maxtasksperchild, + self._wrap_exception) + ) w.name = w.name.replace('Process', 'PoolWorker') w.daemon = True w.start() - pool.append(w) + self._pool.append(w) util.debug('added worker') - @staticmethod - def _maintain_pool(ctx, Process, processes, pool, inqueue, outqueue, - initializer, initargs, maxtasksperchild, - wrap_exception): + def _maintain_pool(self): """Clean up any exited workers and start replacements for them. """ - if Pool._join_exited_workers(pool): - Pool._repopulate_pool_static(ctx, Process, processes, pool, - inqueue, outqueue, initializer, - initargs, maxtasksperchild, - wrap_exception) + if self._join_exited_workers(): + self._repopulate_pool() def _setup_queues(self): self._inqueue = self._ctx.SimpleQueue() @@ -433,20 +411,16 @@ def _map_async(self, func, iterable, mapper, chunksize=None, callback=None, return result @staticmethod - def _handle_workers(cache, taskqueue, ctx, Process, processes, pool, - inqueue, outqueue, initializer, initargs, - maxtasksperchild, wrap_exception): + def _handle_workers(pool): thread = threading.current_thread() # Keep maintaining workers until the cache gets drained, unless the pool # is terminated. - while thread._state == RUN or (cache and thread._state != TERMINATE): - Pool._maintain_pool(ctx, Process, processes, pool, inqueue, - outqueue, initializer, initargs, - maxtasksperchild, wrap_exception) + while thread._state == RUN or (pool._cache and thread._state != TERMINATE): + pool._maintain_pool() time.sleep(0.1) # send sentinel to stop workers - taskqueue.put(None) + pool._taskqueue.put(None) util.debug('worker handler exiting') @staticmethod @@ -828,7 +802,7 @@ class ThreadPool(Pool): _wrap_exception = False @staticmethod - def Process(ctx, *args, **kwds): + def Process(*args, **kwds): from .dummy import Process return Process(*args, **kwds) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index b62c119e9ae0..163419c30eba 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2558,13 +2558,6 @@ def test_release_task_refs(self): # they were released too. self.assertEqual(CountedObject.n_instances, 0) - @support.reap_threads - def test_del_pool(self): - p = self.Pool(1) - wr = weakref.ref(p) - del p - gc.collect() - self.assertIsNone(wr()) def raising(): raise KeyError("key") diff --git a/Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst b/Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst deleted file mode 100644 index d1c5a7721019..000000000000 --- a/Misc/NEWS.d/next/Library/2018-07-26-10-31-52.bpo-34172.8ovLNi.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a reference issue inside multiprocessing.Pool that caused the pool to remain alive if it was deleted without being closed or terminated explicitly. From webhook-mailer at python.org Thu Dec 6 03:25:43 2018 From: webhook-mailer at python.org (Terry Jan Reedy) Date: Thu, 06 Dec 2018 08:25:43 -0000 Subject: [Python-checkins] [3.7] bpo-34162: Update idlelib NEWS to 2018-12-05 (GH-10964) (GH-10980) Message-ID: https://github.com/python/cpython/commit/de8037db8c203ca0a1bf549f690230d5e7b8429e commit: de8037db8c203ca0a1bf549f690230d5e7b8429e branch: 3.7 author: Terry Jan Reedy committer: GitHub date: 2018-12-06T03:25:40-05:00 summary: [3.7] bpo-34162: Update idlelib NEWS to 2018-12-05 (GH-10964) (GH-10980) Cherry-picked from 6ea9d54. files: M Lib/idlelib/NEWS.txt diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 133807c447b9..e34b439319dd 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -1,8 +1,45 @@ -What's New in IDLE 3.7.1 +What's New in IDLE 3.7.2 Released on 2018-07-31? ====================================== +bpo-35213: Where appropriate, use 'macOS' in idlelib. + +bpo-34864: Document two IDLE on MacOS issues. The System Preferences +Dock "prefer tabs always" setting disables some IDLE features. +Menus are a bit different than as described for Windows and Linux. + +bpo-35202: Remove unused imports in idlelib. + +bpo-33000: Document that IDLE's shell has no line limit. +A program that runs indefinitely can overfill memory. + +bpo-23220: Explain how IDLE's Shell displays output. +Add new subsection "User output in Shell". + +bpo-35099: Improve the doc about IDLE running user code. +"IDLE -- console differences" is renamed "Running user code". +It mostly covers the implications of using custom sys.stdxxx objects. + +bpo-35097: Add IDLE doc subsection explaining editor windows. +Topics include opening, title and status bars, .py* extension, and running. + +Issue 35093: Document the IDLE document viewer in the IDLE doc. +Add a paragraph in "Help and preferences", "Help sources" subsection. + +bpo-1529353: Explain Shell text squeezing in the IDLE doc. + +bpo-35088: Update idlelib.help.copy_string docstring. +We now use git and backporting instead of hg and forward merging. + +bpo-35087: Update idlelib help files for the current doc build. +The main change is the elimination of chapter-section numbers. + + +What's New in IDLE 3.7.1 +Released on 2018-07-31? +====================================== + bpo-1529353: Output over N lines (50 by default) is squeezed down to a button. N can be changed in the PyShell section of the General page of the Settings dialog. Fewer, but possibly extra long, lines can be squeezed by From webhook-mailer at python.org Thu Dec 6 03:26:00 2018 From: webhook-mailer at python.org (Terry Jan Reedy) Date: Thu, 06 Dec 2018 08:26:00 -0000 Subject: [Python-checkins] [3.6] bpo-34162: Update idlelib NEWS to 2018-12-05 (GH-10964) (GH-10979) Message-ID: https://github.com/python/cpython/commit/af1f977575331623547d53247d99be8953a13b9f commit: af1f977575331623547d53247d99be8953a13b9f branch: 3.6 author: Terry Jan Reedy committer: GitHub date: 2018-12-06T03:25:57-05:00 summary: [3.6] bpo-34162: Update idlelib NEWS to 2018-12-05 (GH-10964) (GH-10979) Cherry-picked from 6ea9d54. files: M Lib/idlelib/NEWS.txt diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 8ecd85a9ceb6..0868b1f1a3c3 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -1,8 +1,46 @@ -What's New in IDLE 3.6.7 -Released on 2018-09-24? +What's New in IDLE 3.6.8 +Released on 2018-12-20? +**This is the final 3.6 bugfix maintenance release** ====================================== +bpo-35213: Where appropriate, use 'macOS' in idlelib. + +bpo-34864: Document two IDLE on MacOS issues. The System Preferences +Dock "prefer tabs always" setting disables some IDLE features. +Menus are a bit different than as described for Windows and Linux. + +bpo-35202: Remove unused imports in idlelib. + +bpo-33000: Document that IDLE's shell has no line limit. +A program that runs indefinitely can overfill memory. + +bpo-23220: Explain how IDLE's Shell displays output. +Add new subsection "User output in Shell". + +bpo-35099: Improve the doc about IDLE running user code. +"IDLE -- console differences" is renamed "Running user code". +It mostly covers the implications of using custom sys.stdxxx objects. + +bpo-35097: Add IDLE doc subsection explaining editor windows. +Topics include opening, title and status bars, .py* extension, and running. + +Issue 35093: Document the IDLE document viewer in the IDLE doc. +Add a paragraph in "Help and preferences", "Help sources" subsection. + +bpo-1529353: Explain Shell text squeezing in the IDLE doc. + +bpo-35088: Update idlelib.help.copy_string docstring. +We now use git and backporting instead of hg and forward merging. + +bpo-35087: Update idlelib help files for the current doc build. +The main change is the elimination of chapter-section numbers. + + +What's New in IDLE 3.6.7 +Released on 2018-10-20 +====================================== + bpo-1529353: Output over N lines (50 by default) is squeezed down to a button. N can be changed in the PyShell section of the General page of the Settings dialog. Fewer, but possibly extra long, lines can be squeezed by From solipsis at pitrou.net Thu Dec 6 04:06:12 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Thu, 06 Dec 2018 09:06:12 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=4 Message-ID: <20181206090612.1.5DBE175CAA9E65D3@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_collections leaked [0, 7, -7] memory blocks, sum=0 test_functools leaked [0, 3, 1] memory blocks, sum=4 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogO_YxoN', '--timeout', '7200'] From webhook-mailer at python.org Thu Dec 6 04:16:30 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Thu, 06 Dec 2018 09:16:30 -0000 Subject: [Python-checkins] bpo-35384: The repr of ctypes.CArgObject no longer fails for non-ascii character. (GH-10863) Message-ID: https://github.com/python/cpython/commit/3ffa8b9ba190101f674a0e524e482a83ed09cccd commit: 3ffa8b9ba190101f674a0e524e482a83ed09cccd branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-06T11:16:24+02:00 summary: bpo-35384: The repr of ctypes.CArgObject no longer fails for non-ascii character. (GH-10863) files: M Lib/ctypes/test/test_bytes.py M Modules/_ctypes/callproc.c diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py index 20fa05650340..092ec5af0524 100644 --- a/Lib/ctypes/test/test_bytes.py +++ b/Lib/ctypes/test/test_bytes.py @@ -12,6 +12,7 @@ def test_c_char(self): x.value = "y" c_char.from_param(b"x") self.assertRaises(TypeError, c_char.from_param, "x") + self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) (c_char * 3)(b"a", b"b", b"c") self.assertRaises(TypeError, c_char * 3, "a", "b", "c") diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index 1185c9156ae9..a7965c19b70c 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -455,6 +455,12 @@ PyCArg_dealloc(PyCArgObject *self) PyObject_Del(self); } +static int +is_literal_char(unsigned char c) +{ + return c < 128 && _PyUnicode_IsPrintable(c) && c != '\\' && c != '\''; +} + static PyObject * PyCArg_repr(PyCArgObject *self) { @@ -501,8 +507,14 @@ PyCArg_repr(PyCArgObject *self) break; case 'c': - sprintf(buffer, "", - self->tag, self->value.c); + if (is_literal_char((unsigned char)self->value.c)) { + sprintf(buffer, "", + self->tag, self->value.c); + } + else { + sprintf(buffer, "", + self->tag, (unsigned char)self->value.c); + } break; /* Hm, are these 'z' and 'Z' codes useful at all? @@ -517,8 +529,14 @@ PyCArg_repr(PyCArgObject *self) break; default: - sprintf(buffer, "", - self->tag, self); + if (is_literal_char((unsigned char)self->tag)) { + sprintf(buffer, "", + (unsigned char)self->tag, self); + } + else { + sprintf(buffer, "", + (unsigned char)self->tag, self); + } break; } return PyUnicode_FromString(buffer); From webhook-mailer at python.org Thu Dec 6 04:19:28 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Thu, 06 Dec 2018 09:19:28 -0000 Subject: [Python-checkins] [2.7] Correct a couple of unbalanced parenthesis. (GH-10779). (GH-10963) Message-ID: https://github.com/python/cpython/commit/46aa472a8f2dd9e47ba6fbe3cc416ec7c62f11f4 commit: 46aa472a8f2dd9e47ba6fbe3cc416ec7c62f11f4 branch: 2.7 author: Andre Delfino committer: Serhiy Storchaka date: 2018-12-06T11:19:23+02:00 summary: [2.7] Correct a couple of unbalanced parenthesis. (GH-10779). (GH-10963) (cherry picked from commit 55f41e45b4318cbe19209f5144641344d0049fb8) files: M Doc/c-api/buffer.rst M Doc/faq/extending.rst M Doc/library/sysconfig.rst diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index 4e5a04397a35..77940acaf600 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -98,7 +98,7 @@ The new-style Py_buffer struct suboffset value that it negative indicates that no de-referencing should occur (striding in a contiguous memory block). - If all suboffsets are negative (i.e. no de-referencing is needed, then + If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value). Here is a function that returns a pointer to the element in an N-D array diff --git a/Doc/faq/extending.rst b/Doc/faq/extending.rst index 4be58d69714c..0a256b56eed2 100644 --- a/Doc/faq/extending.rst +++ b/Doc/faq/extending.rst @@ -286,7 +286,7 @@ However sometimes you have to run the embedded Python interpreter in the same thread as your rest application and you can't allow the :c:func:`PyRun_InteractiveLoop` to stop while waiting for user input. The one solution then is to call :c:func:`PyParser_ParseString` and test for ``e.error`` -equal to ``E_EOF``, which means the input is incomplete). Here's a sample code +equal to ``E_EOF``, which means the input is incomplete. Here's a sample code fragment, untested, inspired by code from Alex Farber:: #include diff --git a/Doc/library/sysconfig.rst b/Doc/library/sysconfig.rst index 3b58266d228a..7655c60c4041 100644 --- a/Doc/library/sysconfig.rst +++ b/Doc/library/sysconfig.rst @@ -188,7 +188,7 @@ Other functions Windows will return one of: - - win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + - win-amd64 (64bit Windows on AMD64, aka x86_64, Intel64, and EM64T) - win-ia64 (64bit Windows on Itanium) - win32 (all others - specifically, sys.platform is returned) From webhook-mailer at python.org Thu Dec 6 04:41:19 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 09:41:19 -0000 Subject: [Python-checkins] bpo-35384: The repr of ctypes.CArgObject no longer fails for non-ascii character. (GH-10863) Message-ID: https://github.com/python/cpython/commit/f9d8b686285926c985cfe88a8392a9a497c0a916 commit: f9d8b686285926c985cfe88a8392a9a497c0a916 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T01:41:16-08:00 summary: bpo-35384: The repr of ctypes.CArgObject no longer fails for non-ascii character. (GH-10863) (cherry picked from commit 3ffa8b9ba190101f674a0e524e482a83ed09cccd) Co-authored-by: Serhiy Storchaka files: M Lib/ctypes/test/test_bytes.py M Modules/_ctypes/callproc.c diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py index 20fa05650340..092ec5af0524 100644 --- a/Lib/ctypes/test/test_bytes.py +++ b/Lib/ctypes/test/test_bytes.py @@ -12,6 +12,7 @@ def test_c_char(self): x.value = "y" c_char.from_param(b"x") self.assertRaises(TypeError, c_char.from_param, "x") + self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) (c_char * 3)(b"a", b"b", b"c") self.assertRaises(TypeError, c_char * 3, "a", "b", "c") diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index 04fbc010ca7d..d1c190f35910 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -452,6 +452,12 @@ PyCArg_dealloc(PyCArgObject *self) PyObject_Del(self); } +static int +is_literal_char(unsigned char c) +{ + return c < 128 && _PyUnicode_IsPrintable(c) && c != '\\' && c != '\''; +} + static PyObject * PyCArg_repr(PyCArgObject *self) { @@ -498,8 +504,14 @@ PyCArg_repr(PyCArgObject *self) break; case 'c': - sprintf(buffer, "", - self->tag, self->value.c); + if (is_literal_char((unsigned char)self->value.c)) { + sprintf(buffer, "", + self->tag, self->value.c); + } + else { + sprintf(buffer, "", + self->tag, (unsigned char)self->value.c); + } break; /* Hm, are these 'z' and 'Z' codes useful at all? @@ -514,8 +526,14 @@ PyCArg_repr(PyCArgObject *self) break; default: - sprintf(buffer, "", - self->tag, self); + if (is_literal_char((unsigned char)self->tag)) { + sprintf(buffer, "", + (unsigned char)self->tag, self); + } + else { + sprintf(buffer, "", + (unsigned char)self->tag, self); + } break; } return PyUnicode_FromString(buffer); From webhook-mailer at python.org Thu Dec 6 04:43:40 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Thu, 06 Dec 2018 09:43:40 -0000 Subject: [Python-checkins] [2.7] bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) (GH-7279) Message-ID: https://github.com/python/cpython/commit/29a4cbff92862207eb9df9a970b3636b8b06ff5d commit: 29a4cbff92862207eb9df9a970b3636b8b06ff5d branch: 2.7 author: native-api committer: Serhiy Storchaka date: 2018-12-06T11:43:37+02:00 summary: [2.7] bpo-33709: test_ntpath and test_posixpath fail in Windows with ACP!=1252. (GH-7278) (GH-7279) files: M Lib/test/support/__init__.py M Lib/test/test_posixpath.py diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 23b7065174ee..aaf028632a59 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -657,8 +657,12 @@ def u(s): unichr(0x20AC), ): try: - character.encode(sys.getfilesystemencoding())\ - .decode(sys.getfilesystemencoding()) + # In Windows, 'mbcs' is used, and encode() returns '?' + # for characters missing in the ANSI codepage + if character.encode(sys.getfilesystemencoding())\ + .decode(sys.getfilesystemencoding())\ + != character: + raise UnicodeError except UnicodeError: pass else: diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 0663a21ff043..18ea2e42eade 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -496,12 +496,10 @@ def test_relpath(self): finally: os.getcwd = real_getcwd - @test_support.requires_unicode + @unittest.skipUnless(test_support.FS_NONASCII, 'need test_support.FS_NONASCII') def test_expandvars_nonascii_word(self): encoding = sys.getfilesystemencoding() - # Non-ASCII word characters - letters = test_support.u(r'\xe6\u0130\u0141\u03c6\u041a\u05d0\u062a\u0e01') - uwnonascii = letters.encode(encoding, 'ignore').decode(encoding)[:3] + uwnonascii = test_support.FS_NONASCII swnonascii = uwnonascii.encode(encoding) if not swnonascii: self.skipTest('Needs non-ASCII word characters') From webhook-mailer at python.org Thu Dec 6 04:58:31 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 09:58:31 -0000 Subject: [Python-checkins] bpo-35384: The repr of ctypes.CArgObject no longer fails for non-ascii character. (GH-10863) Message-ID: https://github.com/python/cpython/commit/f740818f3d92497c564d515a661039dc8434fc6c commit: f740818f3d92497c564d515a661039dc8434fc6c branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T01:58:24-08:00 summary: bpo-35384: The repr of ctypes.CArgObject no longer fails for non-ascii character. (GH-10863) (cherry picked from commit 3ffa8b9ba190101f674a0e524e482a83ed09cccd) Co-authored-by: Serhiy Storchaka files: M Lib/ctypes/test/test_bytes.py M Modules/_ctypes/callproc.c diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py index 20fa05650340..092ec5af0524 100644 --- a/Lib/ctypes/test/test_bytes.py +++ b/Lib/ctypes/test/test_bytes.py @@ -12,6 +12,7 @@ def test_c_char(self): x.value = "y" c_char.from_param(b"x") self.assertRaises(TypeError, c_char.from_param, "x") + self.assertIn('xbd', repr(c_char.from_param(b"\xbd"))) (c_char * 3)(b"a", b"b", b"c") self.assertRaises(TypeError, c_char * 3, "a", "b", "c") diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index ad40ca1c5247..ec596b4de31d 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -452,6 +452,12 @@ PyCArg_dealloc(PyCArgObject *self) PyObject_Del(self); } +static int +is_literal_char(unsigned char c) +{ + return c < 128 && _PyUnicode_IsPrintable(c) && c != '\\' && c != '\''; +} + static PyObject * PyCArg_repr(PyCArgObject *self) { @@ -498,8 +504,14 @@ PyCArg_repr(PyCArgObject *self) break; case 'c': - sprintf(buffer, "", - self->tag, self->value.c); + if (is_literal_char((unsigned char)self->value.c)) { + sprintf(buffer, "", + self->tag, self->value.c); + } + else { + sprintf(buffer, "", + self->tag, (unsigned char)self->value.c); + } break; /* Hm, are these 'z' and 'Z' codes useful at all? @@ -514,8 +526,14 @@ PyCArg_repr(PyCArgObject *self) break; default: - sprintf(buffer, "", - self->tag, self); + if (is_literal_char((unsigned char)self->tag)) { + sprintf(buffer, "", + (unsigned char)self->tag, self); + } + else { + sprintf(buffer, "", + (unsigned char)self->tag, self); + } break; } return PyUnicode_FromString(buffer); From webhook-mailer at python.org Thu Dec 6 05:56:06 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 10:56:06 -0000 Subject: [Python-checkins] bpo-35359: Add _CRT_SECURE_NO_WARNINGS to pythoncore project (GH-10819) Message-ID: https://github.com/python/cpython/commit/49cedc51a68b4cd2525c14ab02bd1a483d8be389 commit: 49cedc51a68b4cd2525c14ab02bd1a483d8be389 branch: 2.7 author: Victor Stinner committer: GitHub date: 2018-12-06T11:56:00+01:00 summary: bpo-35359: Add _CRT_SECURE_NO_WARNINGS to pythoncore project (GH-10819) Define _CRT_SECURE_NO_WARNINGS in the Visual Studio pythoncore project to make quiet security warnings when building zlib C files (Modules\zlib\ subdirectory). files: M PCbuild/pythoncore.vcxproj diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 99291ea5a95d..7cae0a54802b 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -70,7 +70,7 @@ /Zm200 %(AdditionalOptions) $(PySourcePath)Python;$(PySourcePath)Modules\zlib;%(AdditionalIncludeDirectories) - _USRDLL;Py_BUILD_CORE;Py_ENABLE_SHARED;MS_DLL_ID="$(SysWinVer)";%(PreprocessorDefinitions) + _USRDLL;Py_BUILD_CORE;Py_ENABLE_SHARED;_CRT_SECURE_NO_WARNINGS;MS_DLL_ID="$(SysWinVer)";%(PreprocessorDefinitions) ws2_32.lib;%(AdditionalDependencies) From webhook-mailer at python.org Thu Dec 6 05:56:55 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 10:56:55 -0000 Subject: [Python-checkins] bpo-35424: test_multiprocessing: join 3 pools (GH-10986) Message-ID: https://github.com/python/cpython/commit/388c8c208d9d09bd28289c1e4776b947d4d0f0f0 commit: 388c8c208d9d09bd28289c1e4776b947d4d0f0f0 branch: master author: Victor Stinner committer: GitHub date: 2018-12-06T11:56:52+01:00 summary: bpo-35424: test_multiprocessing: join 3 pools (GH-10986) Join 3 pools in these tests: * test.test_multiprocessing_spawn.WithProcessesTestPool.test_context * test.test_multiprocessing_spawn.WithProcessesTestPool.test_traceback files: M Lib/test/_test_multiprocessing.py diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 163419c30eba..0b0fe7c9b298 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2471,6 +2471,7 @@ def test_context(self): with self.Pool(2) as p: r = p.map_async(sqr, L) self.assertEqual(r.get(), expected) + p.join() self.assertRaises(ValueError, p.map_async, sqr, L) @classmethod @@ -2488,6 +2489,7 @@ def test_traceback(self): exc = e else: self.fail('expected RuntimeError') + p.join() self.assertIs(type(exc), RuntimeError) self.assertEqual(exc.args, (123,)) cause = exc.__cause__ @@ -2512,6 +2514,7 @@ def test_traceback(self): self.fail('expected SayWhenError') self.assertIs(type(exc), SayWhenError) self.assertIs(exc.__cause__, None) + p.join() @classmethod def _test_wrapped_exception(cls): From webhook-mailer at python.org Thu Dec 6 06:20:55 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 11:20:55 -0000 Subject: [Python-checkins] bpo-35424: test_multiprocessing: join 3 pools (GH-10986) Message-ID: https://github.com/python/cpython/commit/b7c67c4d510a7a72a35983cc168dbb2ce796cb8c commit: b7c67c4d510a7a72a35983cc168dbb2ce796cb8c branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T03:20:50-08:00 summary: bpo-35424: test_multiprocessing: join 3 pools (GH-10986) Join 3 pools in these tests: * test.test_multiprocessing_spawn.WithProcessesTestPool.test_context * test.test_multiprocessing_spawn.WithProcessesTestPool.test_traceback (cherry picked from commit 388c8c208d9d09bd28289c1e4776b947d4d0f0f0) Co-authored-by: Victor Stinner files: M Lib/test/_test_multiprocessing.py diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index a0daa43a55a1..abcb5d45e7ab 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2465,6 +2465,7 @@ def test_context(self): with self.Pool(2) as p: r = p.map_async(sqr, L) self.assertEqual(r.get(), expected) + p.join() self.assertRaises(ValueError, p.map_async, sqr, L) @classmethod @@ -2482,6 +2483,7 @@ def test_traceback(self): exc = e else: self.fail('expected RuntimeError') + p.join() self.assertIs(type(exc), RuntimeError) self.assertEqual(exc.args, (123,)) cause = exc.__cause__ @@ -2506,6 +2508,7 @@ def test_traceback(self): self.fail('expected SayWhenError') self.assertIs(type(exc), SayWhenError) self.assertIs(exc.__cause__, None) + p.join() @classmethod def _test_wrapped_exception(cls): From webhook-mailer at python.org Thu Dec 6 06:23:23 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 11:23:23 -0000 Subject: [Python-checkins] bpo-35424: test_multiprocessing: join 3 pools (GH-10986) Message-ID: https://github.com/python/cpython/commit/e44b5b2afa6fe2966d8caff45e36c0980413bb86 commit: e44b5b2afa6fe2966d8caff45e36c0980413bb86 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T03:23:20-08:00 summary: bpo-35424: test_multiprocessing: join 3 pools (GH-10986) Join 3 pools in these tests: * test.test_multiprocessing_spawn.WithProcessesTestPool.test_context * test.test_multiprocessing_spawn.WithProcessesTestPool.test_traceback (cherry picked from commit 388c8c208d9d09bd28289c1e4776b947d4d0f0f0) Co-authored-by: Victor Stinner files: M Lib/test/_test_multiprocessing.py diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 6cafc2e9cbc0..d5c1ec128482 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -2199,6 +2199,7 @@ def test_context(self): with self.Pool(2) as p: r = p.map_async(sqr, L) self.assertEqual(r.get(), expected) + p.join() self.assertRaises(ValueError, p.map_async, sqr, L) @classmethod @@ -2216,6 +2217,7 @@ def test_traceback(self): exc = e else: self.fail('expected RuntimeError') + p.join() self.assertIs(type(exc), RuntimeError) self.assertEqual(exc.args, (123,)) cause = exc.__cause__ @@ -2240,6 +2242,7 @@ def test_traceback(self): self.fail('expected SayWhenError') self.assertIs(type(exc), SayWhenError) self.assertIs(exc.__cause__, None) + p.join() @classmethod def _test_wrapped_exception(cls): From webhook-mailer at python.org Thu Dec 6 08:16:26 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Thu, 06 Dec 2018 13:16:26 -0000 Subject: [Python-checkins] bpo-35363: test_eintr uses print(flush=True) (GH-10990) Message-ID: https://github.com/python/cpython/commit/0644b33821b70efbf0ac1ec1fb8729b05796564a commit: 0644b33821b70efbf0ac1ec1fb8729b05796564a branch: master author: Victor Stinner committer: GitHub date: 2018-12-06T14:16:21+01:00 summary: bpo-35363: test_eintr uses print(flush=True) (GH-10990) files: M Lib/test/test_eintr.py diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index c2e8deadbab7..f61efa3c648e 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -20,12 +20,13 @@ def test_all(self): args = ["-u", tester, "-v"] if support.verbose: print() - print("--- run eintr_tester.py ---") + print("--- run eintr_tester.py ---", flush=True) # In verbose mode, the child process inherit stdout and stdout, # to see output in realtime and reduce the risk of loosing output. args = [sys.executable, "-E", "-X", "faulthandler", *args] proc = subprocess.run(args) - print(f"--- eintr_tester.py completed: exit code {proc.returncode} ---") + print(f"--- eintr_tester.py completed: " + f"exit code {proc.returncode} ---", flush=True) if proc.returncode: self.fail("eintr_tester.py failed") else: From webhook-mailer at python.org Thu Dec 6 08:35:04 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 13:35:04 -0000 Subject: [Python-checkins] bpo-35363: test_eintr uses print(flush=True) (GH-10990) Message-ID: https://github.com/python/cpython/commit/560fa4db17983ce37c1453c057901c627b2c3abc commit: 560fa4db17983ce37c1453c057901c627b2c3abc branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T05:34:59-08:00 summary: bpo-35363: test_eintr uses print(flush=True) (GH-10990) (cherry picked from commit 0644b33821b70efbf0ac1ec1fb8729b05796564a) Co-authored-by: Victor Stinner files: M Lib/test/test_eintr.py diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index c2e8deadbab7..f61efa3c648e 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -20,12 +20,13 @@ def test_all(self): args = ["-u", tester, "-v"] if support.verbose: print() - print("--- run eintr_tester.py ---") + print("--- run eintr_tester.py ---", flush=True) # In verbose mode, the child process inherit stdout and stdout, # to see output in realtime and reduce the risk of loosing output. args = [sys.executable, "-E", "-X", "faulthandler", *args] proc = subprocess.run(args) - print(f"--- eintr_tester.py completed: exit code {proc.returncode} ---") + print(f"--- eintr_tester.py completed: " + f"exit code {proc.returncode} ---", flush=True) if proc.returncode: self.fail("eintr_tester.py failed") else: From webhook-mailer at python.org Thu Dec 6 08:40:35 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 13:40:35 -0000 Subject: [Python-checkins] bpo-35363: test_eintr uses print(flush=True) (GH-10990) Message-ID: https://github.com/python/cpython/commit/3f0e8e225e2275d22c4bd2e8f8f212b6a8b849aa commit: 3f0e8e225e2275d22c4bd2e8f8f212b6a8b849aa branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T05:40:31-08:00 summary: bpo-35363: test_eintr uses print(flush=True) (GH-10990) (cherry picked from commit 0644b33821b70efbf0ac1ec1fb8729b05796564a) Co-authored-by: Victor Stinner files: M Lib/test/test_eintr.py diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index c2e8deadbab7..f61efa3c648e 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -20,12 +20,13 @@ def test_all(self): args = ["-u", tester, "-v"] if support.verbose: print() - print("--- run eintr_tester.py ---") + print("--- run eintr_tester.py ---", flush=True) # In verbose mode, the child process inherit stdout and stdout, # to see output in realtime and reduce the risk of loosing output. args = [sys.executable, "-E", "-X", "faulthandler", *args] proc = subprocess.run(args) - print(f"--- eintr_tester.py completed: exit code {proc.returncode} ---") + print(f"--- eintr_tester.py completed: " + f"exit code {proc.returncode} ---", flush=True) if proc.returncode: self.fail("eintr_tester.py failed") else: From webhook-mailer at python.org Thu Dec 6 12:06:00 2018 From: webhook-mailer at python.org (Pablo Galindo) Date: Thu, 06 Dec 2018 17:06:00 -0000 Subject: [Python-checkins] Remove unused function in `testmock/support.py` (GH-10975) Message-ID: https://github.com/python/cpython/commit/20428527a7c188d988d20b267cfef58da10b0fc9 commit: 20428527a7c188d988d20b267cfef58da10b0fc9 branch: master author: Mario Corchero committer: Pablo Galindo date: 2018-12-06T17:05:46Z summary: Remove unused function in `testmock/support.py` (GH-10975) The function is never imported and the implementation is actually buggy. As `warnings.catch_warnings` is not imported here. files: M Lib/unittest/test/testmock/support.py diff --git a/Lib/unittest/test/testmock/support.py b/Lib/unittest/test/testmock/support.py index 205431adcacc..c7ad20b80665 100644 --- a/Lib/unittest/test/testmock/support.py +++ b/Lib/unittest/test/testmock/support.py @@ -12,10 +12,3 @@ def wibble(self): class X(object): pass - - -def examine_warnings(func): - def wrapper(): - with catch_warnings(record=True) as ws: - func(ws) - return wrapper From webhook-mailer at python.org Thu Dec 6 15:37:04 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Thu, 06 Dec 2018 20:37:04 -0000 Subject: [Python-checkins] bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934) Message-ID: https://github.com/python/cpython/commit/42b1d6127bd8595522a78a75166ebb9fba74a6a2 commit: 42b1d6127bd8595522a78a75166ebb9fba74a6a2 branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-06T22:36:55+02:00 summary: bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934) files: M Lib/idlelib/debugger_r.py M Lib/ssl.py M Lib/test/test_ssl.py diff --git a/Lib/idlelib/debugger_r.py b/Lib/idlelib/debugger_r.py index 01a3bd25998f..0e6dcfbd12c2 100644 --- a/Lib/idlelib/debugger_r.py +++ b/Lib/idlelib/debugger_r.py @@ -157,7 +157,7 @@ def code_filename(self, cid): #----------called by a DictProxy---------- def dict_keys(self, did): - raise NotImplemented("dict_keys not public or pickleable") + raise NotImplementedError("dict_keys not public or pickleable") ## dict = dicttable[did] ## return dict.keys() diff --git a/Lib/ssl.py b/Lib/ssl.py index 8f6d402209b1..e6e3a6d0fa8d 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -884,8 +884,8 @@ def session_reused(self): return self._sslobj.session_reused def dup(self): - raise NotImplemented("Can't dup() %s instances" % - self.__class__.__name__) + raise NotImplementedError("Can't dup() %s instances" % + self.__class__.__name__) def _checkClosed(self, msg=None): # raise an exception here if you wish to check for spurious closes diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 74a91f6c2396..7f6b93148f45 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -463,8 +463,12 @@ def test_wrapped_unconnected(self): self.assertRaises(OSError, ss.recvfrom_into, bytearray(b'x'), 1) self.assertRaises(OSError, ss.send, b'x') self.assertRaises(OSError, ss.sendto, b'x', ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.dup) self.assertRaises(NotImplementedError, ss.sendmsg, [b'x'], (), 0, ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.recvmsg, 100) + self.assertRaises(NotImplementedError, ss.recvmsg_into, + [bytearray(100)]) def test_timeout(self): # Issue #8524: when creating an SSL socket, the timeout of the @@ -3382,10 +3386,11 @@ def _recvfrom_into(): # Make sure sendmsg et al are disallowed to avoid # inadvertent disclosure of data and/or corruption # of the encrypted data stream + self.assertRaises(NotImplementedError, s.dup) self.assertRaises(NotImplementedError, s.sendmsg, [b"data"]) self.assertRaises(NotImplementedError, s.recvmsg, 100) self.assertRaises(NotImplementedError, - s.recvmsg_into, bytearray(100)) + s.recvmsg_into, [bytearray(100)]) s.write(b"over\n") self.assertRaises(ValueError, s.recv, -1) From webhook-mailer at python.org Thu Dec 6 15:52:48 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 20:52:48 -0000 Subject: [Python-checkins] bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934) Message-ID: https://github.com/python/cpython/commit/6485aa6eb1024672f08afdd577e2b5792eb6b03c commit: 6485aa6eb1024672f08afdd577e2b5792eb6b03c branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T12:52:43-08:00 summary: bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934) (cherry picked from commit 42b1d6127bd8595522a78a75166ebb9fba74a6a2) Co-authored-by: Serhiy Storchaka files: M Lib/idlelib/debugger_r.py M Lib/ssl.py M Lib/test/test_ssl.py diff --git a/Lib/idlelib/debugger_r.py b/Lib/idlelib/debugger_r.py index 01a3bd25998f..0e6dcfbd12c2 100644 --- a/Lib/idlelib/debugger_r.py +++ b/Lib/idlelib/debugger_r.py @@ -157,7 +157,7 @@ def code_filename(self, cid): #----------called by a DictProxy---------- def dict_keys(self, did): - raise NotImplemented("dict_keys not public or pickleable") + raise NotImplementedError("dict_keys not public or pickleable") ## dict = dicttable[did] ## return dict.keys() diff --git a/Lib/ssl.py b/Lib/ssl.py index 38aa38907e15..d1d986682035 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -884,8 +884,8 @@ def session_reused(self): return self._sslobj.session_reused def dup(self): - raise NotImplemented("Can't dup() %s instances" % - self.__class__.__name__) + raise NotImplementedError("Can't dup() %s instances" % + self.__class__.__name__) def _checkClosed(self, msg=None): # raise an exception here if you wish to check for spurious closes diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index d4132c5043c7..f1b9565c8d91 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -480,8 +480,12 @@ def test_wrapped_unconnected(self): self.assertRaises(OSError, ss.recvfrom_into, bytearray(b'x'), 1) self.assertRaises(OSError, ss.send, b'x') self.assertRaises(OSError, ss.sendto, b'x', ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.dup) self.assertRaises(NotImplementedError, ss.sendmsg, [b'x'], (), 0, ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.recvmsg, 100) + self.assertRaises(NotImplementedError, ss.recvmsg_into, + [bytearray(100)]) def test_timeout(self): # Issue #8524: when creating an SSL socket, the timeout of the @@ -3410,10 +3414,11 @@ def _recvfrom_into(): # Make sure sendmsg et al are disallowed to avoid # inadvertent disclosure of data and/or corruption # of the encrypted data stream + self.assertRaises(NotImplementedError, s.dup) self.assertRaises(NotImplementedError, s.sendmsg, [b"data"]) self.assertRaises(NotImplementedError, s.recvmsg, 100) self.assertRaises(NotImplementedError, - s.recvmsg_into, bytearray(100)) + s.recvmsg_into, [bytearray(100)]) s.write(b"over\n") self.assertRaises(ValueError, s.recv, -1) From webhook-mailer at python.org Thu Dec 6 15:56:28 2018 From: webhook-mailer at python.org (Gregory P. Smith) Date: Thu, 06 Dec 2018 20:56:28 -0000 Subject: [Python-checkins] Clarify expectedFailure in the unittest docs. (#10953) Message-ID: https://github.com/python/cpython/commit/91f259b478ae8bfb4c73e5b5a767e4bf0ee9257f commit: 91f259b478ae8bfb4c73e5b5a767e4bf0ee9257f branch: master author: Gregory P. Smith committer: GitHub date: 2018-12-06T12:56:24-08:00 summary: Clarify expectedFailure in the unittest docs. (#10953) files: M Doc/library/unittest.rst diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 8afbee642620..acf9b49548b3 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -585,8 +585,8 @@ The following decorators implement test skipping and expected failures: .. decorator:: expectedFailure - Mark the test as an expected failure. If the test fails when run, the test - is not counted as a failure. + Mark the test as an expected failure. If the test fails it will be + considered a success. If the test passes, it will be considered a failure. .. exception:: SkipTest(reason) From webhook-mailer at python.org Thu Dec 6 16:00:42 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Thu, 06 Dec 2018 21:00:42 -0000 Subject: [Python-checkins] bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934). (GH-11001) Message-ID: https://github.com/python/cpython/commit/7a2cf1e7d3bf300e98c702589d405734f4a8fcf8 commit: 7a2cf1e7d3bf300e98c702589d405734f4a8fcf8 branch: 3.6 author: Serhiy Storchaka committer: GitHub date: 2018-12-06T23:00:39+02:00 summary: bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934). (GH-11001) (cherry picked from commit 42b1d6127bd8595522a78a75166ebb9fba74a6a2) files: M Lib/idlelib/debugger_r.py M Lib/ssl.py M Lib/test/test_ssl.py diff --git a/Lib/idlelib/debugger_r.py b/Lib/idlelib/debugger_r.py index 01a3bd25998f..0e6dcfbd12c2 100644 --- a/Lib/idlelib/debugger_r.py +++ b/Lib/idlelib/debugger_r.py @@ -157,7 +157,7 @@ def code_filename(self, cid): #----------called by a DictProxy---------- def dict_keys(self, did): - raise NotImplemented("dict_keys not public or pickleable") + raise NotImplementedError("dict_keys not public or pickleable") ## dict = dicttable[did] ## return dict.keys() diff --git a/Lib/ssl.py b/Lib/ssl.py index cd216a15cda8..58d3e939226b 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -848,8 +848,8 @@ def session_reused(self): return self._sslobj.session_reused def dup(self): - raise NotImplemented("Can't dup() %s instances" % - self.__class__.__name__) + raise NotImplementedError("Can't dup() %s instances" % + self.__class__.__name__) def _checkClosed(self, msg=None): # raise an exception here if you wish to check for spurious closes diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 2f0b6a75e96f..705f1d3245b6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -408,6 +408,12 @@ def test_wrapped_unconnected(self): self.assertRaises(OSError, ss.recvfrom_into, bytearray(b'x'), 1) self.assertRaises(OSError, ss.send, b'x') self.assertRaises(OSError, ss.sendto, b'x', ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.dup) + self.assertRaises(NotImplementedError, ss.sendmsg, + [b'x'], (), 0, ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.recvmsg, 100) + self.assertRaises(NotImplementedError, ss.recvmsg_into, + [bytearray(100)]) def test_timeout(self): # Issue #8524: when creating an SSL socket, the timeout of the @@ -2942,11 +2948,11 @@ def _recvfrom_into(): # Make sure sendmsg et al are disallowed to avoid # inadvertent disclosure of data and/or corruption # of the encrypted data stream + self.assertRaises(NotImplementedError, s.dup) self.assertRaises(NotImplementedError, s.sendmsg, [b"data"]) self.assertRaises(NotImplementedError, s.recvmsg, 100) self.assertRaises(NotImplementedError, - s.recvmsg_into, bytearray(100)) - + s.recvmsg_into, [bytearray(100)]) s.write(b"over\n") self.assertRaises(ValueError, s.recv, -1) From webhook-mailer at python.org Thu Dec 6 16:06:59 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 21:06:59 -0000 Subject: [Python-checkins] Add missing period in distutils.dep_util.newer_group doc (GH-11003) Message-ID: https://github.com/python/cpython/commit/c9566b8c454120e3d0ddb5ab970f262a6cd80077 commit: c9566b8c454120e3d0ddb5ab970f262a6cd80077 branch: master author: Andre Delfino committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-06T13:06:55-08:00 summary: Add missing period in distutils.dep_util.newer_group doc (GH-11003) files: M Doc/distutils/apiref.rst diff --git a/Doc/distutils/apiref.rst b/Doc/distutils/apiref.rst index b10b39ae22ac..a825efc1a672 100644 --- a/Doc/distutils/apiref.rst +++ b/Doc/distutils/apiref.rst @@ -941,7 +941,7 @@ timestamp dependency analysis. .. function:: newer_group(sources, target[, missing='error']) Return true if *target* is out-of-date with respect to any file listed in - *sources* In other words, if *target* exists and is newer than every file in + *sources*. In other words, if *target* exists and is newer than every file in *sources*, return false; otherwise return true. *missing* controls what we do when a source file is missing; the default (``'error'``) is to blow up with an :exc:`OSError` from inside :func:`os.stat`; if it is ``'ignore'``, we silently From webhook-mailer at python.org Thu Dec 6 16:16:08 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 21:16:08 -0000 Subject: [Python-checkins] Clarify expectedFailure in the unittest docs. (GH-10953) Message-ID: https://github.com/python/cpython/commit/f913d44eb75f930c37dc6bcc5d6579be5740ae73 commit: f913d44eb75f930c37dc6bcc5d6579be5740ae73 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T13:16:03-08:00 summary: Clarify expectedFailure in the unittest docs. (GH-10953) (cherry picked from commit 91f259b478ae8bfb4c73e5b5a767e4bf0ee9257f) Co-authored-by: Gregory P. Smith files: M Doc/library/unittest.rst diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index fb5bd2b7648e..774b0875bbf1 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -585,8 +585,8 @@ The following decorators implement test skipping and expected failures: .. decorator:: expectedFailure - Mark the test as an expected failure. If the test fails when run, the test - is not counted as a failure. + Mark the test as an expected failure. If the test fails it will be + considered a success. If the test passes, it will be considered a failure. .. exception:: SkipTest(reason) From webhook-mailer at python.org Thu Dec 6 16:30:17 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 21:30:17 -0000 Subject: [Python-checkins] Add missing period in distutils.dep_util.newer_group doc (GH-11003) Message-ID: https://github.com/python/cpython/commit/72c71956cade606bd5500cf76d4d7c1d50a7ccae commit: 72c71956cade606bd5500cf76d4d7c1d50a7ccae branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T13:30:13-08:00 summary: Add missing period in distutils.dep_util.newer_group doc (GH-11003) (cherry picked from commit c9566b8c454120e3d0ddb5ab970f262a6cd80077) Co-authored-by: Andre Delfino files: M Doc/distutils/apiref.rst diff --git a/Doc/distutils/apiref.rst b/Doc/distutils/apiref.rst index dccd7ce236cb..8efeffb6376e 100644 --- a/Doc/distutils/apiref.rst +++ b/Doc/distutils/apiref.rst @@ -941,7 +941,7 @@ timestamp dependency analysis. .. function:: newer_group(sources, target[, missing='error']) Return true if *target* is out-of-date with respect to any file listed in - *sources* In other words, if *target* exists and is newer than every file in + *sources*. In other words, if *target* exists and is newer than every file in *sources*, return false; otherwise return true. *missing* controls what we do when a source file is missing; the default (``'error'``) is to blow up with an :exc:`OSError` from inside :func:`os.stat`; if it is ``'ignore'``, we silently From webhook-mailer at python.org Thu Dec 6 16:32:39 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 21:32:39 -0000 Subject: [Python-checkins] Add missing period in distutils.dep_util.newer_group doc (GH-11003) Message-ID: https://github.com/python/cpython/commit/a51a5ca77eae079b34f911975a77e713b0c237f1 commit: a51a5ca77eae079b34f911975a77e713b0c237f1 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T13:32:31-08:00 summary: Add missing period in distutils.dep_util.newer_group doc (GH-11003) (cherry picked from commit c9566b8c454120e3d0ddb5ab970f262a6cd80077) Co-authored-by: Andre Delfino files: M Doc/distutils/apiref.rst diff --git a/Doc/distutils/apiref.rst b/Doc/distutils/apiref.rst index f6163258df2e..207f43864c93 100644 --- a/Doc/distutils/apiref.rst +++ b/Doc/distutils/apiref.rst @@ -937,7 +937,7 @@ timestamp dependency analysis. .. function:: newer_group(sources, target[, missing='error']) Return true if *target* is out-of-date with respect to any file listed in - *sources* In other words, if *target* exists and is newer than every file in + *sources*. In other words, if *target* exists and is newer than every file in *sources*, return false; otherwise return true. *missing* controls what we do when a source file is missing; the default (``'error'``) is to blow up with an :exc:`OSError` from inside :func:`os.stat`; if it is ``'ignore'``, we silently From webhook-mailer at python.org Thu Dec 6 16:34:19 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Thu, 06 Dec 2018 21:34:19 -0000 Subject: [Python-checkins] Add missing period in distutils.dep_util.newer_group doc (GH-11003) Message-ID: https://github.com/python/cpython/commit/107b27eee013f0747ba133886f87aacc7f451030 commit: 107b27eee013f0747ba133886f87aacc7f451030 branch: 2.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T13:34:15-08:00 summary: Add missing period in distutils.dep_util.newer_group doc (GH-11003) (cherry picked from commit c9566b8c454120e3d0ddb5ab970f262a6cd80077) Co-authored-by: Andre Delfino files: M Doc/distutils/apiref.rst diff --git a/Doc/distutils/apiref.rst b/Doc/distutils/apiref.rst index 34a3cb34af32..eade90542c0a 100644 --- a/Doc/distutils/apiref.rst +++ b/Doc/distutils/apiref.rst @@ -934,7 +934,7 @@ timestamp dependency analysis. .. function:: newer_group(sources, target[, missing='error']) Return true if *target* is out-of-date with respect to any file listed in - *sources* In other words, if *target* exists and is newer than every file in + *sources*. In other words, if *target* exists and is newer than every file in *sources*, return false; otherwise return true. *missing* controls what we do when a source file is missing; the default (``'error'``) is to blow up with an :exc:`OSError` from inside :func:`os.stat`; if it is ``'ignore'``, we silently From webhook-mailer at python.org Fri Dec 7 00:09:26 2018 From: webhook-mailer at python.org (Steve Dower) Date: Fri, 07 Dec 2018 05:09:26 -0000 Subject: [Python-checkins] bpo-34977: Add Windows App Store package (GH-10245) Message-ID: https://github.com/python/cpython/commit/468a15aaf9206448a744fc5eab3fc21f51966aad commit: 468a15aaf9206448a744fc5eab3fc21f51966aad branch: master author: Steve Dower committer: GitHub date: 2018-12-06T21:09:20-08:00 summary: bpo-34977: Add Windows App Store package (GH-10245) files: A .azure-pipelines/windows-appx-test.yml A Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst A PC/classicAppCompat.can.xml A PC/classicAppCompat.cat A PC/classicAppCompat.sccd A PC/icons/pythonwx150.png A PC/icons/pythonwx44.png A PC/icons/pythonx150.png A PC/icons/pythonx44.png A PC/icons/pythonx50.png A PC/layout/__init__.py A PC/layout/__main__.py A PC/layout/main.py A PC/layout/support/__init__.py A PC/layout/support/appxmanifest.py A PC/layout/support/catalog.py A PC/layout/support/constants.py A PC/layout/support/distutils.command.bdist_wininst.py A PC/layout/support/filesets.py A PC/layout/support/logging.py A PC/layout/support/options.py A PC/layout/support/pip.py A PC/layout/support/props.py A PC/layout/support/python.props A PC/python_uwp.cpp A PC/store_info.txt A PCbuild/python_uwp.vcxproj A PCbuild/pythonw_uwp.vcxproj A PCbuild/venvlauncher.vcxproj A PCbuild/venvwlauncher.vcxproj A Tools/msi/make_appx.ps1 A Tools/msi/make_cat.ps1 A Tools/msi/sdktools.psm1 A Tools/msi/sign_build.ps1 D Tools/msi/make_zip.py D Tools/nuget/python.props M .gitattributes M Doc/make.bat M Lib/test/test_pathlib.py M Lib/test/test_venv.py M Lib/venv/__init__.py M PC/getpathp.c M PC/launcher.c M PC/pylauncher.rc M PCbuild/_tkinter.vcxproj M PCbuild/find_msbuild.bat M PCbuild/pcbuild.proj M PCbuild/pcbuild.sln M PCbuild/python.props M PCbuild/pythoncore.vcxproj M Tools/msi/buildrelease.bat M Tools/msi/make_zip.proj M Tools/nuget/make_pkg.proj diff --git a/.azure-pipelines/windows-appx-test.yml b/.azure-pipelines/windows-appx-test.yml new file mode 100644 index 000000000000..9840c0a1221f --- /dev/null +++ b/.azure-pipelines/windows-appx-test.yml @@ -0,0 +1,65 @@ +jobs: +- job: Prebuild + displayName: Pre-build checks + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./prebuild-checks.yml + + +- job: Windows_Appx_Tests + displayName: Windows Appx Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: vs2017-win2016 + + strategy: + matrix: + win64: + arch: amd64 + buildOpt: '-p x64' + testRunTitle: '$(Build.SourceBranchName)-win64-appx' + testRunPlatform: win64 + maxParallel: 2 + + steps: + - checkout: self + clean: true + fetchDepth: 5 + + - powershell: | + # Relocate build outputs outside of source directory to make cleaning faster + Write-Host '##vso[task.setvariable variable=Py_IntDir]$(Build.BinariesDirectory)\obj' + # UNDONE: Do not build to a different directory because of broken tests + Write-Host '##vso[task.setvariable variable=Py_OutDir]$(Build.SourcesDirectory)\PCbuild' + Write-Host '##vso[task.setvariable variable=EXTERNAL_DIR]$(Build.BinariesDirectory)\externals' + displayName: Update build locations + + - script: PCbuild\build.bat -e $(buildOpt) + displayName: 'Build CPython' + + - script: python.bat PC\layout -vv -s "$(Build.SourcesDirectory)" -b "$(Py_OutDir)\$(arch)" -t "$(Py_IntDir)\layout-tmp-$(arch)" --copy "$(Py_IntDir)\layout-$(arch)" --precompile --preset-appx --include-tests + displayName: 'Create APPX layout' + + - script: .\python.exe -m test.pythoninfo + workingDirectory: $(Py_IntDir)\layout-$(arch) + displayName: 'Display build info' + + - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results.xml" --tempdir "$(Py_IntDir)\tmp-$(arch)" + workingDirectory: $(Py_IntDir)\layout-$(arch) + displayName: 'Tests' + env: + PREFIX: $(Py_IntDir)\layout-$(arch) + + - task: PublishTestResults at 2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(Build.BinariesDirectory)\test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: succeededOrFailed() diff --git a/.gitattributes b/.gitattributes index 4a487c3c2a14..16237bb2b3ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,7 @@ # Specific binary files Lib/test/sndhdrdata/sndhdr.* binary +PC/classicAppCompat.* binary # Text files that should not be subject to eol conversion Lib/test/cjkencodings/* -text diff --git a/Doc/make.bat b/Doc/make.bat index d28dae78e86d..a8b32375810d 100644 --- a/Doc/make.bat +++ b/Doc/make.bat @@ -117,10 +117,12 @@ if not exist "%BUILDDIR%" mkdir "%BUILDDIR%" if exist ..\Misc\NEWS ( echo.Copying Misc\NEWS to build\NEWS + if not exist build mkdir build copy ..\Misc\NEWS build\NEWS > nul ) else if exist ..\Misc\NEWS.D ( if defined BLURB ( echo.Merging Misc/NEWS with %BLURB% + if not exist build mkdir build %BLURB% merge -f build\NEWS ) else ( echo.No Misc/NEWS file and Blurb is not available. diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 876eecccfd5f..d3fd4bd9e6b7 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1521,7 +1521,7 @@ def test_resolve_common(self): # resolves to 'dirB/..' first before resolving to parent of dirB. self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) # Now create absolute symlinks - d = support._longpath(tempfile.mkdtemp(suffix='-dirD')) + d = support._longpath(tempfile.mkdtemp(suffix='-dirD', dir=os.getcwd())) self.addCleanup(support.rmtree, d) os.symlink(os.path.join(d), join('dirA', 'linkX')) os.symlink(join('dirB'), os.path.join(d, 'linkY')) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 461fe7afd213..22a3b78852f8 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -243,6 +243,7 @@ def test_isolation(self): self.assertIn('include-system-site-packages = %s\n' % s, data) @unittest.skipUnless(can_symlink(), 'Needs symlinks') + @unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows') def test_symlinking(self): """ Test symlinking works as expected diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 043420897e47..5438b0d4e508 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -64,10 +64,11 @@ def create(self, env_dir): self.system_site_packages = False self.create_configuration(context) self.setup_python(context) + if not self.upgrade: + self.setup_scripts(context) if self.with_pip: self._setup_pip(context) if not self.upgrade: - self.setup_scripts(context) self.post_setup(context) if true_system_site_packages: # We had set it to False before, now @@ -158,14 +159,6 @@ def create_configuration(self, context): f.write('include-system-site-packages = %s\n' % incl) f.write('version = %d.%d.%d\n' % sys.version_info[:3]) - if os.name == 'nt': - def include_binary(self, f): - if f.endswith(('.pyd', '.dll')): - result = True - else: - result = f.startswith('python') and f.endswith('.exe') - return result - def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): """ Try symlinking a file, and if that fails, fall back to copying. @@ -195,9 +188,9 @@ def setup_python(self, context): binpath = context.bin_path path = context.env_exe copier = self.symlink_or_copy - copier(context.executable, path) dirname = context.python_dir if os.name != 'nt': + copier(context.executable, path) if not os.path.islink(path): os.chmod(path, 0o755) for suffix in ('python', 'python3'): @@ -209,26 +202,22 @@ def setup_python(self, context): if not os.path.islink(path): os.chmod(path, 0o755) else: - # See bpo-34011. When using a proper install, we should only need to - # copy the top-level of DLLs. - include = self.include_binary - files = [f for f in os.listdir(dirname) if include(f)] - for f in files: - src = os.path.join(dirname, f) - dst = os.path.join(binpath, f) - if dst != context.env_exe: # already done, above - copier(src, dst) - - # When creating from a build directory, we continue to copy all files. + # For normal cases, the venvlauncher will be copied from + # our scripts folder. For builds, we need to copy it + # manually. if sysconfig.is_python_build(True): - subdir = 'DLLs' - dirname = os.path.join(dirname, subdir) - if os.path.isdir(dirname): - files = [f for f in os.listdir(dirname) if include(f)] - for f in files: - src = os.path.join(dirname, f) - dst = os.path.join(binpath, f) - copier(src, dst) + suffix = '.exe' + if context.python_exe.lower().endswith('_d.exe'): + suffix = '_d.exe' + + src = os.path.join(dirname, "venvlauncher" + suffix) + dst = os.path.join(binpath, context.python_exe) + copier(src, dst) + + src = os.path.join(dirname, "venvwlauncher" + suffix) + dst = os.path.join(binpath, "pythonw" + suffix) + copier(src, dst) + # copy init.tcl over for root, dirs, files in os.walk(context.python_dir): if 'init.tcl' in files: @@ -326,7 +315,7 @@ def install_scripts(self, context, path): dstfile = os.path.join(dstdir, f) with open(srcfile, 'rb') as f: data = f.read() - if not srcfile.endswith('.exe'): + if not srcfile.endswith(('.exe', '.pdb')): try: data = data.decode('utf-8') data = self.replace_variables(data, context) diff --git a/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst new file mode 100644 index 000000000000..8e1a4ba84880 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst @@ -0,0 +1 @@ +Adds support for building a Windows App Store package diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml new file mode 100644 index 000000000000..f00475c8da31 --- /dev/null +++ b/PC/classicAppCompat.can.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat new file mode 100644 index 000000000000..3d213596accf Binary files /dev/null and b/PC/classicAppCompat.cat differ diff --git a/PC/classicAppCompat.sccd b/PC/classicAppCompat.sccd new file mode 100644 index 000000000000..97648985a2cc --- /dev/null +++ b/PC/classicAppCompat.sccd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 diff --git a/PC/getpathp.c b/PC/getpathp.c index 25f371fc9f9d..452501a9a884 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -536,10 +536,16 @@ static _PyInitError get_program_full_path(const _PyCoreConfig *core_config, PyCalculatePath *calculate, _PyPathConfig *config) { + const wchar_t *pyvenv_launcher; wchar_t program_full_path[MAXPATHLEN+1]; memset(program_full_path, 0, sizeof(program_full_path)); - if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { + /* The launcher may need to force the executable path to a + * different environment, so override it here. */ + pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__"); + if (pyvenv_launcher && pyvenv_launcher[0]) { + wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher); + } else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { /* GetModuleFileName should never fail when passed NULL */ return _Py_INIT_ERR("Cannot determine program path"); } diff --git a/PC/icons/pythonwx150.png b/PC/icons/pythonwx150.png new file mode 100644 index 000000000000..4c3eb316739c Binary files /dev/null and b/PC/icons/pythonwx150.png differ diff --git a/PC/icons/pythonwx44.png b/PC/icons/pythonwx44.png new file mode 100644 index 000000000000..e3b32a871f90 Binary files /dev/null and b/PC/icons/pythonwx44.png differ diff --git a/PC/icons/pythonx150.png b/PC/icons/pythonx150.png new file mode 100644 index 000000000000..5f8d30418386 Binary files /dev/null and b/PC/icons/pythonx150.png differ diff --git a/PC/icons/pythonx44.png b/PC/icons/pythonx44.png new file mode 100644 index 000000000000..3881daaef233 Binary files /dev/null and b/PC/icons/pythonx44.png differ diff --git a/PC/icons/pythonx50.png b/PC/icons/pythonx50.png new file mode 100644 index 000000000000..7cc3aecd0242 Binary files /dev/null and b/PC/icons/pythonx50.png differ diff --git a/PC/launcher.c b/PC/launcher.c index 2c2da76f6146..0242f2639119 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -28,7 +28,7 @@ #define RC_NO_PYTHON 103 #define RC_NO_MEMORY 104 /* - * SCRIPT_WRAPPER is used to choose between two variants of an executable built + * SCRIPT_WRAPPER is used to choose one of the variants of an executable built * from this source file. If not defined, the PEP 397 Python launcher is built; * if defined, a script launcher of the type used by setuptools is built, which * looks for a script name related to the executable name and runs that script @@ -40,6 +40,15 @@ #if defined(SCRIPT_WRAPPER) #define RC_NO_SCRIPT 105 #endif +/* + * VENV_REDIRECT is used to choose the variant that looks for an adjacent or + * one-level-higher pyvenv.cfg, and uses its "home" property to locate and + * launch the original python.exe. + */ +#if defined(VENV_REDIRECT) +#define RC_NO_VENV_CFG 106 +#define RC_BAD_VENV_CFG 107 +#endif /* Just for now - static definition */ @@ -97,7 +106,7 @@ error(int rc, wchar_t * format, ... ) #if !defined(_WINDOWS) fwprintf(stderr, L"%ls\n", message); #else - MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."), + MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", MB_OK); #endif exit(rc); @@ -131,6 +140,17 @@ static wchar_t * get_env(wchar_t * key) return buf; } +#if defined(_DEBUG) +#if defined(_WINDOWS) + +#define PYTHON_EXECUTABLE L"pythonw_d.exe" + +#else + +#define PYTHON_EXECUTABLE L"python_d.exe" + +#endif +#else #if defined(_WINDOWS) #define PYTHON_EXECUTABLE L"pythonw.exe" @@ -139,6 +159,7 @@ static wchar_t * get_env(wchar_t * key) #define PYTHON_EXECUTABLE L"python.exe" +#endif #endif #define MAX_VERSION_SIZE 4 @@ -1457,6 +1478,87 @@ show_python_list(wchar_t ** argv) return FALSE; /* If this has been called we cannot continue */ } +#if defined(VENV_REDIRECT) + +static int +find_home_value(const char *buffer, const char **start, DWORD *length) +{ + for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) { + if (*s == '\n') { + ++s; + } + for (int i = 4; i > 0 && *s; --i, ++s); + + while (*s && iswspace(*s)) { + ++s; + } + if (*s != L'=') { + continue; + } + + do { + ++s; + } while (*s && iswspace(*s)); + + *start = s; + char *nl = strchr(s, '\n'); + if (nl) { + *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s); + } else { + *length = (DWORD)strlen(s); + } + return 1; + } + return 0; +} +#endif + +static wchar_t * +wcsdup_pad(const wchar_t *s, int padding, int *newlen) +{ + size_t len = wcslen(s); + len += 1 + padding; + wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t)); + if (!r) { + return NULL; + } + if (wcscpy_s(r, len, s)) { + free(r); + return NULL; + } + *newlen = len < MAXINT ? (int)len : MAXINT; + return r; +} + +static wchar_t * +get_process_name() +{ + DWORD bufferLen = MAX_PATH; + DWORD len = bufferLen; + wchar_t *r = NULL; + + while (!r) { + r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); + if (!r) { + error(RC_NO_MEMORY, L"out of memory"); + return NULL; + } + len = GetModuleFileNameW(NULL, r, bufferLen); + if (len == 0) { + free(r); + error(0, L"Failed to get module name"); + return NULL; + } else if (len == bufferLen && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(r); + r = NULL; + bufferLen *= 2; + } + } + + return r; +} + static int process(int argc, wchar_t ** argv) { @@ -1464,21 +1566,27 @@ process(int argc, wchar_t ** argv) wchar_t * command; wchar_t * executable; wchar_t * p; + wchar_t * argv0; int rc = 0; - size_t plen; INSTALLED_PYTHON * ip; BOOL valid; DWORD size, attrs; - HRESULT hr; wchar_t message[MSGSIZE]; void * version_data; VS_FIXEDFILEINFO * file_info; UINT block_size; - int index; -#if defined(SCRIPT_WRAPPER) +#if defined(VENV_REDIRECT) + wchar_t * venv_cfg_path; int newlen; +#elif defined(SCRIPT_WRAPPER) wchar_t * newcommand; wchar_t * av[2]; + int newlen; + HRESULT hr; + int index; +#else + HRESULT hr; + int index; #endif setvbuf(stderr, (char *)NULL, _IONBF, 0); @@ -1496,6 +1604,7 @@ process(int argc, wchar_t ** argv) #else debug(L"launcher executable: Console\n"); #endif +#if !defined(VENV_REDIRECT) /* Get the local appdata folder (non-roaming) */ hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appdata_ini_path); @@ -1504,9 +1613,7 @@ process(int argc, wchar_t ** argv) appdata_ini_path[0] = L'\0'; } else { - plen = wcslen(appdata_ini_path); - p = &appdata_ini_path[plen]; - wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE); + wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE); attrs = GetFileAttributesW(appdata_ini_path); if (attrs == INVALID_FILE_ATTRIBUTES) { debug(L"File '%ls' non-existent\n", appdata_ini_path); @@ -1515,8 +1622,9 @@ process(int argc, wchar_t ** argv) debug(L"Using local configuration file '%ls'\n", appdata_ini_path); } } - plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH); - size = GetFileVersionInfoSizeW(launcher_ini_path, &size); +#endif + argv0 = get_process_name(); + size = GetFileVersionInfoSizeW(argv0, &size); if (size == 0) { winerror(GetLastError(), message, MSGSIZE); debug(L"GetFileVersionInfoSize failed: %ls\n", message); @@ -1524,7 +1632,7 @@ process(int argc, wchar_t ** argv) else { version_data = malloc(size); if (version_data) { - valid = GetFileVersionInfoW(launcher_ini_path, 0, size, + valid = GetFileVersionInfoW(argv0, 0, size, version_data); if (!valid) debug(L"GetFileVersionInfo failed: %X\n", GetLastError()); @@ -1541,15 +1649,51 @@ process(int argc, wchar_t ** argv) free(version_data); } } + +#if defined(VENV_REDIRECT) + /* Allocate some extra space for new filenames */ + venv_cfg_path = wcsdup_pad(argv0, 32, &newlen); + if (!venv_cfg_path) { + error(RC_NO_MEMORY, L"Failed to copy module name"); + } + p = wcsrchr(venv_cfg_path, L'\\'); + + if (p == NULL) { + error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); + } + p[0] = L'\0'; + wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); + attrs = GetFileAttributesW(venv_cfg_path); + if (attrs == INVALID_FILE_ATTRIBUTES) { + debug(L"File '%ls' non-existent\n", venv_cfg_path); + p[0] = '\0'; + p = wcsrchr(venv_cfg_path, L'\\'); + if (p != NULL) { + p[0] = '\0'; + wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); + attrs = GetFileAttributesW(venv_cfg_path); + if (attrs == INVALID_FILE_ATTRIBUTES) { + debug(L"File '%ls' non-existent\n", venv_cfg_path); + error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); + } + } + } + debug(L"Using venv configuration file '%ls'\n", venv_cfg_path); +#else + /* Allocate some extra space for new filenames */ + if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) { + error(RC_NO_MEMORY, L"Failed to copy module name"); + } p = wcsrchr(launcher_ini_path, L'\\'); + if (p == NULL) { debug(L"GetModuleFileNameW returned value has no backslash: %ls\n", launcher_ini_path); launcher_ini_path[0] = L'\0'; } else { - wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini", - _TRUNCATE); + p[0] = L'\0'; + wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini"); attrs = GetFileAttributesW(launcher_ini_path); if (attrs == INVALID_FILE_ATTRIBUTES) { debug(L"File '%ls' non-existent\n", launcher_ini_path); @@ -1558,6 +1702,7 @@ process(int argc, wchar_t ** argv) debug(L"Using global configuration file '%ls'\n", launcher_ini_path); } } +#endif command = skip_me(GetCommandLineW()); debug(L"Called with command line: %ls\n", command); @@ -1593,6 +1738,52 @@ process(int argc, wchar_t ** argv) command = newcommand; valid = FALSE; } +#elif defined(VENV_REDIRECT) + { + FILE *f; + char buffer[4096]; /* 4KB should be enough for anybody */ + char *start; + DWORD len, cch, cch_actual; + size_t cb; + if (_wfopen_s(&f, venv_cfg_path, L"r")) { + error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path); + } + cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]), + sizeof(buffer) / sizeof(buffer[0]), f); + fclose(f); + + if (!find_home_value(buffer, &start, &len)) { + error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'", + venv_cfg_path); + } + + cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0); + if (!cch) { + error(0, L"Cannot determine memory for home path"); + } + cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */ + executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); + cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch); + if (!cch_actual) { + error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", + venv_cfg_path); + } + if (executable[cch_actual - 1] != L'\\') { + executable[cch_actual++] = L'\\'; + executable[cch_actual] = L'\0'; + } + if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) { + error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'", + venv_cfg_path); + } + if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) { + error(RC_NO_PYTHON, L"No Python at '%ls'", executable); + } + if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) { + error(0, L"Failed to set launcher environment"); + } + valid = 1; + } #else if (argc <= 1) { valid = FALSE; @@ -1600,7 +1791,6 @@ process(int argc, wchar_t ** argv) } else { p = argv[1]; - plen = wcslen(p); if ((argc == 2) && // list version args (!wcsncmp(p, L"-0", wcslen(L"-0")) || !wcsncmp(p, L"--list", wcslen(L"--list")))) diff --git a/PC/layout/__init__.py b/PC/layout/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/PC/layout/__main__.py b/PC/layout/__main__.py new file mode 100644 index 000000000000..f7aa1e6d261f --- /dev/null +++ b/PC/layout/__main__.py @@ -0,0 +1,14 @@ +import sys + +try: + import layout +except ImportError: + # Failed to import our package, which likely means we were started directly + # Add the additional search path needed to locate our module. + from pathlib import Path + + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from layout.main import main + +sys.exit(int(main() or 0)) diff --git a/PC/layout/main.py b/PC/layout/main.py new file mode 100644 index 000000000000..82d0536ca920 --- /dev/null +++ b/PC/layout/main.py @@ -0,0 +1,612 @@ +""" +Generates a layout of Python for Windows from a build. + +See python make_layout.py --help for usage. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import argparse +import functools +import os +import re +import shutil +import subprocess +import sys +import tempfile +import zipfile + +from pathlib import Path + +if __name__ == "__main__": + # Started directly, so enable relative imports + __path__ = [str(Path(__file__).resolve().parent)] + +from .support.appxmanifest import * +from .support.catalog import * +from .support.constants import * +from .support.filesets import * +from .support.logging import * +from .support.options import * +from .support.pip import * +from .support.props import * + +BDIST_WININST_FILES_ONLY = FileNameSet("wininst-*", "bdist_wininst.py") +BDIST_WININST_STUB = "PC/layout/support/distutils.command.bdist_wininst.py" + +TEST_PYDS_ONLY = FileStemSet("xxlimited", "_ctypes_test", "_test*") +TEST_DIRS_ONLY = FileNameSet("test", "tests") + +IDLE_DIRS_ONLY = FileNameSet("idlelib") + +TCLTK_PYDS_ONLY = FileStemSet("tcl*", "tk*", "_tkinter") +TCLTK_DIRS_ONLY = FileNameSet("tkinter", "turtledemo") +TCLTK_FILES_ONLY = FileNameSet("turtle.py") + +VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip") + +EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext") +EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle") +EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt") +EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*") +EXCLUDE_FROM_CATALOG = FileSuffixSet(".exe", ".pyd", ".dll") + +REQUIRED_DLLS = FileStemSet("libcrypto*", "libssl*") + +LIB2TO3_GRAMMAR_FILES = FileNameSet("Grammar.txt", "PatternGrammar.txt") + +PY_FILES = FileSuffixSet(".py") +PYC_FILES = FileSuffixSet(".pyc") +CAT_FILES = FileSuffixSet(".cat") +CDF_FILES = FileSuffixSet(".cdf") + +DATA_DIRS = FileNameSet("data") + +TOOLS_DIRS = FileNameSet("scripts", "i18n", "pynche", "demo", "parser") +TOOLS_FILES = FileSuffixSet(".py", ".pyw", ".txt") + + +def get_lib_layout(ns): + def _c(f): + if f in EXCLUDE_FROM_LIB: + return False + if f.is_dir(): + if f in TEST_DIRS_ONLY: + return ns.include_tests + if f in TCLTK_DIRS_ONLY: + return ns.include_tcltk + if f in IDLE_DIRS_ONLY: + return ns.include_idle + if f in VENV_DIRS_ONLY: + return ns.include_venv + else: + if f in TCLTK_FILES_ONLY: + return ns.include_tcltk + if f in BDIST_WININST_FILES_ONLY: + return ns.include_bdist_wininst + return True + + for dest, src in rglob(ns.source / "Lib", "**/*", _c): + yield dest, src + + if not ns.include_bdist_wininst: + src = ns.source / BDIST_WININST_STUB + yield Path("distutils/command/bdist_wininst.py"), src + + +def get_tcltk_lib(ns): + if not ns.include_tcltk: + return + + tcl_lib = os.getenv("TCL_LIBRARY") + if not tcl_lib or not os.path.isdir(tcl_lib): + try: + with open(ns.build / "TCL_LIBRARY.env", "r", encoding="utf-8-sig") as f: + tcl_lib = f.read().strip() + except FileNotFoundError: + pass + if not tcl_lib or not os.path.isdir(tcl_lib): + warn("Failed to find TCL_LIBRARY") + return + + for dest, src in rglob(Path(tcl_lib).parent, "**/*"): + yield "tcl/{}".format(dest), src + + +def get_layout(ns): + def in_build(f, dest="", new_name=None): + n, _, x = f.rpartition(".") + n = new_name or n + src = ns.build / f + if ns.debug and src not in REQUIRED_DLLS: + if not src.stem.endswith("_d"): + src = src.parent / (src.stem + "_d" + src.suffix) + if not n.endswith("_d"): + n += "_d" + f = n + "." + x + yield dest + n + "." + x, src + if ns.include_symbols: + pdb = src.with_suffix(".pdb") + if pdb.is_file(): + yield dest + n + ".pdb", pdb + if ns.include_dev: + lib = src.with_suffix(".lib") + if lib.is_file(): + yield "libs/" + n + ".lib", lib + + yield from in_build("python_uwp.exe", new_name="python") + yield from in_build("pythonw_uwp.exe", new_name="pythonw") + + yield from in_build(PYTHON_DLL_NAME) + + if ns.include_launchers: + if ns.include_pip: + yield from in_build("python_uwp.exe", new_name="pip") + if ns.include_idle: + yield from in_build("pythonw_uwp.exe", new_name="idle") + + if ns.include_stable: + yield from in_build(PYTHON_STABLE_DLL_NAME) + + for dest, src in rglob(ns.build, "vcruntime*.dll"): + yield dest, src + + for dest, src in rglob(ns.build, ("*.pyd", "*.dll")): + if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS: + continue + if src in EXCLUDE_FROM_PYDS: + continue + if src in TEST_PYDS_ONLY and not ns.include_tests: + continue + if src in TCLTK_PYDS_ONLY and not ns.include_tcltk: + continue + + yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/") + + if ns.zip_lib: + zip_name = PYTHON_ZIP_NAME + yield zip_name, ns.temp / zip_name + else: + for dest, src in get_lib_layout(ns): + yield "Lib/{}".format(dest), src + + if ns.include_venv: + yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python") + yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw") + + if ns.include_tools: + + def _c(d): + if d.is_dir(): + return d in TOOLS_DIRS + return d in TOOLS_FILES + + for dest, src in rglob(ns.source / "Tools", "**/*", _c): + yield "Tools/{}".format(dest), src + + if ns.include_underpth: + yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME + + if ns.include_dev: + + def _c(d): + if d.is_dir(): + return d.name != "internal" + return True + + for dest, src in rglob(ns.source / "Include", "**/*.h", _c): + yield "include/{}".format(dest), src + src = ns.source / "PC" / "pyconfig.h" + yield "include/pyconfig.h", src + + for dest, src in get_tcltk_lib(ns): + yield dest, src + + if ns.include_pip: + pip_dir = get_pip_dir(ns) + if not pip_dir.is_dir(): + log_warning("Failed to find {} - pip will not be included", pip_dir) + else: + pkg_root = "packages/{}" if ns.zip_lib else "Lib/site-packages/{}" + for dest, src in rglob(pip_dir, "**/*"): + if src in EXCLUDE_FROM_LIB or src in EXCLUDE_FROM_PACKAGED_LIB: + continue + yield pkg_root.format(dest), src + + if ns.include_chm: + for dest, src in rglob(ns.doc_build / "htmlhelp", PYTHON_CHM_NAME): + yield "Doc/{}".format(dest), src + + if ns.include_html_doc: + for dest, src in rglob(ns.doc_build / "html", "**/*"): + yield "Doc/html/{}".format(dest), src + + if ns.include_props: + for dest, src in get_props_layout(ns): + yield dest, src + + for dest, src in get_appx_layout(ns): + yield dest, src + + if ns.include_cat: + if ns.flat_dlls: + yield ns.include_cat.name, ns.include_cat + else: + yield "DLLs/{}".format(ns.include_cat.name), ns.include_cat + + +def _compile_one_py(src, dest, name, optimize): + import py_compile + + if dest is not None: + dest = str(dest) + + try: + return Path( + py_compile.compile( + str(src), + dest, + str(name), + doraise=True, + optimize=optimize, + invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, + ) + ) + except py_compile.PyCompileError: + log_warning("Failed to compile {}", src) + return None + + +def _py_temp_compile(src, ns, dest_dir=None): + if not ns.precompile or src not in PY_FILES or src.parent in DATA_DIRS: + return None + + dest = (dest_dir or ns.temp) / (src.stem + ".py") + return _compile_one_py(src, dest.with_suffix(".pyc"), dest, optimize=2) + + +def _write_to_zip(zf, dest, src, ns): + pyc = _py_temp_compile(src, ns) + if pyc: + try: + zf.write(str(pyc), dest.with_suffix(".pyc")) + finally: + try: + pyc.unlink() + except: + log_exception("Failed to delete {}", pyc) + return + + if src in LIB2TO3_GRAMMAR_FILES: + from lib2to3.pgen2.driver import load_grammar + + tmp = ns.temp / src.name + try: + shutil.copy(src, tmp) + load_grammar(str(tmp)) + for f in ns.temp.glob(src.stem + "*.pickle"): + zf.write(str(f), str(dest.parent / f.name)) + try: + f.unlink() + except: + log_exception("Failed to delete {}", f) + except: + log_exception("Failed to compile {}", src) + finally: + try: + tmp.unlink() + except: + log_exception("Failed to delete {}", tmp) + + zf.write(str(src), str(dest)) + + +def generate_source_files(ns): + if ns.zip_lib: + zip_name = PYTHON_ZIP_NAME + zip_path = ns.temp / zip_name + if zip_path.is_file(): + zip_path.unlink() + elif zip_path.is_dir(): + log_error( + "Cannot create zip file because a directory exists by the same name" + ) + return + log_info("Generating {} in {}", zip_name, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: + for dest, src in get_lib_layout(ns): + _write_to_zip(zf, dest, src, ns) + + if ns.include_underpth: + log_info("Generating {} in {}", PYTHON_PTH_NAME, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with open(ns.temp / PYTHON_PTH_NAME, "w", encoding="utf-8") as f: + if ns.zip_lib: + print(PYTHON_ZIP_NAME, file=f) + if ns.include_pip: + print("packages", file=f) + else: + print("Lib", file=f) + print("Lib/site-packages", file=f) + if not ns.flat_dlls: + print("DLLs", file=f) + print(".", file=f) + print(file=f) + print("# Uncomment to run site.main() automatically", file=f) + print("#import site", file=f) + + if ns.include_appxmanifest: + log_info("Generating AppxManifest.xml in {}", ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + + with open(ns.temp / "AppxManifest.xml", "wb") as f: + f.write(get_appxmanifest(ns)) + + with open(ns.temp / "_resources.xml", "wb") as f: + f.write(get_resources_xml(ns)) + + if ns.include_pip: + pip_dir = get_pip_dir(ns) + if not (pip_dir / "pip").is_dir(): + log_info("Extracting pip to {}", pip_dir) + pip_dir.mkdir(parents=True, exist_ok=True) + extract_pip_files(ns) + + if ns.include_props: + log_info("Generating {} in {}", PYTHON_PROPS_NAME, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with open(ns.temp / PYTHON_PROPS_NAME, "wb") as f: + f.write(get_props(ns)) + + +def _create_zip_file(ns): + if not ns.zip: + return None + + if ns.zip.is_file(): + try: + ns.zip.unlink() + except OSError: + log_exception("Unable to remove {}", ns.zip) + sys.exit(8) + elif ns.zip.is_dir(): + log_error("Cannot create ZIP file because {} is a directory", ns.zip) + sys.exit(8) + + ns.zip.parent.mkdir(parents=True, exist_ok=True) + return zipfile.ZipFile(ns.zip, "w", zipfile.ZIP_DEFLATED) + + +def copy_files(files, ns): + if ns.copy: + ns.copy.mkdir(parents=True, exist_ok=True) + + try: + total = len(files) + except TypeError: + total = None + count = 0 + + zip_file = _create_zip_file(ns) + try: + need_compile = [] + in_catalog = [] + + for dest, src in files: + count += 1 + if count % 10 == 0: + if total: + log_info("Processed {:>4} of {} files", count, total) + else: + log_info("Processed {} files", count) + log_debug("Processing {!s}", src) + + if ( + ns.precompile + and src in PY_FILES + and src not in EXCLUDE_FROM_COMPILE + and src.parent not in DATA_DIRS + and os.path.normcase(str(dest)).startswith(os.path.normcase("Lib")) + ): + if ns.copy: + need_compile.append((dest, ns.copy / dest)) + else: + (ns.temp / "Lib" / dest).parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, ns.temp / "Lib" / dest) + need_compile.append((dest, ns.temp / "Lib" / dest)) + + if src not in EXCLUDE_FROM_CATALOG: + in_catalog.append((src.name, src)) + + if ns.copy: + log_debug("Copy {} -> {}", src, ns.copy / dest) + (ns.copy / dest).parent.mkdir(parents=True, exist_ok=True) + try: + shutil.copy2(src, ns.copy / dest) + except shutil.SameFileError: + pass + + if ns.zip: + log_debug("Zip {} into {}", src, ns.zip) + zip_file.write(src, str(dest)) + + if need_compile: + for dest, src in need_compile: + compiled = [ + _compile_one_py(src, None, dest, optimize=0), + _compile_one_py(src, None, dest, optimize=1), + _compile_one_py(src, None, dest, optimize=2), + ] + for c in compiled: + if not c: + continue + cdest = Path(dest).parent / Path(c).relative_to(src.parent) + if ns.zip: + log_debug("Zip {} into {}", c, ns.zip) + zip_file.write(c, str(cdest)) + in_catalog.append((cdest.name, cdest)) + + if ns.catalog: + # Just write out the CDF now. Compilation and signing is + # an extra step + log_info("Generating {}", ns.catalog) + ns.catalog.parent.mkdir(parents=True, exist_ok=True) + write_catalog(ns.catalog, in_catalog) + + finally: + if zip_file: + zip_file.close() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-v", help="Increase verbosity", action="count") + parser.add_argument( + "-s", + "--source", + metavar="dir", + help="The directory containing the repository root", + type=Path, + default=None, + ) + parser.add_argument( + "-b", "--build", metavar="dir", help="Specify the build directory", type=Path + ) + parser.add_argument( + "--doc-build", + metavar="dir", + help="Specify the docs build directory", + type=Path, + default=None, + ) + parser.add_argument( + "--copy", + metavar="directory", + help="The name of the directory to copy an extracted layout to", + type=Path, + default=None, + ) + parser.add_argument( + "--zip", + metavar="file", + help="The ZIP file to write all files to", + type=Path, + default=None, + ) + parser.add_argument( + "--catalog", + metavar="file", + help="The CDF file to write catalog entries to", + type=Path, + default=None, + ) + parser.add_argument( + "--log", + metavar="file", + help="Write all operations to the specified file", + type=Path, + default=None, + ) + parser.add_argument( + "-t", + "--temp", + metavar="file", + help="A temporary working directory", + type=Path, + default=None, + ) + parser.add_argument( + "-d", "--debug", help="Include debug build", action="store_true" + ) + parser.add_argument( + "-p", + "--precompile", + help="Include .pyc files instead of .py", + action="store_true", + ) + parser.add_argument( + "-z", "--zip-lib", help="Include library in a ZIP file", action="store_true" + ) + parser.add_argument( + "--flat-dlls", help="Does not create a DLLs directory", action="store_true" + ) + parser.add_argument( + "-a", + "--include-all", + help="Include all optional components", + action="store_true", + ) + parser.add_argument( + "--include-cat", + metavar="file", + help="Specify the catalog file to include", + type=Path, + default=None, + ) + for opt, help in get_argparse_options(): + parser.add_argument(opt, help=help, action="store_true") + + ns = parser.parse_args() + update_presets(ns) + + ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent) + ns.build = ns.build or Path(sys.executable).parent + ns.temp = ns.temp or Path(tempfile.mkdtemp()) + ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build") + if not ns.source.is_absolute(): + ns.source = (Path.cwd() / ns.source).resolve() + if not ns.build.is_absolute(): + ns.build = (Path.cwd() / ns.build).resolve() + if not ns.temp.is_absolute(): + ns.temp = (Path.cwd() / ns.temp).resolve() + if not ns.doc_build.is_absolute(): + ns.doc_build = (Path.cwd() / ns.doc_build).resolve() + if ns.include_cat and not ns.include_cat.is_absolute(): + ns.include_cat = (Path.cwd() / ns.include_cat).resolve() + + if ns.copy and not ns.copy.is_absolute(): + ns.copy = (Path.cwd() / ns.copy).resolve() + if ns.zip and not ns.zip.is_absolute(): + ns.zip = (Path.cwd() / ns.zip).resolve() + if ns.catalog and not ns.catalog.is_absolute(): + ns.catalog = (Path.cwd() / ns.catalog).resolve() + + configure_logger(ns) + + log_info( + """OPTIONS +Source: {ns.source} +Build: {ns.build} +Temp: {ns.temp} + +Copy to: {ns.copy} +Zip to: {ns.zip} +Catalog: {ns.catalog}""", + ns=ns, + ) + + if ns.include_idle and not ns.include_tcltk: + log_warning("Assuming --include-tcltk to support --include-idle") + ns.include_tcltk = True + + try: + generate_source_files(ns) + files = list(get_layout(ns)) + copy_files(files, ns) + except KeyboardInterrupt: + log_info("Interrupted by Ctrl+C") + return 3 + except SystemExit: + raise + except: + log_exception("Unhandled error") + + if error_was_logged(): + log_error("Errors occurred.") + return 1 + + +if __name__ == "__main__": + sys.exit(int(main() or 0)) diff --git a/PC/layout/support/__init__.py b/PC/layout/support/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py new file mode 100644 index 000000000000..c5dda70c7ef8 --- /dev/null +++ b/PC/layout/support/appxmanifest.py @@ -0,0 +1,487 @@ +""" +File generation for APPX/MSIX manifests. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import collections +import ctypes +import io +import os +import sys + +from pathlib import Path, PureWindowsPath +from xml.etree import ElementTree as ET + +from .constants import * + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +APPX_DATA = dict( + Name="PythonSoftwareFoundation.Python.{}".format(VER_DOT), + Version="{}.{}.{}.0".format(VER_MAJOR, VER_MINOR, VER_FIELD3), + Publisher=os.getenv( + "APPX_DATA_PUBLISHER", "CN=4975D53F-AA7E-49A5-8B49-EA4FDC1BB66B" + ), + DisplayName="Python {}".format(VER_DOT), + Description="The Python {} runtime and console.".format(VER_DOT), + ProcessorArchitecture="x64" if IS_X64 else "x86", +) + +PYTHON_VE_DATA = dict( + DisplayName="Python {}".format(VER_DOT), + Description="Python interactive console", + Square150x150Logo="_resources/pythonx150.png", + Square44x44Logo="_resources/pythonx44.png", + BackgroundColor="transparent", +) + +PYTHONW_VE_DATA = dict( + DisplayName="Python {} (Windowed)".format(VER_DOT), + Description="Python windowed app launcher", + Square150x150Logo="_resources/pythonwx150.png", + Square44x44Logo="_resources/pythonwx44.png", + BackgroundColor="transparent", + AppListEntry="none", +) + +PIP_VE_DATA = dict( + DisplayName="pip (Python {})".format(VER_DOT), + Description="pip package manager for Python {}".format(VER_DOT), + Square150x150Logo="_resources/pythonx150.png", + Square44x44Logo="_resources/pythonx44.png", + BackgroundColor="transparent", + AppListEntry="none", +) + +IDLE_VE_DATA = dict( + DisplayName="IDLE (Python {})".format(VER_DOT), + Description="IDLE editor for Python {}".format(VER_DOT), + Square150x150Logo="_resources/pythonwx150.png", + Square44x44Logo="_resources/pythonwx44.png", + BackgroundColor="transparent", +) + +APPXMANIFEST_NS = { + "": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", + "m": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", + "uap": "http://schemas.microsoft.com/appx/manifest/uap/windows10", + "rescap": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities", + "rescap4": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4", + "desktop4": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/4", + "desktop6": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6", + "uap3": "http://schemas.microsoft.com/appx/manifest/uap/windows10/3", + "uap4": "http://schemas.microsoft.com/appx/manifest/uap/windows10/4", + "uap5": "http://schemas.microsoft.com/appx/manifest/uap/windows10/5", +} + +APPXMANIFEST_TEMPLATE = """ + + + + + Python Software Foundation + + _resources/pythonx50.png + + + + + + + + + + + + + + +""" + + +RESOURCES_XML_TEMPLATE = r""" + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + +SCCD_FILENAME = "PC/classicAppCompat.sccd" + +REGISTRY = { + "HKCU\\Software\\Python\\PythonCore": { + VER_DOT: { + "DisplayName": APPX_DATA["DisplayName"], + "SupportUrl": "https://www.python.org/", + "SysArchitecture": "64bit" if IS_X64 else "32bit", + "SysVersion": VER_DOT, + "Version": "{}.{}.{}".format(VER_MAJOR, VER_MINOR, VER_MICRO), + "InstallPath": { + # I have no idea why the trailing spaces are needed, but they seem to be needed. + "": "[{AppVPackageRoot}][ ]", + "ExecutablePath": "[{AppVPackageRoot}]python.exe[ ]", + "WindowedExecutablePath": "[{AppVPackageRoot}]pythonw.exe[ ]", + }, + "Help": { + "Main Python Documentation": { + "_condition": lambda ns: ns.include_chm, + "": "[{{AppVPackageRoot}}]Doc\\{}[ ]".format( + PYTHON_CHM_NAME + ), + }, + "Local Python Documentation": { + "_condition": lambda ns: ns.include_html_doc, + "": "[{AppVPackageRoot}]Doc\\html\\index.html[ ]", + }, + "Online Python Documentation": { + "": "https://docs.python.org/{}".format(VER_DOT) + }, + }, + "Idle": { + "_condition": lambda ns: ns.include_idle, + "": "[{AppVPackageRoot}]Lib\\idlelib\\idle.pyw[ ]", + }, + } + } +} + + +def get_packagefamilyname(name, publisher_id): + class PACKAGE_ID(ctypes.Structure): + _fields_ = [ + ("reserved", ctypes.c_uint32), + ("processorArchitecture", ctypes.c_uint32), + ("version", ctypes.c_uint64), + ("name", ctypes.c_wchar_p), + ("publisher", ctypes.c_wchar_p), + ("resourceId", ctypes.c_wchar_p), + ("publisherId", ctypes.c_wchar_p), + ] + _pack_ = 4 + + pid = PACKAGE_ID(0, 0, 0, name, publisher_id, None, None) + result = ctypes.create_unicode_buffer(256) + result_len = ctypes.c_uint32(256) + r = ctypes.windll.kernel32.PackageFamilyNameFromId( + pid, ctypes.byref(result_len), result + ) + if r: + raise OSError(r, "failed to get package family name") + return result.value[: result_len.value] + + +def _fixup_sccd(ns, sccd, new_hash=None): + if not new_hash: + return sccd + + NS = dict(s="http://schemas.microsoft.com/appx/2016/sccd") + with open(sccd, "rb") as f: + xml = ET.parse(f) + + pfn = get_packagefamilyname(APPX_DATA["Name"], APPX_DATA["Publisher"]) + + ae = xml.find("s:AuthorizedEntities", NS) + ae.clear() + + e = ET.SubElement(ae, ET.QName(NS["s"], "AuthorizedEntity")) + e.set("AppPackageFamilyName", pfn) + e.set("CertificateSignatureHash", new_hash) + + for e in xml.findall("s:Catalog", NS): + e.text = "FFFF" + + sccd = ns.temp / sccd.name + sccd.parent.mkdir(parents=True, exist_ok=True) + with open(sccd, "wb") as f: + xml.write(f, encoding="utf-8") + + return sccd + + + at public +def get_appx_layout(ns): + if not ns.include_appxmanifest: + return + + yield "AppxManifest.xml", ns.temp / "AppxManifest.xml" + yield "_resources.xml", ns.temp / "_resources.xml" + icons = ns.source / "PC" / "icons" + yield "_resources/pythonx44.png", icons / "pythonx44.png" + yield "_resources/pythonx44$targetsize-44_altform-unplated.png", icons / "pythonx44.png" + yield "_resources/pythonx50.png", icons / "pythonx50.png" + yield "_resources/pythonx50$targetsize-50_altform-unplated.png", icons / "pythonx50.png" + yield "_resources/pythonx150.png", icons / "pythonx150.png" + yield "_resources/pythonx150$targetsize-150_altform-unplated.png", icons / "pythonx150.png" + yield "_resources/pythonwx44.png", icons / "pythonwx44.png" + yield "_resources/pythonwx44$targetsize-44_altform-unplated.png", icons / "pythonwx44.png" + yield "_resources/pythonwx150.png", icons / "pythonwx150.png" + yield "_resources/pythonwx150$targetsize-150_altform-unplated.png", icons / "pythonwx150.png" + sccd = ns.source / SCCD_FILENAME + if sccd.is_file(): + # This should only be set for side-loading purposes. + sccd = _fixup_sccd(ns, sccd, os.getenv("APPX_DATA_SHA256")) + yield sccd.name, sccd + + +def find_or_add(xml, element, attr=None, always_add=False): + if always_add: + e = None + else: + q = element + if attr: + q += "[@{}='{}']".format(*attr) + e = xml.find(q, APPXMANIFEST_NS) + if e is None: + prefix, _, name = element.partition(":") + name = ET.QName(APPXMANIFEST_NS[prefix or ""], name) + e = ET.SubElement(xml, name) + if attr: + e.set(*attr) + return e + + +def _get_app(xml, appid): + if appid: + app = xml.find( + "m:Applications/m:Application[@Id='{}']".format(appid), APPXMANIFEST_NS + ) + if app is None: + raise LookupError(appid) + else: + app = xml + return app + + +def add_visual(xml, appid, data): + app = _get_app(xml, appid) + e = find_or_add(app, "uap:VisualElements") + for i in data.items(): + e.set(*i) + return e + + +def add_alias(xml, appid, alias, subsystem="windows"): + app = _get_app(xml, appid) + e = find_or_add(app, "m:Extensions") + e = find_or_add(e, "uap5:Extension", ("Category", "windows.appExecutionAlias")) + e = find_or_add(e, "uap5:AppExecutionAlias") + e.set(ET.QName(APPXMANIFEST_NS["desktop4"], "Subsystem"), subsystem) + e = find_or_add(e, "uap5:ExecutionAlias", ("Alias", alias)) + + +def add_file_type(xml, appid, name, suffix, parameters='"%1"'): + app = _get_app(xml, appid) + e = find_or_add(app, "m:Extensions") + e = find_or_add(e, "uap3:Extension", ("Category", "windows.fileTypeAssociation")) + e = find_or_add(e, "uap3:FileTypeAssociation", ("Name", name)) + e.set("Parameters", parameters) + e = find_or_add(e, "uap:SupportedFileTypes") + if isinstance(suffix, str): + suffix = [suffix] + for s in suffix: + ET.SubElement(e, ET.QName(APPXMANIFEST_NS["uap"], "FileType")).text = s + + +def add_application( + ns, xml, appid, executable, aliases, visual_element, subsystem, file_types +): + node = xml.find("m:Applications", APPXMANIFEST_NS) + suffix = "_d.exe" if ns.debug else ".exe" + app = ET.SubElement( + node, + ET.QName(APPXMANIFEST_NS[""], "Application"), + { + "Id": appid, + "Executable": executable + suffix, + "EntryPoint": "Windows.FullTrustApplication", + ET.QName(APPXMANIFEST_NS["desktop4"], "SupportsMultipleInstances"): "true", + }, + ) + if visual_element: + add_visual(app, None, visual_element) + for alias in aliases: + add_alias(app, None, alias + suffix, subsystem) + if file_types: + add_file_type(app, None, *file_types) + return app + + +def _get_registry_entries(ns, root="", d=None): + r = root if root else PureWindowsPath("") + if d is None: + d = REGISTRY + for key, value in d.items(): + if key == "_condition": + continue + elif isinstance(value, dict): + cond = value.get("_condition") + if cond and not cond(ns): + continue + fullkey = r + for part in PureWindowsPath(key).parts: + fullkey /= part + if len(fullkey.parts) > 1: + yield str(fullkey), None, None + yield from _get_registry_entries(ns, fullkey, value) + elif len(r.parts) > 1: + yield str(r), key, value + + +def add_registry_entries(ns, xml): + e = find_or_add(xml, "m:Extensions") + e = find_or_add(e, "rescap4:Extension") + e.set("Category", "windows.classicAppCompatKeys") + e.set("EntryPoint", "Windows.FullTrustApplication") + e = ET.SubElement(e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKeys")) + for name, valuename, value in _get_registry_entries(ns): + k = ET.SubElement( + e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKey") + ) + k.set("Name", name) + if value: + k.set("ValueName", valuename) + k.set("Value", value) + k.set("ValueType", "REG_SZ") + + +def disable_registry_virtualization(xml): + e = find_or_add(xml, "m:Properties") + e = find_or_add(e, "desktop6:RegistryWriteVirtualization") + e.text = "disabled" + e = find_or_add(xml, "m:Capabilities") + e = find_or_add(e, "rescap:Capability", ("Name", "unvirtualizedResources")) + + + at public +def get_appxmanifest(ns): + for k, v in APPXMANIFEST_NS.items(): + ET.register_namespace(k, v) + ET.register_namespace("", APPXMANIFEST_NS["m"]) + + xml = ET.parse(io.StringIO(APPXMANIFEST_TEMPLATE)) + NS = APPXMANIFEST_NS + QN = ET.QName + + node = xml.find("m:Identity", NS) + for k in node.keys(): + value = APPX_DATA.get(k) + if value: + node.set(k, value) + + for node in xml.find("m:Properties", NS): + value = APPX_DATA.get(node.tag.rpartition("}")[2]) + if value: + node.text = value + + winver = sys.getwindowsversion()[:3] + if winver < (10, 0, 17763): + winver = 10, 0, 17763 + find_or_add(xml, "m:Dependencies/m:TargetDeviceFamily").set( + "MaxVersionTested", "{}.{}.{}.0".format(*winver) + ) + + if winver > (10, 0, 17763): + disable_registry_virtualization(xml) + + app = add_application( + ns, + xml, + "Python", + "python", + ["python", "python{}".format(VER_MAJOR), "python{}".format(VER_DOT)], + PYTHON_VE_DATA, + "console", + ("python.file", [".py"]), + ) + + add_application( + ns, + xml, + "PythonW", + "pythonw", + ["pythonw", "pythonw{}".format(VER_MAJOR), "pythonw{}".format(VER_DOT)], + PYTHONW_VE_DATA, + "windows", + ("python.windowedfile", [".pyw"]), + ) + + if ns.include_pip and ns.include_launchers: + add_application( + ns, + xml, + "Pip", + "pip", + ["pip", "pip{}".format(VER_MAJOR), "pip{}".format(VER_DOT)], + PIP_VE_DATA, + "console", + ("python.wheel", [".whl"], 'install "%1"'), + ) + + if ns.include_idle and ns.include_launchers: + add_application( + ns, + xml, + "Idle", + "idle", + ["idle", "idle{}".format(VER_MAJOR), "idle{}".format(VER_DOT)], + IDLE_VE_DATA, + "windows", + None, + ) + + if (ns.source / SCCD_FILENAME).is_file(): + add_registry_entries(ns, xml) + node = xml.find("m:Capabilities", NS) + node = ET.SubElement(node, QN(NS["uap4"], "CustomCapability")) + node.set("Name", "Microsoft.classicAppCompat_8wekyb3d8bbwe") + + buffer = io.BytesIO() + xml.write(buffer, encoding="utf-8", xml_declaration=True) + return buffer.getbuffer() + + + at public +def get_resources_xml(ns): + return RESOURCES_XML_TEMPLATE.encode("utf-8") diff --git a/PC/layout/support/catalog.py b/PC/layout/support/catalog.py new file mode 100644 index 000000000000..43121187ed18 --- /dev/null +++ b/PC/layout/support/catalog.py @@ -0,0 +1,44 @@ +""" +File generation for catalog signing non-binary contents. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import sys + +__all__ = ["PYTHON_CAT_NAME", "PYTHON_CDF_NAME"] + + +def public(f): + __all__.append(f.__name__) + return f + + +PYTHON_CAT_NAME = "python.cat" +PYTHON_CDF_NAME = "python.cdf" + + +CATALOG_TEMPLATE = r"""[CatalogHeader] +Name={target.stem}.cat +ResultDir={target.parent} +PublicVersion=1 +CatalogVersion=2 +HashAlgorithms=SHA256 +PageHashes=false +EncodingType= + +[CatalogFiles] +""" + + +def can_sign(file): + return file.is_file() and file.stat().st_size + + + at public +def write_catalog(target, files): + with target.open("w", encoding="utf-8") as cat: + cat.write(CATALOG_TEMPLATE.format(target=target)) + cat.writelines("{}={}\n".format(n, f) for n, f in files if can_sign(f)) diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py new file mode 100644 index 000000000000..88ea410b340e --- /dev/null +++ b/PC/layout/support/constants.py @@ -0,0 +1,28 @@ +""" +Constants for generating the layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import struct +import sys + +VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = struct.pack(">i", sys.hexversion) +VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4 +VER_NAME = {"alpha": "a", "beta": "b", "rc": "rc"}.get( + sys.version_info.releaselevel, "" +) +VER_SERIAL = sys.version_info.serial if VER_NAME else "" +VER_DOT = "{}.{}".format(VER_MAJOR, VER_MINOR) + +PYTHON_DLL_NAME = "python{}{}.dll".format(VER_MAJOR, VER_MINOR) +PYTHON_STABLE_DLL_NAME = "python{}.dll".format(VER_MAJOR) +PYTHON_ZIP_NAME = "python{}{}.zip".format(VER_MAJOR, VER_MINOR) +PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR) + +PYTHON_CHM_NAME = "python{}{}{}{}{}.chm".format( + VER_MAJOR, VER_MINOR, VER_MICRO, VER_NAME, VER_SERIAL +) + +IS_X64 = sys.maxsize > 2 ** 32 diff --git a/PC/layout/support/distutils.command.bdist_wininst.py b/PC/layout/support/distutils.command.bdist_wininst.py new file mode 100644 index 000000000000..6e9b49fe42df --- /dev/null +++ b/PC/layout/support/distutils.command.bdist_wininst.py @@ -0,0 +1,25 @@ +"""distutils.command.bdist_wininst + +Suppress the 'bdist_wininst' command, while still allowing +setuptools to import it without breaking.""" + +from distutils.core import Command +from distutils.errors import DistutilsPlatformError + + +class bdist_wininst(Command): + description = "create an executable installer for MS Windows" + + # Marker for tests that we have the unsupported bdist_wininst + _unsupported = True + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + raise DistutilsPlatformError( + "bdist_wininst is not supported in this Python distribution" + ) diff --git a/PC/layout/support/filesets.py b/PC/layout/support/filesets.py new file mode 100644 index 000000000000..47f727c05784 --- /dev/null +++ b/PC/layout/support/filesets.py @@ -0,0 +1,100 @@ +""" +File sets and globbing helper for make_layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import os + + +class FileStemSet: + def __init__(self, *patterns): + self._names = set() + self._prefixes = [] + self._suffixes = [] + for p in map(os.path.normcase, patterns): + if p.endswith("*"): + self._prefixes.append(p[:-1]) + elif p.startswith("*"): + self._suffixes.append(p[1:]) + else: + self._names.add(p) + + def _make_name(self, f): + return os.path.normcase(f.stem) + + def __contains__(self, f): + bn = self._make_name(f) + return ( + bn in self._names + or any(map(bn.startswith, self._prefixes)) + or any(map(bn.endswith, self._suffixes)) + ) + + +class FileNameSet(FileStemSet): + def _make_name(self, f): + return os.path.normcase(f.name) + + +class FileSuffixSet: + def __init__(self, *patterns): + self._names = set() + self._prefixes = [] + self._suffixes = [] + for p in map(os.path.normcase, patterns): + if p.startswith("*."): + self._names.add(p[1:]) + elif p.startswith("*"): + self._suffixes.append(p[1:]) + elif p.endswith("*"): + self._prefixes.append(p[:-1]) + elif p.startswith("."): + self._names.add(p) + else: + self._names.add("." + p) + + def _make_name(self, f): + return os.path.normcase(f.suffix) + + def __contains__(self, f): + bn = self._make_name(f) + return ( + bn in self._names + or any(map(bn.startswith, self._prefixes)) + or any(map(bn.endswith, self._suffixes)) + ) + + +def _rglob(root, pattern, condition): + dirs = [root] + recurse = pattern[:3] in {"**/", "**\\"} + if recurse: + pattern = pattern[3:] + + while dirs: + d = dirs.pop(0) + if recurse: + dirs.extend( + filter( + condition, (type(root)(f2) for f2 in os.scandir(d) if f2.is_dir()) + ) + ) + yield from ( + (f.relative_to(root), f) + for f in d.glob(pattern) + if f.is_file() and condition(f) + ) + + +def _return_true(f): + return True + + +def rglob(root, patterns, condition=None): + if isinstance(patterns, tuple): + for p in patterns: + yield from _rglob(root, p, condition or _return_true) + else: + yield from _rglob(root, patterns, condition or _return_true) diff --git a/PC/layout/support/logging.py b/PC/layout/support/logging.py new file mode 100644 index 000000000000..30869b949a1c --- /dev/null +++ b/PC/layout/support/logging.py @@ -0,0 +1,93 @@ +""" +Logging support for make_layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import logging +import sys + +__all__ = [] + +LOG = None +HAS_ERROR = False + + +def public(f): + __all__.append(f.__name__) + return f + + + at public +def configure_logger(ns): + global LOG + if LOG: + return + + LOG = logging.getLogger("make_layout") + LOG.level = logging.DEBUG + + if ns.v: + s_level = max(logging.ERROR - ns.v * 10, logging.DEBUG) + f_level = max(logging.WARNING - ns.v * 10, logging.DEBUG) + else: + s_level = logging.ERROR + f_level = logging.INFO + + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter("{levelname:8s} {message}", style="{")) + handler.setLevel(s_level) + LOG.addHandler(handler) + + if ns.log: + handler = logging.FileHandler(ns.log, encoding="utf-8", delay=True) + handler.setFormatter( + logging.Formatter("[{asctime}]{levelname:8s}: {message}", style="{") + ) + handler.setLevel(f_level) + LOG.addHandler(handler) + + +class BraceMessage: + def __init__(self, fmt, *args, **kwargs): + self.fmt = fmt + self.args = args + self.kwargs = kwargs + + def __str__(self): + return self.fmt.format(*self.args, **self.kwargs) + + + at public +def log_debug(msg, *args, **kwargs): + return LOG.debug(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_info(msg, *args, **kwargs): + return LOG.info(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_warning(msg, *args, **kwargs): + return LOG.warning(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_error(msg, *args, **kwargs): + global HAS_ERROR + HAS_ERROR = True + return LOG.error(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_exception(msg, *args, **kwargs): + global HAS_ERROR + HAS_ERROR = True + return LOG.exception(BraceMessage(msg, *args, **kwargs)) + + + at public +def error_was_logged(): + return HAS_ERROR diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py new file mode 100644 index 000000000000..76d9e34e1f46 --- /dev/null +++ b/PC/layout/support/options.py @@ -0,0 +1,122 @@ +""" +List of optional components. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +OPTIONS = { + "stable": {"help": "stable ABI stub"}, + "pip": {"help": "pip"}, + "distutils": {"help": "distutils"}, + "tcltk": {"help": "Tcl, Tk and tkinter"}, + "idle": {"help": "Idle"}, + "tests": {"help": "test suite"}, + "tools": {"help": "tools"}, + "venv": {"help": "venv"}, + "dev": {"help": "headers and libs"}, + "symbols": {"help": "symbols"}, + "bdist-wininst": {"help": "bdist_wininst support"}, + "underpth": {"help": "a python._pth file", "not-in-all": True}, + "launchers": {"help": "specific launchers"}, + "appxmanifest": {"help": "an appxmanifest"}, + "props": {"help": "a python.props file"}, + "chm": {"help": "the CHM documentation"}, + "html-doc": {"help": "the HTML documentation"}, +} + + +PRESETS = { + "appx": { + "help": "APPX package", + "options": [ + "stable", + "pip", + "distutils", + "tcltk", + "idle", + "venv", + "dev", + "launchers", + "appxmanifest", + # XXX: Disabled for now "precompile", + ], + }, + "nuget": { + "help": "nuget package", + "options": ["stable", "pip", "distutils", "dev", "props"], + }, + "default": { + "help": "development kit package", + "options": [ + "stable", + "pip", + "distutils", + "tcltk", + "idle", + "tests", + "tools", + "venv", + "dev", + "symbols", + "bdist-wininst", + "chm", + ], + }, + "embed": { + "help": "embeddable package", + "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"], + }, +} + + + at public +def get_argparse_options(): + for opt, info in OPTIONS.items(): + help = "When specified, includes {}".format(info["help"]) + if info.get("not-in-all"): + help = "{}. Not affected by --include-all".format(help) + + yield "--include-{}".format(opt), help + + for opt, info in PRESETS.items(): + help = "When specified, includes default options for {}".format(info["help"]) + yield "--preset-{}".format(opt), help + + +def ns_get(ns, key, default=False): + return getattr(ns, key.replace("-", "_"), default) + + +def ns_set(ns, key, value=True): + k1 = key.replace("-", "_") + k2 = "include_{}".format(k1) + if hasattr(ns, k2): + setattr(ns, k2, value) + elif hasattr(ns, k1): + setattr(ns, k1, value) + else: + raise AttributeError("no argument named '{}'".format(k1)) + + + at public +def update_presets(ns): + for preset, info in PRESETS.items(): + if ns_get(ns, "preset-{}".format(preset)): + for opt in info["options"]: + ns_set(ns, opt) + + if ns.include_all: + for opt in OPTIONS: + if OPTIONS[opt].get("not-in-all"): + continue + ns_set(ns, opt) diff --git a/PC/layout/support/pip.py b/PC/layout/support/pip.py new file mode 100644 index 000000000000..369a923ce139 --- /dev/null +++ b/PC/layout/support/pip.py @@ -0,0 +1,79 @@ +""" +Extraction and file list generation for pip. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import os +import shutil +import subprocess +import sys + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + + at public +def get_pip_dir(ns): + if ns.copy: + if ns.zip_lib: + return ns.copy / "packages" + return ns.copy / "Lib" / "site-packages" + else: + return ns.temp / "packages" + + + at public +def extract_pip_files(ns): + dest = get_pip_dir(ns) + dest.mkdir(parents=True, exist_ok=True) + + src = ns.source / "Lib" / "ensurepip" / "_bundled" + + ns.temp.mkdir(parents=True, exist_ok=True) + wheels = [shutil.copy(whl, ns.temp) for whl in src.glob("*.whl")] + search_path = os.pathsep.join(wheels) + if os.environ.get("PYTHONPATH"): + search_path += ";" + os.environ["PYTHONPATH"] + + env = os.environ.copy() + env["PYTHONPATH"] = search_path + + output = subprocess.check_output( + [ + sys.executable, + "-m", + "pip", + "--no-color", + "install", + "pip", + "setuptools", + "--upgrade", + "--target", + str(dest), + "--no-index", + "--no-cache-dir", + "-f", + str(src), + "--only-binary", + ":all:", + ], + env=env, + ) + + try: + shutil.rmtree(dest / "bin") + except OSError: + pass + + for file in wheels: + try: + os.remove(file) + except OSError: + pass diff --git a/PC/layout/support/props.py b/PC/layout/support/props.py new file mode 100644 index 000000000000..3a047d215058 --- /dev/null +++ b/PC/layout/support/props.py @@ -0,0 +1,110 @@ +""" +Provides .props file. +""" + +import os + +from .constants import * + +__all__ = ["PYTHON_PROPS_NAME"] + + +def public(f): + __all__.append(f.__name__) + return f + + +PYTHON_PROPS_NAME = "python.props" + +PROPS_DATA = { + "PYTHON_TAG": VER_DOT, + "PYTHON_VERSION": os.getenv("PYTHON_NUSPEC_VERSION"), + "PYTHON_PLATFORM": os.getenv("PYTHON_PROPS_PLATFORM"), + "PYTHON_TARGET": "", +} + +if not PROPS_DATA["PYTHON_VERSION"]: + if VER_NAME: + PROPS_DATA["PYTHON_VERSION"] = "{}.{}-{}{}".format( + VER_DOT, VER_MICRO, VER_NAME, VER_SERIAL + ) + else: + PROPS_DATA["PYTHON_VERSION"] = "{}.{}".format(VER_DOT, VER_MICRO) + +if not PROPS_DATA["PYTHON_PLATFORM"]: + PROPS_DATA["PYTHON_PLATFORM"] = "x64" if IS_X64 else "Win32" + +PROPS_DATA["PYTHON_TARGET"] = "_GetPythonRuntimeFilesDependsOn{}{}_{}".format( + VER_MAJOR, VER_MINOR, PROPS_DATA["PYTHON_PLATFORM"] +) + +PROPS_TEMPLATE = r""" + + + $([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python_d.exe") + $([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python.exe") + $(PythonHome)\include + $(PythonHome)\libs + {PYTHON_TAG} + {PYTHON_VERSION} + + true + false + false + false + + {PYTHON_TARGET};$(GetPythonRuntimeFilesDependsOn) + + + + + $(PythonInclude);%(AdditionalIncludeDirectories) + MultiThreadedDLL + + + $(PythonLibs);%(AdditionalLibraryDirectories) + + + + + + + + <_PythonRuntimeExe Include="$(PythonHome)\python*.dll" /> + <_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" /> + <_PythonRuntimeExe> + %(Filename)%(Extension) + + <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" /> + <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" /> + <_PythonRuntimeDlls> + DLLs\%(Filename)%(Extension) + + <_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" /> + <_PythonRuntimeLib> + Lib\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + +""" + + + at public +def get_props_layout(ns): + if ns.include_all or ns.include_props: + yield "python.props", ns.temp / "python.props" + + + at public +def get_props(ns): + # TODO: Filter contents of props file according to included/excluded items + props = PROPS_TEMPLATE.format_map(PROPS_DATA) + return props.encode("utf-8") diff --git a/Tools/nuget/python.props b/PC/layout/support/python.props similarity index 100% rename from Tools/nuget/python.props rename to PC/layout/support/python.props diff --git a/PC/pylauncher.rc b/PC/pylauncher.rc index 3da3445f5fc4..92987af7138d 100644 --- a/PC/pylauncher.rc +++ b/PC/pylauncher.rc @@ -7,6 +7,11 @@ #include 1 RT_MANIFEST "python.manifest" +#if defined(PY_ICON) +1 ICON DISCARDABLE "icons\python.ico" +#elif defined(PYW_ICON) +1 ICON DISCARDABLE "icons\pythonw.ico" +#else 1 ICON DISCARDABLE "icons\launcher.ico" 2 ICON DISCARDABLE "icons\py.ico" 3 ICON DISCARDABLE "icons\pyc.ico" @@ -14,6 +19,7 @@ 5 ICON DISCARDABLE "icons\python.ico" 6 ICON DISCARDABLE "icons\pythonw.ico" 7 ICON DISCARDABLE "icons\setup.ico" +#endif ///////////////////////////////////////////////////////////////////////////// // diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp new file mode 100644 index 000000000000..1658d05994bb --- /dev/null +++ b/PC/python_uwp.cpp @@ -0,0 +1,226 @@ +/* Main program when embedded in a UWP application on Windows */ + +#include "Python.h" +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#ifdef PYTHONW +#ifdef _DEBUG +const wchar_t *PROGNAME = L"pythonw_d.exe"; +#else +const wchar_t *PROGNAME = L"pythonw.exe"; +#endif +#else +#ifdef _DEBUG +const wchar_t *PROGNAME = L"python_d.exe"; +#else +const wchar_t *PROGNAME = L"python.exe"; +#endif +#endif + +static void +set_user_base() +{ + wchar_t envBuffer[2048]; + try { + const auto appData = winrt::Windows::Storage::ApplicationData::Current(); + if (appData) { + const auto localCache = appData.LocalCacheFolder(); + if (localCache) { + auto path = localCache.Path(); + if (!path.empty() && + !wcscpy_s(envBuffer, path.c_str()) && + !wcscat_s(envBuffer, L"\\local-packages") + ) { + _wputenv_s(L"PYTHONUSERBASE", envBuffer); + } + } + } + } catch (...) { + } +} + +static const wchar_t * +get_argv0(const wchar_t *argv0) +{ + winrt::hstring installPath; + const wchar_t *launcherPath; + wchar_t *buffer; + size_t len; + + launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__"); + if (launcherPath && launcherPath[0]) { + len = wcslen(launcherPath) + 1; + buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); + if (!buffer) { + Py_FatalError("out of memory"); + return NULL; + } + if (wcscpy_s(buffer, len, launcherPath)) { + Py_FatalError("failed to copy to buffer"); + return NULL; + } + return buffer; + } + + try { + const auto package = winrt::Windows::ApplicationModel::Package::Current(); + if (package) { + const auto install = package.InstalledLocation(); + if (install) { + installPath = install.Path(); + } + } + } + catch (...) { + } + + if (!installPath.empty()) { + len = installPath.size() + wcslen(PROGNAME) + 2; + } else { + len = wcslen(argv0) + wcslen(PROGNAME) + 1; + } + + buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); + if (!buffer) { + Py_FatalError("out of memory"); + return NULL; + } + + if (!installPath.empty()) { + if (wcscpy_s(buffer, len, installPath.c_str())) { + Py_FatalError("failed to copy to buffer"); + return NULL; + } + if (wcscat_s(buffer, len, L"\\")) { + Py_FatalError("failed to concatenate backslash"); + return NULL; + } + } else { + if (wcscpy_s(buffer, len, argv0)) { + Py_FatalError("failed to copy argv[0]"); + return NULL; + } + + wchar_t *name = wcsrchr(buffer, L'\\'); + if (name) { + name[1] = L'\0'; + } else { + buffer[0] = L'\0'; + } + } + + if (wcscat_s(buffer, len, PROGNAME)) { + Py_FatalError("failed to concatenate program name"); + return NULL; + } + + return buffer; +} + +static wchar_t * +get_process_name() +{ + DWORD bufferLen = MAX_PATH; + DWORD len = bufferLen; + wchar_t *r = NULL; + + while (!r) { + r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); + if (!r) { + Py_FatalError("out of memory"); + return NULL; + } + len = GetModuleFileNameW(NULL, r, bufferLen); + if (len == 0) { + free((void *)r); + return NULL; + } else if (len == bufferLen && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(r); + r = NULL; + bufferLen *= 2; + } + } + + return r; +} + +int +wmain(int argc, wchar_t **argv) +{ + const wchar_t **new_argv; + int new_argc; + const wchar_t *exeName; + + new_argc = argc; + new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2)); + if (new_argv == NULL) { + Py_FatalError("out of memory"); + return -1; + } + + exeName = get_process_name(); + + new_argv[0] = get_argv0(exeName ? exeName : argv[0]); + for (int i = 1; i < argc; ++i) { + new_argv[i] = argv[i]; + } + + set_user_base(); + + if (exeName) { + const wchar_t *p = wcsrchr(exeName, L'\\'); + if (p) { + const wchar_t *moduleName = NULL; + if (*p++ == L'\\') { + if (wcsnicmp(p, L"pip", 3) == 0) { + moduleName = L"pip"; + _wputenv_s(L"PIP_USER", L"true"); + } + else if (wcsnicmp(p, L"idle", 4) == 0) { + moduleName = L"idlelib"; + } + } + + if (moduleName) { + new_argc += 2; + for (int i = argc; i >= 1; --i) { + new_argv[i + 2] = new_argv[i]; + } + new_argv[1] = L"-m"; + new_argv[2] = moduleName; + } + } + } + + /* Override program_full_path from here so that + sys.executable is set correctly. */ + _Py_SetProgramFullPath(new_argv[0]); + + int result = Py_Main(new_argc, (wchar_t **)new_argv); + + free((void *)exeName); + free((void *)new_argv); + + return result; +} + +#ifdef PYTHONW + +int WINAPI wWinMain( + HINSTANCE hInstance, /* handle to current instance */ + HINSTANCE hPrevInstance, /* handle to previous instance */ + LPWSTR lpCmdLine, /* pointer to command line */ + int nCmdShow /* show state of window */ +) +{ + return wmain(__argc, __wargv); +} + +#endif diff --git a/PC/store_info.txt b/PC/store_info.txt new file mode 100644 index 000000000000..ed40a918e2e7 --- /dev/null +++ b/PC/store_info.txt @@ -0,0 +1,146 @@ +# Overview + +NOTE: This file requires more content. + +Since Python 3.8.2, releases have been made through the Microsoft Store +to allow easy installation on Windows 10.0.17763.0 and later. + +# Building + +To build the store package, the PC/layout script should be used. +Execute the directory with the build of Python to package, and pass +"-h" for full command-line options. + +To sideload test builds, you will need a local certificate. +Instructions are available at +https://docs.microsoft.com/windows/uwp/packaging/create-certificate-package-signing. + +After exporting your certificate, you will need the subject name and +SHA256 hash. The `certutil -dump ` command will display this +information. + +To build for sideloading, use these commands in PowerShell: + +``` +$env:APPX_DATA_PUBLISHER= +$env:APPX_DATA_SHA256= +$env:SigningCertificateFile= + +python PC/layout --copy --include-appxmanifest +Tools/msi/make_appx.ps1 python.msix -sign + +Add-AppxPackage python.msix +``` + +(Note that only the last command requires PowerShell, and the others +can be used from Command Prompt. You can also double-click to install +the final package.) + +To build for publishing to the Store, use these commands: + +``` +$env:APPX_DATA_PUBLISHER = $null +$env:APPX_DATA_SHA256 = $null + +python PC/layout --copy --preset-appxmanifest --precompile +Tools/msi/make_appx.ps1 python.msix +``` + +Note that this package cannot be installed locally. It may only be +added to a submission for the store. + + +# Submission Metadata + +This file contains the text that we use to fill out the store listing +for the Microsoft Store. It needs to be entered manually when creating +a new submission via the dashboard at +https://partner.microsoft.com/dashboard. + +We keep it here for convenience and to allow it to be updated via pull +requests. + +## Title + +Python 3.8 + +## Short Title + +Python + +## Description + +Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python?s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms. + +The Python interpreter and the extensive standard library are freely available in source or binary form for all major platforms from the Python Web site, https://www.python.org/, and may be freely distributed. The same site also contains distributions of and pointers to many free third party Python modules, programs and tools, and additional documentation. + +The Python interpreter is easily extended with new functions and data types implemented in C or C++ (or other languages callable from C). Python is also suitable as an extension language for customizable applications. + +## ShortDescription + +The Python 3.8 interpreter and runtime. + +## Copyright Trademark Information + +(c) Python Software Foundation + +## Additional License Terms + +Visit https://docs.python.org/3.8/license.html for latest license terms. + +PSF LICENSE AGREEMENT FOR PYTHON 3.8 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + the Individual or Organization ("Licensee") accessing and otherwise using Python + 3.8 software in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby + grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + analyze, test, perform and/or display publicly, prepare derivative works, + distribute, and otherwise use Python 3.8 alone or in any derivative + version, provided, however, that PSF's License Agreement and PSF's notice of + copyright, i.e., "Copyright ? 2001-2018 Python Software Foundation; All Rights + Reserved" are retained in Python 3.8 alone or in any derivative version + prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on or + incorporates Python 3.8 or any part thereof, and wants to make the + derivative work available to others as provided herein, then Licensee hereby + agrees to include in any such work a brief summary of the changes made to Python + 3.8. + +4. PSF is making Python 3.8 available to Licensee on an "AS IS" basis. + PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF + EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR + WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE + USE OF PYTHON 3.8 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.8 + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.8, OR ANY DERIVATIVE + THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material breach of + its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any relationship + of agency, partnership, or joint venture between PSF and Licensee. This License + Agreement does not grant permission to use PSF trademarks or trade name in a + trademark sense to endorse or promote products or services of Licensee, or any + third party. + +8. By copying, installing or otherwise using Python 3.8, Licensee agrees + to be bound by the terms and conditions of this License Agreement. + +## Features + +* Easy to install Python runtime +* Supported by core CPython team +* Find Python, Pip and Idle on PATH + +## Search Terms + +* Python +* Scripting +* Interpreter + diff --git a/PCbuild/_tkinter.vcxproj b/PCbuild/_tkinter.vcxproj index 95e3cd50eca5..bd61c0d4f689 100644 --- a/PCbuild/_tkinter.vcxproj +++ b/PCbuild/_tkinter.vcxproj @@ -95,4 +95,10 @@ + + + + + + \ No newline at end of file diff --git a/PCbuild/find_msbuild.bat b/PCbuild/find_msbuild.bat index 57512a01927e..a2810f09c45e 100644 --- a/PCbuild/find_msbuild.bat +++ b/PCbuild/find_msbuild.bat @@ -29,6 +29,16 @@ @where msbuild > "%TEMP%\msbuild.loc" 2> nul && set /P MSBUILD= < "%TEMP%\msbuild.loc" & del "%TEMP%\msbuild.loc" @if exist "%MSBUILD%" set MSBUILD="%MSBUILD%" & (set _Py_MSBuild_Source=PATH) & goto :found + at rem VS 2017 and later provide vswhere.exe, which can be used + at if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto :skip_vswhere + at set _Py_MSBuild_Root= + at for /F "tokens=*" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -property installationPath -latest') DO @(set _Py_MSBuild_Root=%%i\MSBuild) + at if not defined _Py_MSBuild_Root goto :skip_vswhere + at for %%j in (Current 15.0) DO @if exist "%_Py_MSBuild_Root%\%%j\Bin\msbuild.exe" (set MSBUILD="%_Py_MSBuild_Root%\%%j\Bin\msbuild.exe") + at set _Py_MSBuild_Root= + at if defined MSBUILD @if exist %MSBUILD% (set _Py_MSBuild_Source=Visual Studio installation) & goto :found +:skip_vswhere + @rem VS 2017 sets exactly one install as the "main" install, so we may find MSBuild in there. @reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v 15.0 /reg:32 >nul 2>nul @if NOT ERRORLEVEL 1 @for /F "tokens=1,2*" %%i in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v 15.0 /reg:32') DO @( diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj index 9e103e12103f..6bf1667e39f8 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -52,6 +52,8 @@ + + @@ -70,6 +72,7 @@ + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index 59b3861ed406..c212d9f8f32c 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -93,6 +93,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_queue", "_queue.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "liblzma.vcxproj", "{12728250-16EC-4DC6-94D7-E21DD88947F8}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python_uwp", "python_uwp.vcxproj", "{9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "venvlauncher", "venvlauncher.vcxproj", "{494BAC80-A60C-43A9-99E7-ACB691CE2C4D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "venvwlauncher", "venvwlauncher.vcxproj", "{FDB84CBB-2FB6-47C8-A2D6-091E0833239D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonw_uwp", "pythonw_uwp.vcxproj", "{AB603547-1E2A-45B3-9E09-B04596006393}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -693,6 +701,70 @@ Global {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.Build.0 = Release|Win32 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.ActiveCfg = Release|x64 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.Build.0 = Release|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|Win32.ActiveCfg = Debug|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|Win32.Build.0 = Debug|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|x64.ActiveCfg = Debug|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|x64.Build.0 = Debug|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|Win32.ActiveCfg = Release|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|Win32.Build.0 = Release|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|x64.ActiveCfg = Release|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|x64.Build.0 = Release|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|Win32.ActiveCfg = Debug|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|Win32.Build.0 = Debug|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|x64.ActiveCfg = Debug|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|x64.Build.0 = Debug|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|Win32.ActiveCfg = Release|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|Win32.Build.0 = Release|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|x64.ActiveCfg = Release|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|x64.Build.0 = Release|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|Win32.ActiveCfg = Debug|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|Win32.Build.0 = Debug|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|x64.ActiveCfg = Debug|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|x64.Build.0 = Debug|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|Win32.ActiveCfg = Release|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|Win32.Build.0 = Release|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|x64.ActiveCfg = Release|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|x64.Build.0 = Release|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|Win32.ActiveCfg = Debug|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|Win32.Build.0 = Debug|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|x64.ActiveCfg = Debug|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|x64.Build.0 = Debug|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|Win32.ActiveCfg = Release|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|Win32.Build.0 = Release|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|x64.ActiveCfg = Release|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PCbuild/python.props b/PCbuild/python.props index 09f11d3bba8c..6dbb503b3243 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -77,7 +77,8 @@ --> <_RegistryVersion>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0 at ProductVersion) <_RegistryVersion Condition="$(_RegistryVersion) == ''">$(Registry:HKEY_LOCAL_MACHINE\WOW6432Node\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0 at ProductVersion) - 10.0.17134.0 + 10.0.17763.0 + 10.0.17134.0 10.0.16299.0 10.0.15063.0 10.0.14393.0 diff --git a/PCbuild/python_uwp.vcxproj b/PCbuild/python_uwp.vcxproj new file mode 100644 index 000000000000..af187dd4df30 --- /dev/null +++ b/PCbuild/python_uwp.vcxproj @@ -0,0 +1,86 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF} + + + + + Application + false + Unicode + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + %(PreprocessorDefinitions) + /EHsc /std:c++17 %(AdditionalOptions) + + + windowsapp.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + + + + diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 4ae2d692eee1..78ec9a16efa7 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -479,4 +479,19 @@ + + + $(VCInstallDir)\Redist\MSVC\$(VCToolsRedistVersion)\ + $(VCRedistDir)x86\ + $(VCRedistDir)$(Platform)\ + + + + + + + + + + diff --git a/PCbuild/pythonw_uwp.vcxproj b/PCbuild/pythonw_uwp.vcxproj new file mode 100644 index 000000000000..79e105877fbe --- /dev/null +++ b/PCbuild/pythonw_uwp.vcxproj @@ -0,0 +1,86 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {AB603547-1E2A-45B3-9E09-B04596006393} + + + + + Application + false + Unicode + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + PYTHONW;%(PreprocessorDefinitions) + /EHsc /std:c++17 %(AdditionalOptions) + + + windowsapp.lib;%(AdditionalDependencies) + Windows + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + + + + diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj new file mode 100644 index 000000000000..295b36304733 --- /dev/null +++ b/PCbuild/venvlauncher.vcxproj @@ -0,0 +1,85 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D} + venvlauncher + venvlauncher + false + + + + + Application + MultiByte + + + + + + ClCompile + + + + + + + + + _CONSOLE;VENV_REDIRECT;%(PreprocessorDefinitions) + MultiThreaded + + + PY_ICON;%(PreprocessorDefinitions) + + + version.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + + diff --git a/PCbuild/venvwlauncher.vcxproj b/PCbuild/venvwlauncher.vcxproj new file mode 100644 index 000000000000..e7ba25da41eb --- /dev/null +++ b/PCbuild/venvwlauncher.vcxproj @@ -0,0 +1,85 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D} + venvwlauncher + venvwlauncher + false + + + + + Application + MultiByte + + + + + + ClCompile + + + + + + + + + _WINDOWS;VENV_REDIRECT;%(PreprocessorDefinitions) + MultiThreaded + + + PYW_ICON;%(PreprocessorDefinitions) + + + version.lib;%(AdditionalDependencies) + Windows + + + + + + + + + + + + + + + diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat index 4178981195ee..45e189b537f6 100644 --- a/Tools/msi/buildrelease.bat +++ b/Tools/msi/buildrelease.bat @@ -37,6 +37,7 @@ set BUILDX64= set TARGET=Rebuild set TESTTARGETDIR= set PGO=-m test -q --pgo +set BUILDMSI=1 set BUILDNUGET=1 set BUILDZIP=1 @@ -61,6 +62,7 @@ if "%1" EQU "--pgo" (set PGO=%~2) && shift && shift && goto CheckOpts if "%1" EQU "--skip-pgo" (set PGO=) && shift && goto CheckOpts if "%1" EQU "--skip-nuget" (set BUILDNUGET=) && shift && goto CheckOpts if "%1" EQU "--skip-zip" (set BUILDZIP=) && shift && goto CheckOpts +if "%1" EQU "--skip-msi" (set BUILDMSI=) && shift && goto CheckOpts if "%1" NEQ "" echo Invalid option: "%1" && exit /B 1 @@ -174,10 +176,12 @@ if "%OUTDIR_PLAT%" EQU "win32" ( ) set BUILDOPTS=/p:Platform=%1 /p:BuildForRelease=true /p:DownloadUrl=%DOWNLOAD_URL% /p:DownloadUrlBase=%DOWNLOAD_URL_BASE% /p:ReleaseUri=%RELEASE_URI% -%MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true -if errorlevel 1 exit /B -%MSBUILD% "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false -if errorlevel 1 exit /B +if defined BUILDMSI ( + %MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true + if errorlevel 1 exit /B + %MSBUILD% "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false + if errorlevel 1 exit /B +) if defined BUILDZIP ( %MSBUILD% "%D%make_zip.proj" /t:Build %BUILDOPTS% %CERTOPTS% /p:OutputPath="%BUILD%en-us" @@ -214,6 +218,7 @@ echo --skip-build (-B) Do not build Python (just do the installers) echo --skip-doc (-D) Do not build documentation echo --pgo Specify PGO command for x64 installers echo --skip-pgo Build x64 installers without using PGO +echo --skip-msi Do not build executable/MSI packages echo --skip-nuget Do not build Nuget packages echo --skip-zip Do not build embeddable package echo --download Specify the full download URL for MSIs diff --git a/Tools/msi/make_appx.ps1 b/Tools/msi/make_appx.ps1 new file mode 100644 index 000000000000..b3f190e07db8 --- /dev/null +++ b/Tools/msi/make_appx.ps1 @@ -0,0 +1,71 @@ +<# +.Synopsis + Compiles and signs an APPX package +.Description + Given the file listing, ensures all the contents are signed + and builds and signs the final package. +.Parameter mapfile + The location on disk of the text mapping file. +.Parameter msix + The path and name to store the APPX/MSIX. +.Parameter sign + When set, signs the APPX/MSIX. Packages to be published to + the store should not be signed. +.Parameter description + Description to embed in the signature (optional). +.Parameter certname + The name of the certificate to sign with (optional). +.Parameter certsha1 + The SHA1 hash of the certificate to sign with (optional). +#> +param( + [Parameter(Mandatory=$true)][string]$layout, + [Parameter(Mandatory=$true)][string]$msix, + [switch]$sign, + [string]$description, + [string]$certname, + [string]$certsha1, + [string]$certfile +) + +$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; +Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force + +Set-Alias makeappx (Find-Tool "makeappx.exe") -Scope Script +Set-Alias makepri (Find-Tool "makepri.exe") -Scope Script + +$msixdir = Split-Path $msix -Parent +if ($msixdir) { + $msixdir = (mkdir -Force $msixdir).FullName +} else { + $msixdir = Get-Location +} +$msix = Join-Path $msixdir (Split-Path $msix -Leaf) + +pushd $layout +try { + if (Test-Path resources.pri) { + del resources.pri + } + $name = ([xml](gc AppxManifest.xml)).Package.Identity.Name + makepri new /pr . /mn AppxManifest.xml /in $name /cf _resources.xml /of _resources.pri /mf appx /o + if (-not $? -or -not (Test-Path _resources.map.txt)) { + throw "makepri step failed" + } + $lines = gc _resources.map.txt + $lines | ?{ -not ($_ -match '"_resources[\w\.]+?"') } | Out-File _resources.map.txt -Encoding utf8 + makeappx pack /f _resources.map.txt /m AppxManifest.xml /o /p $msix + if (-not $?) { + throw "makeappx step failed" + } +} finally { + popd +} + +if ($sign) { + Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files $msix + + if (-not $?) { + throw "Package signing failed" + } +} diff --git a/Tools/msi/make_cat.ps1 b/Tools/msi/make_cat.ps1 new file mode 100644 index 000000000000..70741439869a --- /dev/null +++ b/Tools/msi/make_cat.ps1 @@ -0,0 +1,34 @@ +<# +.Synopsis + Compiles and signs a catalog file. +.Description + Given the CDF definition file, builds and signs a catalog. +.Parameter catalog + The path to the catalog definition file to compile and + sign. It is assumed that the .cat file will be the same + name with a new extension. +.Parameter description + The description to add to the signature (optional). +.Parameter certname + The name of the certificate to sign with (optional). +.Parameter certsha1 + The SHA1 hash of the certificate to sign with (optional). +#> +param( + [Parameter(Mandatory=$true)][string]$catalog, + [string]$description, + [string]$certname, + [string]$certsha1, + [string]$certfile +) + +$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; +Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force + +Set-Alias MakeCat (Find-Tool "makecat.exe") -Scope Script + +MakeCat $catalog +if (-not $?) { + throw "Catalog compilation failed" +} +Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files @($catalog -replace 'cdf$', 'cat') diff --git a/Tools/msi/make_zip.proj b/Tools/msi/make_zip.proj index 214111734219..125a434e51f4 100644 --- a/Tools/msi/make_zip.proj +++ b/Tools/msi/make_zip.proj @@ -15,11 +15,12 @@ .zip $(OutputPath)\$(TargetName)$(TargetExt) rmdir /q/s "$(IntermediateOutputPath)\zip_$(ArchName)" - "$(PythonExe)" "$(MSBuildThisFileDirectory)\make_zip.py" - $(Arguments) -e -o "$(TargetPath)" -t "$(IntermediateOutputPath)\zip_$(ArchName)" -b "$(BuildPath.TrimEnd(`\`))" - set DOC_FILENAME=python$(PythonVersion).chm + "$(PythonExe)" "$(PySourcePath)PC\layout" + $(Arguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" + $(Arguments) -t "$(IntermediateOutputPath)\zip_$(ArchName)" + $(Arguments) --zip "$(TargetPath)" + $(Arguments) --precompile --zip-lib --include-underpth --include-stable --flat-dlls $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - $(Environment)%0D%0Aset VCREDIST_PATH=$(CRTRedist)\$(Platform) diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py deleted file mode 100644 index 58f3b15ef852..000000000000 --- a/Tools/msi/make_zip.py +++ /dev/null @@ -1,250 +0,0 @@ -import argparse -import py_compile -import re -import sys -import shutil -import stat -import os -import tempfile - -from itertools import chain -from pathlib import Path -from zipfile import ZipFile, ZIP_DEFLATED - - -TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE) -DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe|pdb|lib)$', re.IGNORECASE) -PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE) - -DEBUG_FILES = { - '_ctypes_test', - '_testbuffer', - '_testcapi', - '_testconsole', - '_testimportmultiple', - '_testmultiphase', - 'xxlimited', - 'python3_dstub', -} - -EXCLUDE_FROM_LIBRARY = { - '__pycache__', - 'idlelib', - 'pydoc_data', - 'site-packages', - 'tkinter', - 'turtledemo', -} - -EXCLUDE_FROM_EMBEDDABLE_LIBRARY = { - 'ensurepip', - 'venv', -} - -EXCLUDE_FILE_FROM_LIBRARY = { - 'bdist_wininst.py', -} - -EXCLUDE_FILE_FROM_LIBS = { - 'liblzma', - 'python3stub', -} - -EXCLUDED_FILES = { - 'pyshellext', -} - -def is_not_debug(p): - if DEBUG_RE.search(p.name): - return False - - if TKTCL_RE.search(p.name): - return False - - return p.stem.lower() not in DEBUG_FILES and p.stem.lower() not in EXCLUDED_FILES - -def is_not_debug_or_python(p): - return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name) - -def include_in_lib(p): - name = p.name.lower() - if p.is_dir(): - if name in EXCLUDE_FROM_LIBRARY: - return False - if name == 'test' and p.parts[-2].lower() == 'lib': - return False - if name in {'test', 'tests'} and p.parts[-3].lower() == 'lib': - return False - return True - - if name in EXCLUDE_FILE_FROM_LIBRARY: - return False - - suffix = p.suffix.lower() - return suffix not in {'.pyc', '.pyo', '.exe'} - -def include_in_embeddable_lib(p): - if p.is_dir() and p.name.lower() in EXCLUDE_FROM_EMBEDDABLE_LIBRARY: - return False - - return include_in_lib(p) - -def include_in_libs(p): - if not is_not_debug(p): - return False - - return p.stem.lower() not in EXCLUDE_FILE_FROM_LIBS - -def include_in_tools(p): - if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}: - return True - - return p.suffix.lower() in {'.py', '.pyw', '.txt'} - -BASE_NAME = 'python{0.major}{0.minor}'.format(sys.version_info) - -FULL_LAYOUT = [ - ('/', '$build', 'python.exe', is_not_debug), - ('/', '$build', 'pythonw.exe', is_not_debug), - ('/', '$build', 'python{}.dll'.format(sys.version_info.major), is_not_debug), - ('/', '$build', '{}.dll'.format(BASE_NAME), is_not_debug), - ('DLLs/', '$build', '*.pyd', is_not_debug), - ('DLLs/', '$build', '*.dll', is_not_debug_or_python), - ('include/', 'include', '*.h', None), - ('include/', 'PC', 'pyconfig.h', None), - ('Lib/', 'Lib', '**/*', include_in_lib), - ('libs/', '$build', '*.lib', include_in_libs), - ('Tools/', 'Tools', '**/*', include_in_tools), -] - -EMBED_LAYOUT = [ - ('/', '$build', 'python*.exe', is_not_debug), - ('/', '$build', '*.pyd', is_not_debug), - ('/', '$build', '*.dll', is_not_debug), - ('{}.zip'.format(BASE_NAME), 'Lib', '**/*', include_in_embeddable_lib), -] - -if os.getenv('DOC_FILENAME'): - FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) -if os.getenv('VCREDIST_PATH'): - FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) - EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) - -def copy_to_layout(target, rel_sources): - count = 0 - - if target.suffix.lower() == '.zip': - if target.exists(): - target.unlink() - - with ZipFile(str(target), 'w', ZIP_DEFLATED) as f: - with tempfile.TemporaryDirectory() as tmpdir: - for s, rel in rel_sources: - if rel.suffix.lower() == '.py': - pyc = Path(tmpdir) / rel.with_suffix('.pyc').name - try: - py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2) - except py_compile.PyCompileError: - f.write(str(s), str(rel)) - else: - f.write(str(pyc), str(rel.with_suffix('.pyc'))) - else: - f.write(str(s), str(rel)) - count += 1 - - else: - for s, rel in rel_sources: - dest = target / rel - try: - dest.parent.mkdir(parents=True) - except FileExistsError: - pass - if dest.is_file(): - dest.chmod(stat.S_IWRITE) - shutil.copy(str(s), str(dest)) - if dest.is_file(): - dest.chmod(stat.S_IWRITE) - count += 1 - - return count - -def rglob(root, pattern, condition): - dirs = [root] - recurse = pattern[:3] in {'**/', '**\\'} - while dirs: - d = dirs.pop(0) - for f in d.glob(pattern[3:] if recurse else pattern): - if recurse and f.is_dir() and (not condition or condition(f)): - dirs.append(f) - elif f.is_file() and (not condition or condition(f)): - yield f, f.relative_to(root) - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path) - parser.add_argument('-o', '--out', metavar='file', help='The name of the output archive', type=Path, default=None) - parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None) - parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False) - parser.add_argument('-b', '--build', help='Specify the build directory', type=Path, default=None) - ns = parser.parse_args() - - source = ns.source or (Path(__file__).resolve().parent.parent.parent) - out = ns.out - build = ns.build or Path(sys.exec_prefix) - assert isinstance(source, Path) - assert not out or isinstance(out, Path) - assert isinstance(build, Path) - - if ns.temp: - temp = ns.temp - delete_temp = False - else: - temp = Path(tempfile.mkdtemp()) - delete_temp = True - - if out: - try: - out.parent.mkdir(parents=True) - except FileExistsError: - pass - try: - temp.mkdir(parents=True) - except FileExistsError: - pass - - layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT - - try: - for t, s, p, c in layout: - if s == '$build': - fs = build - else: - fs = source / s - files = rglob(fs, p, c) - extra_files = [] - if s == 'Lib' and p == '**/*': - extra_files.append(( - source / 'tools' / 'msi' / 'distutils.command.bdist_wininst.py', - Path('distutils') / 'command' / 'bdist_wininst.py' - )) - copied = copy_to_layout(temp / t.rstrip('/'), chain(files, extra_files)) - print('Copied {} files'.format(copied)) - - if ns.embed: - with open(str(temp / (BASE_NAME + '._pth')), 'w') as f: - print(BASE_NAME + '.zip', file=f) - print('.', file=f) - print('', file=f) - print('# Uncomment to run site.main() automatically', file=f) - print('#import site', file=f) - - if out: - total = copy_to_layout(out, rglob(temp, '**/*', None)) - print('Wrote {} files to {}'.format(total, out)) - finally: - if delete_temp: - shutil.rmtree(temp, True) - - -if __name__ == "__main__": - sys.exit(int(main() or 0)) diff --git a/Tools/msi/sdktools.psm1 b/Tools/msi/sdktools.psm1 new file mode 100644 index 000000000000..81a74d3679d7 --- /dev/null +++ b/Tools/msi/sdktools.psm1 @@ -0,0 +1,43 @@ +function Find-Tool { + param([string]$toolname) + + $kitroot = (gp 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots\').KitsRoot10 + $tool = (gci -r "$kitroot\Bin\*\x64\$toolname" | sort FullName -Desc | select -First 1) + if (-not $tool) { + throw "$toolname is not available" + } + Write-Host "Found $toolname at $($tool.FullName)" + return $tool.FullName +} + +Set-Alias SignTool (Find-Tool "signtool.exe") -Scope Script + +function Sign-File { + param([string]$certname, [string]$certsha1, [string]$certfile, [string]$description, [string[]]$files) + + if (-not $description) { + $description = $env:SigningDescription; + if (-not $description) { + $description = "Python"; + } + } + if (-not $certname) { + $certname = $env:SigningCertificate; + } + if (-not $certfile) { + $certfile = $env:SigningCertificateFile; + } + + foreach ($a in $files) { + if ($certsha1) { + SignTool sign /sha1 $certsha1 /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } elseif ($certname) { + SignTool sign /n $certname /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } elseif ($certfile) { + SignTool sign /f $certfile /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } else { + SignTool sign /a /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } + } +} + diff --git a/Tools/msi/sign_build.ps1 b/Tools/msi/sign_build.ps1 new file mode 100644 index 000000000000..6668eb33a2d1 --- /dev/null +++ b/Tools/msi/sign_build.ps1 @@ -0,0 +1,34 @@ +<# +.Synopsis + Recursively signs the contents of a directory. +.Description + Given the file patterns, code signs the contents. +.Parameter root + The root directory to sign. +.Parameter patterns + The file patterns to sign +.Parameter description + The description to add to the signature (optional). +.Parameter certname + The name of the certificate to sign with (optional). +.Parameter certsha1 + The SHA1 hash of the certificate to sign with (optional). +#> +param( + [Parameter(Mandatory=$true)][string]$root, + [string[]]$patterns=@("*.exe", "*.dll", "*.pyd"), + [string]$description, + [string]$certname, + [string]$certsha1, + [string]$certfile +) + +$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; +Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force + +pushd $root +try { + Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files (gci -r $patterns) +} finally { + popd +} \ No newline at end of file diff --git a/Tools/nuget/make_pkg.proj b/Tools/nuget/make_pkg.proj index 9843bc97ccdc..e093a6d0bd76 100644 --- a/Tools/nuget/make_pkg.proj +++ b/Tools/nuget/make_pkg.proj @@ -20,25 +20,28 @@ false $(OutputName).$(NuspecVersion) .nupkg - $(IntermediateOutputPath)\nuget_$(ArchName) + $(IntermediateOutputPath)\nuget_$(ArchName)\ - rmdir /q/s "$(IntermediateOutputPath)" + rmdir /q/s "$(IntermediateOutputPath.TrimEnd(`\`))" - "$(PythonExe)" "$(MSBuildThisFileDirectory)\..\msi\make_zip.py" - $(PythonArguments) -t "$(IntermediateOutputPath)" -b "$(BuildPath.TrimEnd(`\`))" + "$(PythonExe)" "$(PySourcePath)PC\layout" + $(PythonArguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" + $(PythonArguments) -t "$(IntermediateOutputPath)obj" + $(PythonArguments) --copy "$(IntermediateOutputPath)pkg" + $(PythonArguments) --include-dev --include-tools --include-pip --include-stable --include-launcher --include-props - "$(IntermediateOutputPath)\python.exe" -B -c "import sys; sys.path.append(r'$(PySourcePath)\Lib'); import ensurepip; ensurepip._main()" - "$(IntermediateOutputPath)\python.exe" -B -m pip install -U $(Packages) + "$(IntermediateOutputPath)pkg\pip.exe" -B -m pip install -U $(Packages) - "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).nuspec" -BasePath "$(IntermediateOutputPath)" + "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).nuspec" -BasePath "$(IntermediateOutputPath)pkg" "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).symbols.nuspec" -BasePath "$(BuildPath.TrimEnd(`\`))" $(NugetArguments) -OutputDirectory "$(OutputPath.Trim(`\`))" $(NugetArguments) -Version "$(NuspecVersion)" $(NugetArguments) -NoPackageAnalysis -NonInteractive - set DOC_FILENAME=python$(PythonVersion).chm $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - $(Environment)%0D%0Aset VCREDIST_PATH=$(CRTRedist)\$(Platform) + $(Environment)%0D%0Aset PYTHON_NUSPEC_VERSION=$(NuspecVersion) + $(Environment)%0D%0Aset PYTHON_PROPS_PLATFORM=$(Platform) + $(Environment)%0D%0Aset PYTHON_PROPS_PLATFORM=Win32 $(Environment)%0D%0Amkdir "$(OutputPath.Trim(`\`))" >nul 2>nul @@ -48,22 +51,7 @@ - - - - - - <_PropsContents>$([System.IO.File]::ReadAllText('python.props')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_TAG$$', '$(MajorVersionNumber).$(MinorVersionNumber)')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_VERSION$$', '$(NuspecVersion)')) - <_PropsContents Condition="$(Platform) == 'x86'">$(_PropsContents.Replace('$$PYTHON_PLATFORM$$', 'Win32')) - <_PropsContents Condition="$(Platform) != 'x86'">$(_PropsContents.Replace('$$PYTHON_PLATFORM$$', '$(Platform)')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_TARGET$$', '_GetPythonRuntimeFilesDependsOn$(MajorVersionNumber)$(MinorVersionNumber)_$(Platform)')) - <_ExistingContents Condition="Exists('$(IntermediateOutputPath)\python.props')">$([System.IO.File]::ReadAllText('$(IntermediateOutputPath)\python.props')) - - + From webhook-mailer at python.org Fri Dec 7 00:09:58 2018 From: webhook-mailer at python.org (Steve Dower) Date: Fri, 07 Dec 2018 05:09:58 -0000 Subject: [Python-checkins] [3.7] bpo-34977: Add Windows App Store package (GH-10245) Message-ID: https://github.com/python/cpython/commit/253209149389e6793a052034e1f2d97691086f18 commit: 253209149389e6793a052034e1f2d97691086f18 branch: 3.7 author: Steve Dower committer: GitHub date: 2018-12-06T21:09:53-08:00 summary: [3.7] bpo-34977: Add Windows App Store package (GH-10245) files: A .azure-pipelines/windows-appx-test.yml A Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst A PC/classicAppCompat.can.xml A PC/classicAppCompat.cat A PC/classicAppCompat.sccd A PC/icons/pythonwx150.png A PC/icons/pythonwx44.png A PC/icons/pythonx150.png A PC/icons/pythonx44.png A PC/icons/pythonx50.png A PC/layout/__init__.py A PC/layout/__main__.py A PC/layout/main.py A PC/layout/support/__init__.py A PC/layout/support/appxmanifest.py A PC/layout/support/catalog.py A PC/layout/support/constants.py A PC/layout/support/distutils.command.bdist_wininst.py A PC/layout/support/filesets.py A PC/layout/support/logging.py A PC/layout/support/options.py A PC/layout/support/pip.py A PC/layout/support/props.py A PC/layout/support/python.props A PC/python_uwp.cpp A PC/store_info.txt A PCbuild/python_uwp.vcxproj A PCbuild/pythonw_uwp.vcxproj A PCbuild/venvlauncher.vcxproj A PCbuild/venvwlauncher.vcxproj A Tools/msi/make_appx.ps1 A Tools/msi/make_cat.ps1 A Tools/msi/sdktools.psm1 A Tools/msi/sign_build.ps1 D Tools/msi/make_zip.py D Tools/nuget/python.props M .gitattributes M Lib/test/test_pathlib.py M Lib/test/test_venv.py M Lib/venv/__init__.py M PC/getpathp.c M PC/launcher.c M PC/pylauncher.rc M PCbuild/_tkinter.vcxproj M PCbuild/find_msbuild.bat M PCbuild/pcbuild.proj M PCbuild/pcbuild.sln M PCbuild/python.props M PCbuild/pythoncore.vcxproj M Tools/msi/buildrelease.bat M Tools/msi/make_zip.proj M Tools/nuget/make_pkg.proj diff --git a/.azure-pipelines/windows-appx-test.yml b/.azure-pipelines/windows-appx-test.yml new file mode 100644 index 000000000000..9840c0a1221f --- /dev/null +++ b/.azure-pipelines/windows-appx-test.yml @@ -0,0 +1,65 @@ +jobs: +- job: Prebuild + displayName: Pre-build checks + + pool: + vmImage: ubuntu-16.04 + + steps: + - template: ./prebuild-checks.yml + + +- job: Windows_Appx_Tests + displayName: Windows Appx Tests + dependsOn: Prebuild + condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) + + pool: + vmImage: vs2017-win2016 + + strategy: + matrix: + win64: + arch: amd64 + buildOpt: '-p x64' + testRunTitle: '$(Build.SourceBranchName)-win64-appx' + testRunPlatform: win64 + maxParallel: 2 + + steps: + - checkout: self + clean: true + fetchDepth: 5 + + - powershell: | + # Relocate build outputs outside of source directory to make cleaning faster + Write-Host '##vso[task.setvariable variable=Py_IntDir]$(Build.BinariesDirectory)\obj' + # UNDONE: Do not build to a different directory because of broken tests + Write-Host '##vso[task.setvariable variable=Py_OutDir]$(Build.SourcesDirectory)\PCbuild' + Write-Host '##vso[task.setvariable variable=EXTERNAL_DIR]$(Build.BinariesDirectory)\externals' + displayName: Update build locations + + - script: PCbuild\build.bat -e $(buildOpt) + displayName: 'Build CPython' + + - script: python.bat PC\layout -vv -s "$(Build.SourcesDirectory)" -b "$(Py_OutDir)\$(arch)" -t "$(Py_IntDir)\layout-tmp-$(arch)" --copy "$(Py_IntDir)\layout-$(arch)" --precompile --preset-appx --include-tests + displayName: 'Create APPX layout' + + - script: .\python.exe -m test.pythoninfo + workingDirectory: $(Py_IntDir)\layout-$(arch) + displayName: 'Display build info' + + - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results.xml" --tempdir "$(Py_IntDir)\tmp-$(arch)" + workingDirectory: $(Py_IntDir)\layout-$(arch) + displayName: 'Tests' + env: + PREFIX: $(Py_IntDir)\layout-$(arch) + + - task: PublishTestResults at 2 + displayName: 'Publish Test Results' + inputs: + testResultsFiles: '$(Build.BinariesDirectory)\test-results.xml' + mergeTestResults: true + testRunTitle: $(testRunTitle) + platform: $(testRunPlatform) + condition: succeededOrFailed() diff --git a/.gitattributes b/.gitattributes index 4a487c3c2a14..16237bb2b3ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,6 +19,7 @@ # Specific binary files Lib/test/sndhdrdata/sndhdr.* binary +PC/classicAppCompat.* binary # Text files that should not be subject to eol conversion Lib/test/cjkencodings/* -text diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index e436db995ce4..056507ef6fe8 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1519,7 +1519,7 @@ def test_resolve_common(self): # resolves to 'dirB/..' first before resolving to parent of dirB. self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) # Now create absolute symlinks - d = support._longpath(tempfile.mkdtemp(suffix='-dirD')) + d = support._longpath(tempfile.mkdtemp(suffix='-dirD', dir=os.getcwd())) self.addCleanup(support.rmtree, d) os.symlink(os.path.join(d), join('dirA', 'linkX')) os.symlink(join('dirB'), os.path.join(d, 'linkY')) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 461fe7afd213..22a3b78852f8 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -243,6 +243,7 @@ def test_isolation(self): self.assertIn('include-system-site-packages = %s\n' % s, data) @unittest.skipUnless(can_symlink(), 'Needs symlinks') + @unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows') def test_symlinking(self): """ Test symlinking works as expected diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 716129d13987..5438b0d4e508 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -9,6 +9,7 @@ import shutil import subprocess import sys +import sysconfig import types logger = logging.getLogger(__name__) @@ -63,10 +64,11 @@ def create(self, env_dir): self.system_site_packages = False self.create_configuration(context) self.setup_python(context) + if not self.upgrade: + self.setup_scripts(context) if self.with_pip: self._setup_pip(context) if not self.upgrade: - self.setup_scripts(context) self.post_setup(context) if true_system_site_packages: # We had set it to False before, now @@ -157,14 +159,6 @@ def create_configuration(self, context): f.write('include-system-site-packages = %s\n' % incl) f.write('version = %d.%d.%d\n' % sys.version_info[:3]) - if os.name == 'nt': - def include_binary(self, f): - if f.endswith(('.pyd', '.dll')): - result = True - else: - result = f.startswith('python') and f.endswith('.exe') - return result - def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): """ Try symlinking a file, and if that fails, fall back to copying. @@ -194,9 +188,9 @@ def setup_python(self, context): binpath = context.bin_path path = context.env_exe copier = self.symlink_or_copy - copier(context.executable, path) dirname = context.python_dir if os.name != 'nt': + copier(context.executable, path) if not os.path.islink(path): os.chmod(path, 0o755) for suffix in ('python', 'python3'): @@ -208,32 +202,33 @@ def setup_python(self, context): if not os.path.islink(path): os.chmod(path, 0o755) else: - subdir = 'DLLs' - include = self.include_binary - files = [f for f in os.listdir(dirname) if include(f)] - for f in files: - src = os.path.join(dirname, f) - dst = os.path.join(binpath, f) - if dst != context.env_exe: # already done, above - copier(src, dst) - dirname = os.path.join(dirname, subdir) - if os.path.isdir(dirname): - files = [f for f in os.listdir(dirname) if include(f)] - for f in files: - src = os.path.join(dirname, f) - dst = os.path.join(binpath, f) - copier(src, dst) - # copy init.tcl over - for root, dirs, files in os.walk(context.python_dir): - if 'init.tcl' in files: - tcldir = os.path.basename(root) - tcldir = os.path.join(context.env_dir, 'Lib', tcldir) - if not os.path.exists(tcldir): - os.makedirs(tcldir) - src = os.path.join(root, 'init.tcl') - dst = os.path.join(tcldir, 'init.tcl') - shutil.copyfile(src, dst) - break + # For normal cases, the venvlauncher will be copied from + # our scripts folder. For builds, we need to copy it + # manually. + if sysconfig.is_python_build(True): + suffix = '.exe' + if context.python_exe.lower().endswith('_d.exe'): + suffix = '_d.exe' + + src = os.path.join(dirname, "venvlauncher" + suffix) + dst = os.path.join(binpath, context.python_exe) + copier(src, dst) + + src = os.path.join(dirname, "venvwlauncher" + suffix) + dst = os.path.join(binpath, "pythonw" + suffix) + copier(src, dst) + + # copy init.tcl over + for root, dirs, files in os.walk(context.python_dir): + if 'init.tcl' in files: + tcldir = os.path.basename(root) + tcldir = os.path.join(context.env_dir, 'Lib', tcldir) + if not os.path.exists(tcldir): + os.makedirs(tcldir) + src = os.path.join(root, 'init.tcl') + dst = os.path.join(tcldir, 'init.tcl') + shutil.copyfile(src, dst) + break def _setup_pip(self, context): """Installs or upgrades pip in a virtual environment""" @@ -320,7 +315,7 @@ def install_scripts(self, context, path): dstfile = os.path.join(dstdir, f) with open(srcfile, 'rb') as f: data = f.read() - if not srcfile.endswith('.exe'): + if not srcfile.endswith(('.exe', '.pdb')): try: data = data.decode('utf-8') data = self.replace_variables(data, context) diff --git a/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst new file mode 100644 index 000000000000..8e1a4ba84880 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst @@ -0,0 +1 @@ +Adds support for building a Windows App Store package diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml new file mode 100644 index 000000000000..f00475c8da31 --- /dev/null +++ b/PC/classicAppCompat.can.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat new file mode 100644 index 000000000000..3d213596accf Binary files /dev/null and b/PC/classicAppCompat.cat differ diff --git a/PC/classicAppCompat.sccd b/PC/classicAppCompat.sccd new file mode 100644 index 000000000000..97648985a2cc --- /dev/null +++ b/PC/classicAppCompat.sccd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 diff --git a/PC/getpathp.c b/PC/getpathp.c index bc85b58abff1..4075463f2260 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -536,10 +536,16 @@ static _PyInitError get_program_full_path(const _PyCoreConfig *core_config, PyCalculatePath *calculate, _PyPathConfig *config) { + const wchar_t *pyvenv_launcher; wchar_t program_full_path[MAXPATHLEN+1]; memset(program_full_path, 0, sizeof(program_full_path)); - if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { + /* The launcher may need to force the executable path to a + * different environment, so override it here. */ + pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__"); + if (pyvenv_launcher && pyvenv_launcher[0]) { + wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher); + } else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { /* GetModuleFileName should never fail when passed NULL */ return _Py_INIT_ERR("Cannot determine program path"); } diff --git a/PC/icons/pythonwx150.png b/PC/icons/pythonwx150.png new file mode 100644 index 000000000000..4c3eb316739c Binary files /dev/null and b/PC/icons/pythonwx150.png differ diff --git a/PC/icons/pythonwx44.png b/PC/icons/pythonwx44.png new file mode 100644 index 000000000000..e3b32a871f90 Binary files /dev/null and b/PC/icons/pythonwx44.png differ diff --git a/PC/icons/pythonx150.png b/PC/icons/pythonx150.png new file mode 100644 index 000000000000..5f8d30418386 Binary files /dev/null and b/PC/icons/pythonx150.png differ diff --git a/PC/icons/pythonx44.png b/PC/icons/pythonx44.png new file mode 100644 index 000000000000..3881daaef233 Binary files /dev/null and b/PC/icons/pythonx44.png differ diff --git a/PC/icons/pythonx50.png b/PC/icons/pythonx50.png new file mode 100644 index 000000000000..7cc3aecd0242 Binary files /dev/null and b/PC/icons/pythonx50.png differ diff --git a/PC/launcher.c b/PC/launcher.c index 7d666aae4ab1..0242f2639119 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -28,7 +28,7 @@ #define RC_NO_PYTHON 103 #define RC_NO_MEMORY 104 /* - * SCRIPT_WRAPPER is used to choose between two variants of an executable built + * SCRIPT_WRAPPER is used to choose one of the variants of an executable built * from this source file. If not defined, the PEP 397 Python launcher is built; * if defined, a script launcher of the type used by setuptools is built, which * looks for a script name related to the executable name and runs that script @@ -40,6 +40,15 @@ #if defined(SCRIPT_WRAPPER) #define RC_NO_SCRIPT 105 #endif +/* + * VENV_REDIRECT is used to choose the variant that looks for an adjacent or + * one-level-higher pyvenv.cfg, and uses its "home" property to locate and + * launch the original python.exe. + */ +#if defined(VENV_REDIRECT) +#define RC_NO_VENV_CFG 106 +#define RC_BAD_VENV_CFG 107 +#endif /* Just for now - static definition */ @@ -97,7 +106,7 @@ error(int rc, wchar_t * format, ... ) #if !defined(_WINDOWS) fwprintf(stderr, L"%ls\n", message); #else - MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."), + MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", MB_OK); #endif exit(rc); @@ -131,6 +140,17 @@ static wchar_t * get_env(wchar_t * key) return buf; } +#if defined(_DEBUG) +#if defined(_WINDOWS) + +#define PYTHON_EXECUTABLE L"pythonw_d.exe" + +#else + +#define PYTHON_EXECUTABLE L"python_d.exe" + +#endif +#else #if defined(_WINDOWS) #define PYTHON_EXECUTABLE L"pythonw.exe" @@ -139,6 +159,7 @@ static wchar_t * get_env(wchar_t * key) #define PYTHON_EXECUTABLE L"python.exe" +#endif #endif #define MAX_VERSION_SIZE 4 @@ -1118,6 +1139,7 @@ static PYC_MAGIC magic_values[] = { { 3320, 3351, L"3.5" }, { 3360, 3379, L"3.6" }, { 3390, 3399, L"3.7" }, + { 3400, 3409, L"3.8" }, { 0 } }; @@ -1456,6 +1478,87 @@ show_python_list(wchar_t ** argv) return FALSE; /* If this has been called we cannot continue */ } +#if defined(VENV_REDIRECT) + +static int +find_home_value(const char *buffer, const char **start, DWORD *length) +{ + for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) { + if (*s == '\n') { + ++s; + } + for (int i = 4; i > 0 && *s; --i, ++s); + + while (*s && iswspace(*s)) { + ++s; + } + if (*s != L'=') { + continue; + } + + do { + ++s; + } while (*s && iswspace(*s)); + + *start = s; + char *nl = strchr(s, '\n'); + if (nl) { + *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s); + } else { + *length = (DWORD)strlen(s); + } + return 1; + } + return 0; +} +#endif + +static wchar_t * +wcsdup_pad(const wchar_t *s, int padding, int *newlen) +{ + size_t len = wcslen(s); + len += 1 + padding; + wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t)); + if (!r) { + return NULL; + } + if (wcscpy_s(r, len, s)) { + free(r); + return NULL; + } + *newlen = len < MAXINT ? (int)len : MAXINT; + return r; +} + +static wchar_t * +get_process_name() +{ + DWORD bufferLen = MAX_PATH; + DWORD len = bufferLen; + wchar_t *r = NULL; + + while (!r) { + r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); + if (!r) { + error(RC_NO_MEMORY, L"out of memory"); + return NULL; + } + len = GetModuleFileNameW(NULL, r, bufferLen); + if (len == 0) { + free(r); + error(0, L"Failed to get module name"); + return NULL; + } else if (len == bufferLen && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(r); + r = NULL; + bufferLen *= 2; + } + } + + return r; +} + static int process(int argc, wchar_t ** argv) { @@ -1463,21 +1566,27 @@ process(int argc, wchar_t ** argv) wchar_t * command; wchar_t * executable; wchar_t * p; + wchar_t * argv0; int rc = 0; - size_t plen; INSTALLED_PYTHON * ip; BOOL valid; DWORD size, attrs; - HRESULT hr; wchar_t message[MSGSIZE]; void * version_data; VS_FIXEDFILEINFO * file_info; UINT block_size; - int index; -#if defined(SCRIPT_WRAPPER) +#if defined(VENV_REDIRECT) + wchar_t * venv_cfg_path; int newlen; +#elif defined(SCRIPT_WRAPPER) wchar_t * newcommand; wchar_t * av[2]; + int newlen; + HRESULT hr; + int index; +#else + HRESULT hr; + int index; #endif setvbuf(stderr, (char *)NULL, _IONBF, 0); @@ -1495,6 +1604,7 @@ process(int argc, wchar_t ** argv) #else debug(L"launcher executable: Console\n"); #endif +#if !defined(VENV_REDIRECT) /* Get the local appdata folder (non-roaming) */ hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appdata_ini_path); @@ -1503,9 +1613,7 @@ process(int argc, wchar_t ** argv) appdata_ini_path[0] = L'\0'; } else { - plen = wcslen(appdata_ini_path); - p = &appdata_ini_path[plen]; - wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE); + wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE); attrs = GetFileAttributesW(appdata_ini_path); if (attrs == INVALID_FILE_ATTRIBUTES) { debug(L"File '%ls' non-existent\n", appdata_ini_path); @@ -1514,8 +1622,9 @@ process(int argc, wchar_t ** argv) debug(L"Using local configuration file '%ls'\n", appdata_ini_path); } } - plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH); - size = GetFileVersionInfoSizeW(launcher_ini_path, &size); +#endif + argv0 = get_process_name(); + size = GetFileVersionInfoSizeW(argv0, &size); if (size == 0) { winerror(GetLastError(), message, MSGSIZE); debug(L"GetFileVersionInfoSize failed: %ls\n", message); @@ -1523,7 +1632,7 @@ process(int argc, wchar_t ** argv) else { version_data = malloc(size); if (version_data) { - valid = GetFileVersionInfoW(launcher_ini_path, 0, size, + valid = GetFileVersionInfoW(argv0, 0, size, version_data); if (!valid) debug(L"GetFileVersionInfo failed: %X\n", GetLastError()); @@ -1540,15 +1649,51 @@ process(int argc, wchar_t ** argv) free(version_data); } } + +#if defined(VENV_REDIRECT) + /* Allocate some extra space for new filenames */ + venv_cfg_path = wcsdup_pad(argv0, 32, &newlen); + if (!venv_cfg_path) { + error(RC_NO_MEMORY, L"Failed to copy module name"); + } + p = wcsrchr(venv_cfg_path, L'\\'); + + if (p == NULL) { + error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); + } + p[0] = L'\0'; + wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); + attrs = GetFileAttributesW(venv_cfg_path); + if (attrs == INVALID_FILE_ATTRIBUTES) { + debug(L"File '%ls' non-existent\n", venv_cfg_path); + p[0] = '\0'; + p = wcsrchr(venv_cfg_path, L'\\'); + if (p != NULL) { + p[0] = '\0'; + wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); + attrs = GetFileAttributesW(venv_cfg_path); + if (attrs == INVALID_FILE_ATTRIBUTES) { + debug(L"File '%ls' non-existent\n", venv_cfg_path); + error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); + } + } + } + debug(L"Using venv configuration file '%ls'\n", venv_cfg_path); +#else + /* Allocate some extra space for new filenames */ + if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) { + error(RC_NO_MEMORY, L"Failed to copy module name"); + } p = wcsrchr(launcher_ini_path, L'\\'); + if (p == NULL) { debug(L"GetModuleFileNameW returned value has no backslash: %ls\n", launcher_ini_path); launcher_ini_path[0] = L'\0'; } else { - wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini", - _TRUNCATE); + p[0] = L'\0'; + wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini"); attrs = GetFileAttributesW(launcher_ini_path); if (attrs == INVALID_FILE_ATTRIBUTES) { debug(L"File '%ls' non-existent\n", launcher_ini_path); @@ -1557,6 +1702,7 @@ process(int argc, wchar_t ** argv) debug(L"Using global configuration file '%ls'\n", launcher_ini_path); } } +#endif command = skip_me(GetCommandLineW()); debug(L"Called with command line: %ls\n", command); @@ -1592,6 +1738,52 @@ process(int argc, wchar_t ** argv) command = newcommand; valid = FALSE; } +#elif defined(VENV_REDIRECT) + { + FILE *f; + char buffer[4096]; /* 4KB should be enough for anybody */ + char *start; + DWORD len, cch, cch_actual; + size_t cb; + if (_wfopen_s(&f, venv_cfg_path, L"r")) { + error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path); + } + cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]), + sizeof(buffer) / sizeof(buffer[0]), f); + fclose(f); + + if (!find_home_value(buffer, &start, &len)) { + error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'", + venv_cfg_path); + } + + cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0); + if (!cch) { + error(0, L"Cannot determine memory for home path"); + } + cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */ + executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); + cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch); + if (!cch_actual) { + error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", + venv_cfg_path); + } + if (executable[cch_actual - 1] != L'\\') { + executable[cch_actual++] = L'\\'; + executable[cch_actual] = L'\0'; + } + if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) { + error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'", + venv_cfg_path); + } + if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) { + error(RC_NO_PYTHON, L"No Python at '%ls'", executable); + } + if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) { + error(0, L"Failed to set launcher environment"); + } + valid = 1; + } #else if (argc <= 1) { valid = FALSE; @@ -1599,7 +1791,6 @@ process(int argc, wchar_t ** argv) } else { p = argv[1]; - plen = wcslen(p); if ((argc == 2) && // list version args (!wcsncmp(p, L"-0", wcslen(L"-0")) || !wcsncmp(p, L"--list", wcslen(L"--list")))) diff --git a/PC/layout/__init__.py b/PC/layout/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/PC/layout/__main__.py b/PC/layout/__main__.py new file mode 100644 index 000000000000..f7aa1e6d261f --- /dev/null +++ b/PC/layout/__main__.py @@ -0,0 +1,14 @@ +import sys + +try: + import layout +except ImportError: + # Failed to import our package, which likely means we were started directly + # Add the additional search path needed to locate our module. + from pathlib import Path + + sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from layout.main import main + +sys.exit(int(main() or 0)) diff --git a/PC/layout/main.py b/PC/layout/main.py new file mode 100644 index 000000000000..82d0536ca920 --- /dev/null +++ b/PC/layout/main.py @@ -0,0 +1,612 @@ +""" +Generates a layout of Python for Windows from a build. + +See python make_layout.py --help for usage. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import argparse +import functools +import os +import re +import shutil +import subprocess +import sys +import tempfile +import zipfile + +from pathlib import Path + +if __name__ == "__main__": + # Started directly, so enable relative imports + __path__ = [str(Path(__file__).resolve().parent)] + +from .support.appxmanifest import * +from .support.catalog import * +from .support.constants import * +from .support.filesets import * +from .support.logging import * +from .support.options import * +from .support.pip import * +from .support.props import * + +BDIST_WININST_FILES_ONLY = FileNameSet("wininst-*", "bdist_wininst.py") +BDIST_WININST_STUB = "PC/layout/support/distutils.command.bdist_wininst.py" + +TEST_PYDS_ONLY = FileStemSet("xxlimited", "_ctypes_test", "_test*") +TEST_DIRS_ONLY = FileNameSet("test", "tests") + +IDLE_DIRS_ONLY = FileNameSet("idlelib") + +TCLTK_PYDS_ONLY = FileStemSet("tcl*", "tk*", "_tkinter") +TCLTK_DIRS_ONLY = FileNameSet("tkinter", "turtledemo") +TCLTK_FILES_ONLY = FileNameSet("turtle.py") + +VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip") + +EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext") +EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle") +EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt") +EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*") +EXCLUDE_FROM_CATALOG = FileSuffixSet(".exe", ".pyd", ".dll") + +REQUIRED_DLLS = FileStemSet("libcrypto*", "libssl*") + +LIB2TO3_GRAMMAR_FILES = FileNameSet("Grammar.txt", "PatternGrammar.txt") + +PY_FILES = FileSuffixSet(".py") +PYC_FILES = FileSuffixSet(".pyc") +CAT_FILES = FileSuffixSet(".cat") +CDF_FILES = FileSuffixSet(".cdf") + +DATA_DIRS = FileNameSet("data") + +TOOLS_DIRS = FileNameSet("scripts", "i18n", "pynche", "demo", "parser") +TOOLS_FILES = FileSuffixSet(".py", ".pyw", ".txt") + + +def get_lib_layout(ns): + def _c(f): + if f in EXCLUDE_FROM_LIB: + return False + if f.is_dir(): + if f in TEST_DIRS_ONLY: + return ns.include_tests + if f in TCLTK_DIRS_ONLY: + return ns.include_tcltk + if f in IDLE_DIRS_ONLY: + return ns.include_idle + if f in VENV_DIRS_ONLY: + return ns.include_venv + else: + if f in TCLTK_FILES_ONLY: + return ns.include_tcltk + if f in BDIST_WININST_FILES_ONLY: + return ns.include_bdist_wininst + return True + + for dest, src in rglob(ns.source / "Lib", "**/*", _c): + yield dest, src + + if not ns.include_bdist_wininst: + src = ns.source / BDIST_WININST_STUB + yield Path("distutils/command/bdist_wininst.py"), src + + +def get_tcltk_lib(ns): + if not ns.include_tcltk: + return + + tcl_lib = os.getenv("TCL_LIBRARY") + if not tcl_lib or not os.path.isdir(tcl_lib): + try: + with open(ns.build / "TCL_LIBRARY.env", "r", encoding="utf-8-sig") as f: + tcl_lib = f.read().strip() + except FileNotFoundError: + pass + if not tcl_lib or not os.path.isdir(tcl_lib): + warn("Failed to find TCL_LIBRARY") + return + + for dest, src in rglob(Path(tcl_lib).parent, "**/*"): + yield "tcl/{}".format(dest), src + + +def get_layout(ns): + def in_build(f, dest="", new_name=None): + n, _, x = f.rpartition(".") + n = new_name or n + src = ns.build / f + if ns.debug and src not in REQUIRED_DLLS: + if not src.stem.endswith("_d"): + src = src.parent / (src.stem + "_d" + src.suffix) + if not n.endswith("_d"): + n += "_d" + f = n + "." + x + yield dest + n + "." + x, src + if ns.include_symbols: + pdb = src.with_suffix(".pdb") + if pdb.is_file(): + yield dest + n + ".pdb", pdb + if ns.include_dev: + lib = src.with_suffix(".lib") + if lib.is_file(): + yield "libs/" + n + ".lib", lib + + yield from in_build("python_uwp.exe", new_name="python") + yield from in_build("pythonw_uwp.exe", new_name="pythonw") + + yield from in_build(PYTHON_DLL_NAME) + + if ns.include_launchers: + if ns.include_pip: + yield from in_build("python_uwp.exe", new_name="pip") + if ns.include_idle: + yield from in_build("pythonw_uwp.exe", new_name="idle") + + if ns.include_stable: + yield from in_build(PYTHON_STABLE_DLL_NAME) + + for dest, src in rglob(ns.build, "vcruntime*.dll"): + yield dest, src + + for dest, src in rglob(ns.build, ("*.pyd", "*.dll")): + if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS: + continue + if src in EXCLUDE_FROM_PYDS: + continue + if src in TEST_PYDS_ONLY and not ns.include_tests: + continue + if src in TCLTK_PYDS_ONLY and not ns.include_tcltk: + continue + + yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/") + + if ns.zip_lib: + zip_name = PYTHON_ZIP_NAME + yield zip_name, ns.temp / zip_name + else: + for dest, src in get_lib_layout(ns): + yield "Lib/{}".format(dest), src + + if ns.include_venv: + yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python") + yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw") + + if ns.include_tools: + + def _c(d): + if d.is_dir(): + return d in TOOLS_DIRS + return d in TOOLS_FILES + + for dest, src in rglob(ns.source / "Tools", "**/*", _c): + yield "Tools/{}".format(dest), src + + if ns.include_underpth: + yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME + + if ns.include_dev: + + def _c(d): + if d.is_dir(): + return d.name != "internal" + return True + + for dest, src in rglob(ns.source / "Include", "**/*.h", _c): + yield "include/{}".format(dest), src + src = ns.source / "PC" / "pyconfig.h" + yield "include/pyconfig.h", src + + for dest, src in get_tcltk_lib(ns): + yield dest, src + + if ns.include_pip: + pip_dir = get_pip_dir(ns) + if not pip_dir.is_dir(): + log_warning("Failed to find {} - pip will not be included", pip_dir) + else: + pkg_root = "packages/{}" if ns.zip_lib else "Lib/site-packages/{}" + for dest, src in rglob(pip_dir, "**/*"): + if src in EXCLUDE_FROM_LIB or src in EXCLUDE_FROM_PACKAGED_LIB: + continue + yield pkg_root.format(dest), src + + if ns.include_chm: + for dest, src in rglob(ns.doc_build / "htmlhelp", PYTHON_CHM_NAME): + yield "Doc/{}".format(dest), src + + if ns.include_html_doc: + for dest, src in rglob(ns.doc_build / "html", "**/*"): + yield "Doc/html/{}".format(dest), src + + if ns.include_props: + for dest, src in get_props_layout(ns): + yield dest, src + + for dest, src in get_appx_layout(ns): + yield dest, src + + if ns.include_cat: + if ns.flat_dlls: + yield ns.include_cat.name, ns.include_cat + else: + yield "DLLs/{}".format(ns.include_cat.name), ns.include_cat + + +def _compile_one_py(src, dest, name, optimize): + import py_compile + + if dest is not None: + dest = str(dest) + + try: + return Path( + py_compile.compile( + str(src), + dest, + str(name), + doraise=True, + optimize=optimize, + invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, + ) + ) + except py_compile.PyCompileError: + log_warning("Failed to compile {}", src) + return None + + +def _py_temp_compile(src, ns, dest_dir=None): + if not ns.precompile or src not in PY_FILES or src.parent in DATA_DIRS: + return None + + dest = (dest_dir or ns.temp) / (src.stem + ".py") + return _compile_one_py(src, dest.with_suffix(".pyc"), dest, optimize=2) + + +def _write_to_zip(zf, dest, src, ns): + pyc = _py_temp_compile(src, ns) + if pyc: + try: + zf.write(str(pyc), dest.with_suffix(".pyc")) + finally: + try: + pyc.unlink() + except: + log_exception("Failed to delete {}", pyc) + return + + if src in LIB2TO3_GRAMMAR_FILES: + from lib2to3.pgen2.driver import load_grammar + + tmp = ns.temp / src.name + try: + shutil.copy(src, tmp) + load_grammar(str(tmp)) + for f in ns.temp.glob(src.stem + "*.pickle"): + zf.write(str(f), str(dest.parent / f.name)) + try: + f.unlink() + except: + log_exception("Failed to delete {}", f) + except: + log_exception("Failed to compile {}", src) + finally: + try: + tmp.unlink() + except: + log_exception("Failed to delete {}", tmp) + + zf.write(str(src), str(dest)) + + +def generate_source_files(ns): + if ns.zip_lib: + zip_name = PYTHON_ZIP_NAME + zip_path = ns.temp / zip_name + if zip_path.is_file(): + zip_path.unlink() + elif zip_path.is_dir(): + log_error( + "Cannot create zip file because a directory exists by the same name" + ) + return + log_info("Generating {} in {}", zip_name, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: + for dest, src in get_lib_layout(ns): + _write_to_zip(zf, dest, src, ns) + + if ns.include_underpth: + log_info("Generating {} in {}", PYTHON_PTH_NAME, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with open(ns.temp / PYTHON_PTH_NAME, "w", encoding="utf-8") as f: + if ns.zip_lib: + print(PYTHON_ZIP_NAME, file=f) + if ns.include_pip: + print("packages", file=f) + else: + print("Lib", file=f) + print("Lib/site-packages", file=f) + if not ns.flat_dlls: + print("DLLs", file=f) + print(".", file=f) + print(file=f) + print("# Uncomment to run site.main() automatically", file=f) + print("#import site", file=f) + + if ns.include_appxmanifest: + log_info("Generating AppxManifest.xml in {}", ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + + with open(ns.temp / "AppxManifest.xml", "wb") as f: + f.write(get_appxmanifest(ns)) + + with open(ns.temp / "_resources.xml", "wb") as f: + f.write(get_resources_xml(ns)) + + if ns.include_pip: + pip_dir = get_pip_dir(ns) + if not (pip_dir / "pip").is_dir(): + log_info("Extracting pip to {}", pip_dir) + pip_dir.mkdir(parents=True, exist_ok=True) + extract_pip_files(ns) + + if ns.include_props: + log_info("Generating {} in {}", PYTHON_PROPS_NAME, ns.temp) + ns.temp.mkdir(parents=True, exist_ok=True) + with open(ns.temp / PYTHON_PROPS_NAME, "wb") as f: + f.write(get_props(ns)) + + +def _create_zip_file(ns): + if not ns.zip: + return None + + if ns.zip.is_file(): + try: + ns.zip.unlink() + except OSError: + log_exception("Unable to remove {}", ns.zip) + sys.exit(8) + elif ns.zip.is_dir(): + log_error("Cannot create ZIP file because {} is a directory", ns.zip) + sys.exit(8) + + ns.zip.parent.mkdir(parents=True, exist_ok=True) + return zipfile.ZipFile(ns.zip, "w", zipfile.ZIP_DEFLATED) + + +def copy_files(files, ns): + if ns.copy: + ns.copy.mkdir(parents=True, exist_ok=True) + + try: + total = len(files) + except TypeError: + total = None + count = 0 + + zip_file = _create_zip_file(ns) + try: + need_compile = [] + in_catalog = [] + + for dest, src in files: + count += 1 + if count % 10 == 0: + if total: + log_info("Processed {:>4} of {} files", count, total) + else: + log_info("Processed {} files", count) + log_debug("Processing {!s}", src) + + if ( + ns.precompile + and src in PY_FILES + and src not in EXCLUDE_FROM_COMPILE + and src.parent not in DATA_DIRS + and os.path.normcase(str(dest)).startswith(os.path.normcase("Lib")) + ): + if ns.copy: + need_compile.append((dest, ns.copy / dest)) + else: + (ns.temp / "Lib" / dest).parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, ns.temp / "Lib" / dest) + need_compile.append((dest, ns.temp / "Lib" / dest)) + + if src not in EXCLUDE_FROM_CATALOG: + in_catalog.append((src.name, src)) + + if ns.copy: + log_debug("Copy {} -> {}", src, ns.copy / dest) + (ns.copy / dest).parent.mkdir(parents=True, exist_ok=True) + try: + shutil.copy2(src, ns.copy / dest) + except shutil.SameFileError: + pass + + if ns.zip: + log_debug("Zip {} into {}", src, ns.zip) + zip_file.write(src, str(dest)) + + if need_compile: + for dest, src in need_compile: + compiled = [ + _compile_one_py(src, None, dest, optimize=0), + _compile_one_py(src, None, dest, optimize=1), + _compile_one_py(src, None, dest, optimize=2), + ] + for c in compiled: + if not c: + continue + cdest = Path(dest).parent / Path(c).relative_to(src.parent) + if ns.zip: + log_debug("Zip {} into {}", c, ns.zip) + zip_file.write(c, str(cdest)) + in_catalog.append((cdest.name, cdest)) + + if ns.catalog: + # Just write out the CDF now. Compilation and signing is + # an extra step + log_info("Generating {}", ns.catalog) + ns.catalog.parent.mkdir(parents=True, exist_ok=True) + write_catalog(ns.catalog, in_catalog) + + finally: + if zip_file: + zip_file.close() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-v", help="Increase verbosity", action="count") + parser.add_argument( + "-s", + "--source", + metavar="dir", + help="The directory containing the repository root", + type=Path, + default=None, + ) + parser.add_argument( + "-b", "--build", metavar="dir", help="Specify the build directory", type=Path + ) + parser.add_argument( + "--doc-build", + metavar="dir", + help="Specify the docs build directory", + type=Path, + default=None, + ) + parser.add_argument( + "--copy", + metavar="directory", + help="The name of the directory to copy an extracted layout to", + type=Path, + default=None, + ) + parser.add_argument( + "--zip", + metavar="file", + help="The ZIP file to write all files to", + type=Path, + default=None, + ) + parser.add_argument( + "--catalog", + metavar="file", + help="The CDF file to write catalog entries to", + type=Path, + default=None, + ) + parser.add_argument( + "--log", + metavar="file", + help="Write all operations to the specified file", + type=Path, + default=None, + ) + parser.add_argument( + "-t", + "--temp", + metavar="file", + help="A temporary working directory", + type=Path, + default=None, + ) + parser.add_argument( + "-d", "--debug", help="Include debug build", action="store_true" + ) + parser.add_argument( + "-p", + "--precompile", + help="Include .pyc files instead of .py", + action="store_true", + ) + parser.add_argument( + "-z", "--zip-lib", help="Include library in a ZIP file", action="store_true" + ) + parser.add_argument( + "--flat-dlls", help="Does not create a DLLs directory", action="store_true" + ) + parser.add_argument( + "-a", + "--include-all", + help="Include all optional components", + action="store_true", + ) + parser.add_argument( + "--include-cat", + metavar="file", + help="Specify the catalog file to include", + type=Path, + default=None, + ) + for opt, help in get_argparse_options(): + parser.add_argument(opt, help=help, action="store_true") + + ns = parser.parse_args() + update_presets(ns) + + ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent) + ns.build = ns.build or Path(sys.executable).parent + ns.temp = ns.temp or Path(tempfile.mkdtemp()) + ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build") + if not ns.source.is_absolute(): + ns.source = (Path.cwd() / ns.source).resolve() + if not ns.build.is_absolute(): + ns.build = (Path.cwd() / ns.build).resolve() + if not ns.temp.is_absolute(): + ns.temp = (Path.cwd() / ns.temp).resolve() + if not ns.doc_build.is_absolute(): + ns.doc_build = (Path.cwd() / ns.doc_build).resolve() + if ns.include_cat and not ns.include_cat.is_absolute(): + ns.include_cat = (Path.cwd() / ns.include_cat).resolve() + + if ns.copy and not ns.copy.is_absolute(): + ns.copy = (Path.cwd() / ns.copy).resolve() + if ns.zip and not ns.zip.is_absolute(): + ns.zip = (Path.cwd() / ns.zip).resolve() + if ns.catalog and not ns.catalog.is_absolute(): + ns.catalog = (Path.cwd() / ns.catalog).resolve() + + configure_logger(ns) + + log_info( + """OPTIONS +Source: {ns.source} +Build: {ns.build} +Temp: {ns.temp} + +Copy to: {ns.copy} +Zip to: {ns.zip} +Catalog: {ns.catalog}""", + ns=ns, + ) + + if ns.include_idle and not ns.include_tcltk: + log_warning("Assuming --include-tcltk to support --include-idle") + ns.include_tcltk = True + + try: + generate_source_files(ns) + files = list(get_layout(ns)) + copy_files(files, ns) + except KeyboardInterrupt: + log_info("Interrupted by Ctrl+C") + return 3 + except SystemExit: + raise + except: + log_exception("Unhandled error") + + if error_was_logged(): + log_error("Errors occurred.") + return 1 + + +if __name__ == "__main__": + sys.exit(int(main() or 0)) diff --git a/PC/layout/support/__init__.py b/PC/layout/support/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py new file mode 100644 index 000000000000..c5dda70c7ef8 --- /dev/null +++ b/PC/layout/support/appxmanifest.py @@ -0,0 +1,487 @@ +""" +File generation for APPX/MSIX manifests. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import collections +import ctypes +import io +import os +import sys + +from pathlib import Path, PureWindowsPath +from xml.etree import ElementTree as ET + +from .constants import * + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +APPX_DATA = dict( + Name="PythonSoftwareFoundation.Python.{}".format(VER_DOT), + Version="{}.{}.{}.0".format(VER_MAJOR, VER_MINOR, VER_FIELD3), + Publisher=os.getenv( + "APPX_DATA_PUBLISHER", "CN=4975D53F-AA7E-49A5-8B49-EA4FDC1BB66B" + ), + DisplayName="Python {}".format(VER_DOT), + Description="The Python {} runtime and console.".format(VER_DOT), + ProcessorArchitecture="x64" if IS_X64 else "x86", +) + +PYTHON_VE_DATA = dict( + DisplayName="Python {}".format(VER_DOT), + Description="Python interactive console", + Square150x150Logo="_resources/pythonx150.png", + Square44x44Logo="_resources/pythonx44.png", + BackgroundColor="transparent", +) + +PYTHONW_VE_DATA = dict( + DisplayName="Python {} (Windowed)".format(VER_DOT), + Description="Python windowed app launcher", + Square150x150Logo="_resources/pythonwx150.png", + Square44x44Logo="_resources/pythonwx44.png", + BackgroundColor="transparent", + AppListEntry="none", +) + +PIP_VE_DATA = dict( + DisplayName="pip (Python {})".format(VER_DOT), + Description="pip package manager for Python {}".format(VER_DOT), + Square150x150Logo="_resources/pythonx150.png", + Square44x44Logo="_resources/pythonx44.png", + BackgroundColor="transparent", + AppListEntry="none", +) + +IDLE_VE_DATA = dict( + DisplayName="IDLE (Python {})".format(VER_DOT), + Description="IDLE editor for Python {}".format(VER_DOT), + Square150x150Logo="_resources/pythonwx150.png", + Square44x44Logo="_resources/pythonwx44.png", + BackgroundColor="transparent", +) + +APPXMANIFEST_NS = { + "": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", + "m": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", + "uap": "http://schemas.microsoft.com/appx/manifest/uap/windows10", + "rescap": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities", + "rescap4": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4", + "desktop4": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/4", + "desktop6": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6", + "uap3": "http://schemas.microsoft.com/appx/manifest/uap/windows10/3", + "uap4": "http://schemas.microsoft.com/appx/manifest/uap/windows10/4", + "uap5": "http://schemas.microsoft.com/appx/manifest/uap/windows10/5", +} + +APPXMANIFEST_TEMPLATE = """ + + + + + Python Software Foundation + + _resources/pythonx50.png + + + + + + + + + + + + + + +""" + + +RESOURCES_XML_TEMPLATE = r""" + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + +SCCD_FILENAME = "PC/classicAppCompat.sccd" + +REGISTRY = { + "HKCU\\Software\\Python\\PythonCore": { + VER_DOT: { + "DisplayName": APPX_DATA["DisplayName"], + "SupportUrl": "https://www.python.org/", + "SysArchitecture": "64bit" if IS_X64 else "32bit", + "SysVersion": VER_DOT, + "Version": "{}.{}.{}".format(VER_MAJOR, VER_MINOR, VER_MICRO), + "InstallPath": { + # I have no idea why the trailing spaces are needed, but they seem to be needed. + "": "[{AppVPackageRoot}][ ]", + "ExecutablePath": "[{AppVPackageRoot}]python.exe[ ]", + "WindowedExecutablePath": "[{AppVPackageRoot}]pythonw.exe[ ]", + }, + "Help": { + "Main Python Documentation": { + "_condition": lambda ns: ns.include_chm, + "": "[{{AppVPackageRoot}}]Doc\\{}[ ]".format( + PYTHON_CHM_NAME + ), + }, + "Local Python Documentation": { + "_condition": lambda ns: ns.include_html_doc, + "": "[{AppVPackageRoot}]Doc\\html\\index.html[ ]", + }, + "Online Python Documentation": { + "": "https://docs.python.org/{}".format(VER_DOT) + }, + }, + "Idle": { + "_condition": lambda ns: ns.include_idle, + "": "[{AppVPackageRoot}]Lib\\idlelib\\idle.pyw[ ]", + }, + } + } +} + + +def get_packagefamilyname(name, publisher_id): + class PACKAGE_ID(ctypes.Structure): + _fields_ = [ + ("reserved", ctypes.c_uint32), + ("processorArchitecture", ctypes.c_uint32), + ("version", ctypes.c_uint64), + ("name", ctypes.c_wchar_p), + ("publisher", ctypes.c_wchar_p), + ("resourceId", ctypes.c_wchar_p), + ("publisherId", ctypes.c_wchar_p), + ] + _pack_ = 4 + + pid = PACKAGE_ID(0, 0, 0, name, publisher_id, None, None) + result = ctypes.create_unicode_buffer(256) + result_len = ctypes.c_uint32(256) + r = ctypes.windll.kernel32.PackageFamilyNameFromId( + pid, ctypes.byref(result_len), result + ) + if r: + raise OSError(r, "failed to get package family name") + return result.value[: result_len.value] + + +def _fixup_sccd(ns, sccd, new_hash=None): + if not new_hash: + return sccd + + NS = dict(s="http://schemas.microsoft.com/appx/2016/sccd") + with open(sccd, "rb") as f: + xml = ET.parse(f) + + pfn = get_packagefamilyname(APPX_DATA["Name"], APPX_DATA["Publisher"]) + + ae = xml.find("s:AuthorizedEntities", NS) + ae.clear() + + e = ET.SubElement(ae, ET.QName(NS["s"], "AuthorizedEntity")) + e.set("AppPackageFamilyName", pfn) + e.set("CertificateSignatureHash", new_hash) + + for e in xml.findall("s:Catalog", NS): + e.text = "FFFF" + + sccd = ns.temp / sccd.name + sccd.parent.mkdir(parents=True, exist_ok=True) + with open(sccd, "wb") as f: + xml.write(f, encoding="utf-8") + + return sccd + + + at public +def get_appx_layout(ns): + if not ns.include_appxmanifest: + return + + yield "AppxManifest.xml", ns.temp / "AppxManifest.xml" + yield "_resources.xml", ns.temp / "_resources.xml" + icons = ns.source / "PC" / "icons" + yield "_resources/pythonx44.png", icons / "pythonx44.png" + yield "_resources/pythonx44$targetsize-44_altform-unplated.png", icons / "pythonx44.png" + yield "_resources/pythonx50.png", icons / "pythonx50.png" + yield "_resources/pythonx50$targetsize-50_altform-unplated.png", icons / "pythonx50.png" + yield "_resources/pythonx150.png", icons / "pythonx150.png" + yield "_resources/pythonx150$targetsize-150_altform-unplated.png", icons / "pythonx150.png" + yield "_resources/pythonwx44.png", icons / "pythonwx44.png" + yield "_resources/pythonwx44$targetsize-44_altform-unplated.png", icons / "pythonwx44.png" + yield "_resources/pythonwx150.png", icons / "pythonwx150.png" + yield "_resources/pythonwx150$targetsize-150_altform-unplated.png", icons / "pythonwx150.png" + sccd = ns.source / SCCD_FILENAME + if sccd.is_file(): + # This should only be set for side-loading purposes. + sccd = _fixup_sccd(ns, sccd, os.getenv("APPX_DATA_SHA256")) + yield sccd.name, sccd + + +def find_or_add(xml, element, attr=None, always_add=False): + if always_add: + e = None + else: + q = element + if attr: + q += "[@{}='{}']".format(*attr) + e = xml.find(q, APPXMANIFEST_NS) + if e is None: + prefix, _, name = element.partition(":") + name = ET.QName(APPXMANIFEST_NS[prefix or ""], name) + e = ET.SubElement(xml, name) + if attr: + e.set(*attr) + return e + + +def _get_app(xml, appid): + if appid: + app = xml.find( + "m:Applications/m:Application[@Id='{}']".format(appid), APPXMANIFEST_NS + ) + if app is None: + raise LookupError(appid) + else: + app = xml + return app + + +def add_visual(xml, appid, data): + app = _get_app(xml, appid) + e = find_or_add(app, "uap:VisualElements") + for i in data.items(): + e.set(*i) + return e + + +def add_alias(xml, appid, alias, subsystem="windows"): + app = _get_app(xml, appid) + e = find_or_add(app, "m:Extensions") + e = find_or_add(e, "uap5:Extension", ("Category", "windows.appExecutionAlias")) + e = find_or_add(e, "uap5:AppExecutionAlias") + e.set(ET.QName(APPXMANIFEST_NS["desktop4"], "Subsystem"), subsystem) + e = find_or_add(e, "uap5:ExecutionAlias", ("Alias", alias)) + + +def add_file_type(xml, appid, name, suffix, parameters='"%1"'): + app = _get_app(xml, appid) + e = find_or_add(app, "m:Extensions") + e = find_or_add(e, "uap3:Extension", ("Category", "windows.fileTypeAssociation")) + e = find_or_add(e, "uap3:FileTypeAssociation", ("Name", name)) + e.set("Parameters", parameters) + e = find_or_add(e, "uap:SupportedFileTypes") + if isinstance(suffix, str): + suffix = [suffix] + for s in suffix: + ET.SubElement(e, ET.QName(APPXMANIFEST_NS["uap"], "FileType")).text = s + + +def add_application( + ns, xml, appid, executable, aliases, visual_element, subsystem, file_types +): + node = xml.find("m:Applications", APPXMANIFEST_NS) + suffix = "_d.exe" if ns.debug else ".exe" + app = ET.SubElement( + node, + ET.QName(APPXMANIFEST_NS[""], "Application"), + { + "Id": appid, + "Executable": executable + suffix, + "EntryPoint": "Windows.FullTrustApplication", + ET.QName(APPXMANIFEST_NS["desktop4"], "SupportsMultipleInstances"): "true", + }, + ) + if visual_element: + add_visual(app, None, visual_element) + for alias in aliases: + add_alias(app, None, alias + suffix, subsystem) + if file_types: + add_file_type(app, None, *file_types) + return app + + +def _get_registry_entries(ns, root="", d=None): + r = root if root else PureWindowsPath("") + if d is None: + d = REGISTRY + for key, value in d.items(): + if key == "_condition": + continue + elif isinstance(value, dict): + cond = value.get("_condition") + if cond and not cond(ns): + continue + fullkey = r + for part in PureWindowsPath(key).parts: + fullkey /= part + if len(fullkey.parts) > 1: + yield str(fullkey), None, None + yield from _get_registry_entries(ns, fullkey, value) + elif len(r.parts) > 1: + yield str(r), key, value + + +def add_registry_entries(ns, xml): + e = find_or_add(xml, "m:Extensions") + e = find_or_add(e, "rescap4:Extension") + e.set("Category", "windows.classicAppCompatKeys") + e.set("EntryPoint", "Windows.FullTrustApplication") + e = ET.SubElement(e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKeys")) + for name, valuename, value in _get_registry_entries(ns): + k = ET.SubElement( + e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKey") + ) + k.set("Name", name) + if value: + k.set("ValueName", valuename) + k.set("Value", value) + k.set("ValueType", "REG_SZ") + + +def disable_registry_virtualization(xml): + e = find_or_add(xml, "m:Properties") + e = find_or_add(e, "desktop6:RegistryWriteVirtualization") + e.text = "disabled" + e = find_or_add(xml, "m:Capabilities") + e = find_or_add(e, "rescap:Capability", ("Name", "unvirtualizedResources")) + + + at public +def get_appxmanifest(ns): + for k, v in APPXMANIFEST_NS.items(): + ET.register_namespace(k, v) + ET.register_namespace("", APPXMANIFEST_NS["m"]) + + xml = ET.parse(io.StringIO(APPXMANIFEST_TEMPLATE)) + NS = APPXMANIFEST_NS + QN = ET.QName + + node = xml.find("m:Identity", NS) + for k in node.keys(): + value = APPX_DATA.get(k) + if value: + node.set(k, value) + + for node in xml.find("m:Properties", NS): + value = APPX_DATA.get(node.tag.rpartition("}")[2]) + if value: + node.text = value + + winver = sys.getwindowsversion()[:3] + if winver < (10, 0, 17763): + winver = 10, 0, 17763 + find_or_add(xml, "m:Dependencies/m:TargetDeviceFamily").set( + "MaxVersionTested", "{}.{}.{}.0".format(*winver) + ) + + if winver > (10, 0, 17763): + disable_registry_virtualization(xml) + + app = add_application( + ns, + xml, + "Python", + "python", + ["python", "python{}".format(VER_MAJOR), "python{}".format(VER_DOT)], + PYTHON_VE_DATA, + "console", + ("python.file", [".py"]), + ) + + add_application( + ns, + xml, + "PythonW", + "pythonw", + ["pythonw", "pythonw{}".format(VER_MAJOR), "pythonw{}".format(VER_DOT)], + PYTHONW_VE_DATA, + "windows", + ("python.windowedfile", [".pyw"]), + ) + + if ns.include_pip and ns.include_launchers: + add_application( + ns, + xml, + "Pip", + "pip", + ["pip", "pip{}".format(VER_MAJOR), "pip{}".format(VER_DOT)], + PIP_VE_DATA, + "console", + ("python.wheel", [".whl"], 'install "%1"'), + ) + + if ns.include_idle and ns.include_launchers: + add_application( + ns, + xml, + "Idle", + "idle", + ["idle", "idle{}".format(VER_MAJOR), "idle{}".format(VER_DOT)], + IDLE_VE_DATA, + "windows", + None, + ) + + if (ns.source / SCCD_FILENAME).is_file(): + add_registry_entries(ns, xml) + node = xml.find("m:Capabilities", NS) + node = ET.SubElement(node, QN(NS["uap4"], "CustomCapability")) + node.set("Name", "Microsoft.classicAppCompat_8wekyb3d8bbwe") + + buffer = io.BytesIO() + xml.write(buffer, encoding="utf-8", xml_declaration=True) + return buffer.getbuffer() + + + at public +def get_resources_xml(ns): + return RESOURCES_XML_TEMPLATE.encode("utf-8") diff --git a/PC/layout/support/catalog.py b/PC/layout/support/catalog.py new file mode 100644 index 000000000000..43121187ed18 --- /dev/null +++ b/PC/layout/support/catalog.py @@ -0,0 +1,44 @@ +""" +File generation for catalog signing non-binary contents. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import sys + +__all__ = ["PYTHON_CAT_NAME", "PYTHON_CDF_NAME"] + + +def public(f): + __all__.append(f.__name__) + return f + + +PYTHON_CAT_NAME = "python.cat" +PYTHON_CDF_NAME = "python.cdf" + + +CATALOG_TEMPLATE = r"""[CatalogHeader] +Name={target.stem}.cat +ResultDir={target.parent} +PublicVersion=1 +CatalogVersion=2 +HashAlgorithms=SHA256 +PageHashes=false +EncodingType= + +[CatalogFiles] +""" + + +def can_sign(file): + return file.is_file() and file.stat().st_size + + + at public +def write_catalog(target, files): + with target.open("w", encoding="utf-8") as cat: + cat.write(CATALOG_TEMPLATE.format(target=target)) + cat.writelines("{}={}\n".format(n, f) for n, f in files if can_sign(f)) diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py new file mode 100644 index 000000000000..88ea410b340e --- /dev/null +++ b/PC/layout/support/constants.py @@ -0,0 +1,28 @@ +""" +Constants for generating the layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import struct +import sys + +VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = struct.pack(">i", sys.hexversion) +VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4 +VER_NAME = {"alpha": "a", "beta": "b", "rc": "rc"}.get( + sys.version_info.releaselevel, "" +) +VER_SERIAL = sys.version_info.serial if VER_NAME else "" +VER_DOT = "{}.{}".format(VER_MAJOR, VER_MINOR) + +PYTHON_DLL_NAME = "python{}{}.dll".format(VER_MAJOR, VER_MINOR) +PYTHON_STABLE_DLL_NAME = "python{}.dll".format(VER_MAJOR) +PYTHON_ZIP_NAME = "python{}{}.zip".format(VER_MAJOR, VER_MINOR) +PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR) + +PYTHON_CHM_NAME = "python{}{}{}{}{}.chm".format( + VER_MAJOR, VER_MINOR, VER_MICRO, VER_NAME, VER_SERIAL +) + +IS_X64 = sys.maxsize > 2 ** 32 diff --git a/PC/layout/support/distutils.command.bdist_wininst.py b/PC/layout/support/distutils.command.bdist_wininst.py new file mode 100644 index 000000000000..6e9b49fe42df --- /dev/null +++ b/PC/layout/support/distutils.command.bdist_wininst.py @@ -0,0 +1,25 @@ +"""distutils.command.bdist_wininst + +Suppress the 'bdist_wininst' command, while still allowing +setuptools to import it without breaking.""" + +from distutils.core import Command +from distutils.errors import DistutilsPlatformError + + +class bdist_wininst(Command): + description = "create an executable installer for MS Windows" + + # Marker for tests that we have the unsupported bdist_wininst + _unsupported = True + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + raise DistutilsPlatformError( + "bdist_wininst is not supported in this Python distribution" + ) diff --git a/PC/layout/support/filesets.py b/PC/layout/support/filesets.py new file mode 100644 index 000000000000..47f727c05784 --- /dev/null +++ b/PC/layout/support/filesets.py @@ -0,0 +1,100 @@ +""" +File sets and globbing helper for make_layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import os + + +class FileStemSet: + def __init__(self, *patterns): + self._names = set() + self._prefixes = [] + self._suffixes = [] + for p in map(os.path.normcase, patterns): + if p.endswith("*"): + self._prefixes.append(p[:-1]) + elif p.startswith("*"): + self._suffixes.append(p[1:]) + else: + self._names.add(p) + + def _make_name(self, f): + return os.path.normcase(f.stem) + + def __contains__(self, f): + bn = self._make_name(f) + return ( + bn in self._names + or any(map(bn.startswith, self._prefixes)) + or any(map(bn.endswith, self._suffixes)) + ) + + +class FileNameSet(FileStemSet): + def _make_name(self, f): + return os.path.normcase(f.name) + + +class FileSuffixSet: + def __init__(self, *patterns): + self._names = set() + self._prefixes = [] + self._suffixes = [] + for p in map(os.path.normcase, patterns): + if p.startswith("*."): + self._names.add(p[1:]) + elif p.startswith("*"): + self._suffixes.append(p[1:]) + elif p.endswith("*"): + self._prefixes.append(p[:-1]) + elif p.startswith("."): + self._names.add(p) + else: + self._names.add("." + p) + + def _make_name(self, f): + return os.path.normcase(f.suffix) + + def __contains__(self, f): + bn = self._make_name(f) + return ( + bn in self._names + or any(map(bn.startswith, self._prefixes)) + or any(map(bn.endswith, self._suffixes)) + ) + + +def _rglob(root, pattern, condition): + dirs = [root] + recurse = pattern[:3] in {"**/", "**\\"} + if recurse: + pattern = pattern[3:] + + while dirs: + d = dirs.pop(0) + if recurse: + dirs.extend( + filter( + condition, (type(root)(f2) for f2 in os.scandir(d) if f2.is_dir()) + ) + ) + yield from ( + (f.relative_to(root), f) + for f in d.glob(pattern) + if f.is_file() and condition(f) + ) + + +def _return_true(f): + return True + + +def rglob(root, patterns, condition=None): + if isinstance(patterns, tuple): + for p in patterns: + yield from _rglob(root, p, condition or _return_true) + else: + yield from _rglob(root, patterns, condition or _return_true) diff --git a/PC/layout/support/logging.py b/PC/layout/support/logging.py new file mode 100644 index 000000000000..30869b949a1c --- /dev/null +++ b/PC/layout/support/logging.py @@ -0,0 +1,93 @@ +""" +Logging support for make_layout. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + +import logging +import sys + +__all__ = [] + +LOG = None +HAS_ERROR = False + + +def public(f): + __all__.append(f.__name__) + return f + + + at public +def configure_logger(ns): + global LOG + if LOG: + return + + LOG = logging.getLogger("make_layout") + LOG.level = logging.DEBUG + + if ns.v: + s_level = max(logging.ERROR - ns.v * 10, logging.DEBUG) + f_level = max(logging.WARNING - ns.v * 10, logging.DEBUG) + else: + s_level = logging.ERROR + f_level = logging.INFO + + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter("{levelname:8s} {message}", style="{")) + handler.setLevel(s_level) + LOG.addHandler(handler) + + if ns.log: + handler = logging.FileHandler(ns.log, encoding="utf-8", delay=True) + handler.setFormatter( + logging.Formatter("[{asctime}]{levelname:8s}: {message}", style="{") + ) + handler.setLevel(f_level) + LOG.addHandler(handler) + + +class BraceMessage: + def __init__(self, fmt, *args, **kwargs): + self.fmt = fmt + self.args = args + self.kwargs = kwargs + + def __str__(self): + return self.fmt.format(*self.args, **self.kwargs) + + + at public +def log_debug(msg, *args, **kwargs): + return LOG.debug(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_info(msg, *args, **kwargs): + return LOG.info(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_warning(msg, *args, **kwargs): + return LOG.warning(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_error(msg, *args, **kwargs): + global HAS_ERROR + HAS_ERROR = True + return LOG.error(BraceMessage(msg, *args, **kwargs)) + + + at public +def log_exception(msg, *args, **kwargs): + global HAS_ERROR + HAS_ERROR = True + return LOG.exception(BraceMessage(msg, *args, **kwargs)) + + + at public +def error_was_logged(): + return HAS_ERROR diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py new file mode 100644 index 000000000000..76d9e34e1f46 --- /dev/null +++ b/PC/layout/support/options.py @@ -0,0 +1,122 @@ +""" +List of optional components. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + +OPTIONS = { + "stable": {"help": "stable ABI stub"}, + "pip": {"help": "pip"}, + "distutils": {"help": "distutils"}, + "tcltk": {"help": "Tcl, Tk and tkinter"}, + "idle": {"help": "Idle"}, + "tests": {"help": "test suite"}, + "tools": {"help": "tools"}, + "venv": {"help": "venv"}, + "dev": {"help": "headers and libs"}, + "symbols": {"help": "symbols"}, + "bdist-wininst": {"help": "bdist_wininst support"}, + "underpth": {"help": "a python._pth file", "not-in-all": True}, + "launchers": {"help": "specific launchers"}, + "appxmanifest": {"help": "an appxmanifest"}, + "props": {"help": "a python.props file"}, + "chm": {"help": "the CHM documentation"}, + "html-doc": {"help": "the HTML documentation"}, +} + + +PRESETS = { + "appx": { + "help": "APPX package", + "options": [ + "stable", + "pip", + "distutils", + "tcltk", + "idle", + "venv", + "dev", + "launchers", + "appxmanifest", + # XXX: Disabled for now "precompile", + ], + }, + "nuget": { + "help": "nuget package", + "options": ["stable", "pip", "distutils", "dev", "props"], + }, + "default": { + "help": "development kit package", + "options": [ + "stable", + "pip", + "distutils", + "tcltk", + "idle", + "tests", + "tools", + "venv", + "dev", + "symbols", + "bdist-wininst", + "chm", + ], + }, + "embed": { + "help": "embeddable package", + "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"], + }, +} + + + at public +def get_argparse_options(): + for opt, info in OPTIONS.items(): + help = "When specified, includes {}".format(info["help"]) + if info.get("not-in-all"): + help = "{}. Not affected by --include-all".format(help) + + yield "--include-{}".format(opt), help + + for opt, info in PRESETS.items(): + help = "When specified, includes default options for {}".format(info["help"]) + yield "--preset-{}".format(opt), help + + +def ns_get(ns, key, default=False): + return getattr(ns, key.replace("-", "_"), default) + + +def ns_set(ns, key, value=True): + k1 = key.replace("-", "_") + k2 = "include_{}".format(k1) + if hasattr(ns, k2): + setattr(ns, k2, value) + elif hasattr(ns, k1): + setattr(ns, k1, value) + else: + raise AttributeError("no argument named '{}'".format(k1)) + + + at public +def update_presets(ns): + for preset, info in PRESETS.items(): + if ns_get(ns, "preset-{}".format(preset)): + for opt in info["options"]: + ns_set(ns, opt) + + if ns.include_all: + for opt in OPTIONS: + if OPTIONS[opt].get("not-in-all"): + continue + ns_set(ns, opt) diff --git a/PC/layout/support/pip.py b/PC/layout/support/pip.py new file mode 100644 index 000000000000..369a923ce139 --- /dev/null +++ b/PC/layout/support/pip.py @@ -0,0 +1,79 @@ +""" +Extraction and file list generation for pip. +""" + +__author__ = "Steve Dower " +__version__ = "3.8" + + +import os +import shutil +import subprocess +import sys + +__all__ = [] + + +def public(f): + __all__.append(f.__name__) + return f + + + at public +def get_pip_dir(ns): + if ns.copy: + if ns.zip_lib: + return ns.copy / "packages" + return ns.copy / "Lib" / "site-packages" + else: + return ns.temp / "packages" + + + at public +def extract_pip_files(ns): + dest = get_pip_dir(ns) + dest.mkdir(parents=True, exist_ok=True) + + src = ns.source / "Lib" / "ensurepip" / "_bundled" + + ns.temp.mkdir(parents=True, exist_ok=True) + wheels = [shutil.copy(whl, ns.temp) for whl in src.glob("*.whl")] + search_path = os.pathsep.join(wheels) + if os.environ.get("PYTHONPATH"): + search_path += ";" + os.environ["PYTHONPATH"] + + env = os.environ.copy() + env["PYTHONPATH"] = search_path + + output = subprocess.check_output( + [ + sys.executable, + "-m", + "pip", + "--no-color", + "install", + "pip", + "setuptools", + "--upgrade", + "--target", + str(dest), + "--no-index", + "--no-cache-dir", + "-f", + str(src), + "--only-binary", + ":all:", + ], + env=env, + ) + + try: + shutil.rmtree(dest / "bin") + except OSError: + pass + + for file in wheels: + try: + os.remove(file) + except OSError: + pass diff --git a/PC/layout/support/props.py b/PC/layout/support/props.py new file mode 100644 index 000000000000..3a047d215058 --- /dev/null +++ b/PC/layout/support/props.py @@ -0,0 +1,110 @@ +""" +Provides .props file. +""" + +import os + +from .constants import * + +__all__ = ["PYTHON_PROPS_NAME"] + + +def public(f): + __all__.append(f.__name__) + return f + + +PYTHON_PROPS_NAME = "python.props" + +PROPS_DATA = { + "PYTHON_TAG": VER_DOT, + "PYTHON_VERSION": os.getenv("PYTHON_NUSPEC_VERSION"), + "PYTHON_PLATFORM": os.getenv("PYTHON_PROPS_PLATFORM"), + "PYTHON_TARGET": "", +} + +if not PROPS_DATA["PYTHON_VERSION"]: + if VER_NAME: + PROPS_DATA["PYTHON_VERSION"] = "{}.{}-{}{}".format( + VER_DOT, VER_MICRO, VER_NAME, VER_SERIAL + ) + else: + PROPS_DATA["PYTHON_VERSION"] = "{}.{}".format(VER_DOT, VER_MICRO) + +if not PROPS_DATA["PYTHON_PLATFORM"]: + PROPS_DATA["PYTHON_PLATFORM"] = "x64" if IS_X64 else "Win32" + +PROPS_DATA["PYTHON_TARGET"] = "_GetPythonRuntimeFilesDependsOn{}{}_{}".format( + VER_MAJOR, VER_MINOR, PROPS_DATA["PYTHON_PLATFORM"] +) + +PROPS_TEMPLATE = r""" + + + $([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python_d.exe") + $([msbuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), "python.exe") + $(PythonHome)\include + $(PythonHome)\libs + {PYTHON_TAG} + {PYTHON_VERSION} + + true + false + false + false + + {PYTHON_TARGET};$(GetPythonRuntimeFilesDependsOn) + + + + + $(PythonInclude);%(AdditionalIncludeDirectories) + MultiThreadedDLL + + + $(PythonLibs);%(AdditionalLibraryDirectories) + + + + + + + + <_PythonRuntimeExe Include="$(PythonHome)\python*.dll" /> + <_PythonRuntimeExe Include="$(PythonHome)\python*.exe" Condition="$(IncludePythonExe) == 'true'" /> + <_PythonRuntimeExe> + %(Filename)%(Extension) + + <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.pyd" /> + <_PythonRuntimeDlls Include="$(PythonHome)\DLLs\*.dll" /> + <_PythonRuntimeDlls> + DLLs\%(Filename)%(Extension) + + <_PythonRuntimeLib Include="$(PythonHome)\Lib\**\*" Exclude="$(PythonHome)\Lib\**\*.pyc;$(PythonHome)\Lib\site-packages\**\*" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\distutils\**\*" Condition="$(IncludeDistutils) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\lib2to3\**\*" Condition="$(IncludeLib2To3) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\ensurepip\**\*" Condition="$(IncludeVEnv) != 'true'" /> + <_PythonRuntimeLib Remove="$(PythonHome)\Lib\venv\**\*" Condition="$(IncludeVEnv) != 'true'" /> + <_PythonRuntimeLib> + Lib\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + +""" + + + at public +def get_props_layout(ns): + if ns.include_all or ns.include_props: + yield "python.props", ns.temp / "python.props" + + + at public +def get_props(ns): + # TODO: Filter contents of props file according to included/excluded items + props = PROPS_TEMPLATE.format_map(PROPS_DATA) + return props.encode("utf-8") diff --git a/Tools/nuget/python.props b/PC/layout/support/python.props similarity index 100% rename from Tools/nuget/python.props rename to PC/layout/support/python.props diff --git a/PC/pylauncher.rc b/PC/pylauncher.rc index 3da3445f5fc4..92987af7138d 100644 --- a/PC/pylauncher.rc +++ b/PC/pylauncher.rc @@ -7,6 +7,11 @@ #include 1 RT_MANIFEST "python.manifest" +#if defined(PY_ICON) +1 ICON DISCARDABLE "icons\python.ico" +#elif defined(PYW_ICON) +1 ICON DISCARDABLE "icons\pythonw.ico" +#else 1 ICON DISCARDABLE "icons\launcher.ico" 2 ICON DISCARDABLE "icons\py.ico" 3 ICON DISCARDABLE "icons\pyc.ico" @@ -14,6 +19,7 @@ 5 ICON DISCARDABLE "icons\python.ico" 6 ICON DISCARDABLE "icons\pythonw.ico" 7 ICON DISCARDABLE "icons\setup.ico" +#endif ///////////////////////////////////////////////////////////////////////////// // diff --git a/PC/python_uwp.cpp b/PC/python_uwp.cpp new file mode 100644 index 000000000000..1658d05994bb --- /dev/null +++ b/PC/python_uwp.cpp @@ -0,0 +1,226 @@ +/* Main program when embedded in a UWP application on Windows */ + +#include "Python.h" +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#ifdef PYTHONW +#ifdef _DEBUG +const wchar_t *PROGNAME = L"pythonw_d.exe"; +#else +const wchar_t *PROGNAME = L"pythonw.exe"; +#endif +#else +#ifdef _DEBUG +const wchar_t *PROGNAME = L"python_d.exe"; +#else +const wchar_t *PROGNAME = L"python.exe"; +#endif +#endif + +static void +set_user_base() +{ + wchar_t envBuffer[2048]; + try { + const auto appData = winrt::Windows::Storage::ApplicationData::Current(); + if (appData) { + const auto localCache = appData.LocalCacheFolder(); + if (localCache) { + auto path = localCache.Path(); + if (!path.empty() && + !wcscpy_s(envBuffer, path.c_str()) && + !wcscat_s(envBuffer, L"\\local-packages") + ) { + _wputenv_s(L"PYTHONUSERBASE", envBuffer); + } + } + } + } catch (...) { + } +} + +static const wchar_t * +get_argv0(const wchar_t *argv0) +{ + winrt::hstring installPath; + const wchar_t *launcherPath; + wchar_t *buffer; + size_t len; + + launcherPath = _wgetenv(L"__PYVENV_LAUNCHER__"); + if (launcherPath && launcherPath[0]) { + len = wcslen(launcherPath) + 1; + buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); + if (!buffer) { + Py_FatalError("out of memory"); + return NULL; + } + if (wcscpy_s(buffer, len, launcherPath)) { + Py_FatalError("failed to copy to buffer"); + return NULL; + } + return buffer; + } + + try { + const auto package = winrt::Windows::ApplicationModel::Package::Current(); + if (package) { + const auto install = package.InstalledLocation(); + if (install) { + installPath = install.Path(); + } + } + } + catch (...) { + } + + if (!installPath.empty()) { + len = installPath.size() + wcslen(PROGNAME) + 2; + } else { + len = wcslen(argv0) + wcslen(PROGNAME) + 1; + } + + buffer = (wchar_t *)malloc(sizeof(wchar_t) * len); + if (!buffer) { + Py_FatalError("out of memory"); + return NULL; + } + + if (!installPath.empty()) { + if (wcscpy_s(buffer, len, installPath.c_str())) { + Py_FatalError("failed to copy to buffer"); + return NULL; + } + if (wcscat_s(buffer, len, L"\\")) { + Py_FatalError("failed to concatenate backslash"); + return NULL; + } + } else { + if (wcscpy_s(buffer, len, argv0)) { + Py_FatalError("failed to copy argv[0]"); + return NULL; + } + + wchar_t *name = wcsrchr(buffer, L'\\'); + if (name) { + name[1] = L'\0'; + } else { + buffer[0] = L'\0'; + } + } + + if (wcscat_s(buffer, len, PROGNAME)) { + Py_FatalError("failed to concatenate program name"); + return NULL; + } + + return buffer; +} + +static wchar_t * +get_process_name() +{ + DWORD bufferLen = MAX_PATH; + DWORD len = bufferLen; + wchar_t *r = NULL; + + while (!r) { + r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); + if (!r) { + Py_FatalError("out of memory"); + return NULL; + } + len = GetModuleFileNameW(NULL, r, bufferLen); + if (len == 0) { + free((void *)r); + return NULL; + } else if (len == bufferLen && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(r); + r = NULL; + bufferLen *= 2; + } + } + + return r; +} + +int +wmain(int argc, wchar_t **argv) +{ + const wchar_t **new_argv; + int new_argc; + const wchar_t *exeName; + + new_argc = argc; + new_argv = (const wchar_t**)malloc(sizeof(wchar_t *) * (argc + 2)); + if (new_argv == NULL) { + Py_FatalError("out of memory"); + return -1; + } + + exeName = get_process_name(); + + new_argv[0] = get_argv0(exeName ? exeName : argv[0]); + for (int i = 1; i < argc; ++i) { + new_argv[i] = argv[i]; + } + + set_user_base(); + + if (exeName) { + const wchar_t *p = wcsrchr(exeName, L'\\'); + if (p) { + const wchar_t *moduleName = NULL; + if (*p++ == L'\\') { + if (wcsnicmp(p, L"pip", 3) == 0) { + moduleName = L"pip"; + _wputenv_s(L"PIP_USER", L"true"); + } + else if (wcsnicmp(p, L"idle", 4) == 0) { + moduleName = L"idlelib"; + } + } + + if (moduleName) { + new_argc += 2; + for (int i = argc; i >= 1; --i) { + new_argv[i + 2] = new_argv[i]; + } + new_argv[1] = L"-m"; + new_argv[2] = moduleName; + } + } + } + + /* Override program_full_path from here so that + sys.executable is set correctly. */ + _Py_SetProgramFullPath(new_argv[0]); + + int result = Py_Main(new_argc, (wchar_t **)new_argv); + + free((void *)exeName); + free((void *)new_argv); + + return result; +} + +#ifdef PYTHONW + +int WINAPI wWinMain( + HINSTANCE hInstance, /* handle to current instance */ + HINSTANCE hPrevInstance, /* handle to previous instance */ + LPWSTR lpCmdLine, /* pointer to command line */ + int nCmdShow /* show state of window */ +) +{ + return wmain(__argc, __wargv); +} + +#endif diff --git a/PC/store_info.txt b/PC/store_info.txt new file mode 100644 index 000000000000..e252692b73cc --- /dev/null +++ b/PC/store_info.txt @@ -0,0 +1,146 @@ +# Overview + +NOTE: This file requires more content. + +Since Python 3.7.2, releases have been made through the Microsoft Store +to allow easy installation on Windows 10.0.17763.0 and later. + +# Building + +To build the store package, the PC/layout script should be used. +Execute the directory with the build of Python to package, and pass +"-h" for full command-line options. + +To sideload test builds, you will need a local certificate. +Instructions are available at +https://docs.microsoft.com/windows/uwp/packaging/create-certificate-package-signing. + +After exporting your certificate, you will need the subject name and +SHA256 hash. The `certutil -dump ` command will display this +information. + +To build for sideloading, use these commands in PowerShell: + +``` +$env:APPX_DATA_PUBLISHER= +$env:APPX_DATA_SHA256= +$env:SigningCertificateFile= + +python PC/layout --copy --include-appxmanifest +Tools/msi/make_appx.ps1 python.msix -sign + +Add-AppxPackage python.msix +``` + +(Note that only the last command requires PowerShell, and the others +can be used from Command Prompt. You can also double-click to install +the final package.) + +To build for publishing to the Store, use these commands: + +``` +$env:APPX_DATA_PUBLISHER = $null +$env:APPX_DATA_SHA256 = $null + +python PC/layout --copy --preset-appxmanifest --precompile +Tools/msi/make_appx.ps1 python.msix +``` + +Note that this package cannot be installed locally. It may only be +added to a submission for the store. + + +# Submission Metadata + +This file contains the text that we use to fill out the store listing +for the Microsoft Store. It needs to be entered manually when creating +a new submission via the dashboard at +https://partner.microsoft.com/dashboard. + +We keep it here for convenience and to allow it to be updated via pull +requests. + +## Title + +Python 3.7 + +## Short Title + +Python + +## Description + +Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python?s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms. + +The Python interpreter and the extensive standard library are freely available in source or binary form for all major platforms from the Python Web site, https://www.python.org/, and may be freely distributed. The same site also contains distributions of and pointers to many free third party Python modules, programs and tools, and additional documentation. + +The Python interpreter is easily extended with new functions and data types implemented in C or C++ (or other languages callable from C). Python is also suitable as an extension language for customizable applications. + +## ShortDescription + +The Python 3.7 interpreter and runtime. + +## Copyright Trademark Information + +(c) Python Software Foundation + +## Additional License Terms + +Visit https://docs.python.org/3.7/license.html for latest license terms. + +PSF LICENSE AGREEMENT FOR PYTHON 3.7 + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + the Individual or Organization ("Licensee") accessing and otherwise using Python + 3.7 software in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby + grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + analyze, test, perform and/or display publicly, prepare derivative works, + distribute, and otherwise use Python 3.7 alone or in any derivative + version, provided, however, that PSF's License Agreement and PSF's notice of + copyright, i.e., "Copyright ? 2001-2018 Python Software Foundation; All Rights + Reserved" are retained in Python 3.7 alone or in any derivative version + prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on or + incorporates Python 3.7 or any part thereof, and wants to make the + derivative work available to others as provided herein, then Licensee hereby + agrees to include in any such work a brief summary of the changes made to Python + 3.7. + +4. PSF is making Python 3.7 available to Licensee on an "AS IS" basis. + PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF + EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR + WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE + USE OF PYTHON 3.7 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7 + FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7, OR ANY DERIVATIVE + THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material breach of + its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any relationship + of agency, partnership, or joint venture between PSF and Licensee. This License + Agreement does not grant permission to use PSF trademarks or trade name in a + trademark sense to endorse or promote products or services of Licensee, or any + third party. + +8. By copying, installing or otherwise using Python 3.7, Licensee agrees + to be bound by the terms and conditions of this License Agreement. + +## Features + +* Easy to install Python runtime +* Supported by core CPython team +* Find Python, Pip and Idle on PATH + +## Search Terms + +* Python +* Scripting +* Interpreter + diff --git a/PCbuild/_tkinter.vcxproj b/PCbuild/_tkinter.vcxproj index 95e3cd50eca5..bd61c0d4f689 100644 --- a/PCbuild/_tkinter.vcxproj +++ b/PCbuild/_tkinter.vcxproj @@ -95,4 +95,10 @@ + + + + + + \ No newline at end of file diff --git a/PCbuild/find_msbuild.bat b/PCbuild/find_msbuild.bat index 57512a01927e..a2810f09c45e 100644 --- a/PCbuild/find_msbuild.bat +++ b/PCbuild/find_msbuild.bat @@ -29,6 +29,16 @@ @where msbuild > "%TEMP%\msbuild.loc" 2> nul && set /P MSBUILD= < "%TEMP%\msbuild.loc" & del "%TEMP%\msbuild.loc" @if exist "%MSBUILD%" set MSBUILD="%MSBUILD%" & (set _Py_MSBuild_Source=PATH) & goto :found + at rem VS 2017 and later provide vswhere.exe, which can be used + at if not exist "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" goto :skip_vswhere + at set _Py_MSBuild_Root= + at for /F "tokens=*" %%i in ('"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -property installationPath -latest') DO @(set _Py_MSBuild_Root=%%i\MSBuild) + at if not defined _Py_MSBuild_Root goto :skip_vswhere + at for %%j in (Current 15.0) DO @if exist "%_Py_MSBuild_Root%\%%j\Bin\msbuild.exe" (set MSBUILD="%_Py_MSBuild_Root%\%%j\Bin\msbuild.exe") + at set _Py_MSBuild_Root= + at if defined MSBUILD @if exist %MSBUILD% (set _Py_MSBuild_Source=Visual Studio installation) & goto :found +:skip_vswhere + @rem VS 2017 sets exactly one install as the "main" install, so we may find MSBuild in there. @reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v 15.0 /reg:32 >nul 2>nul @if NOT ERRORLEVEL 1 @for /F "tokens=1,2*" %%i in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v 15.0 /reg:32') DO @( diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj index 9e103e12103f..6bf1667e39f8 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -52,6 +52,8 @@ + + @@ -70,6 +72,7 @@ + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index 59b3861ed406..c212d9f8f32c 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -93,6 +93,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_queue", "_queue.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "liblzma.vcxproj", "{12728250-16EC-4DC6-94D7-E21DD88947F8}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python_uwp", "python_uwp.vcxproj", "{9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "venvlauncher", "venvlauncher.vcxproj", "{494BAC80-A60C-43A9-99E7-ACB691CE2C4D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "venvwlauncher", "venvwlauncher.vcxproj", "{FDB84CBB-2FB6-47C8-A2D6-091E0833239D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonw_uwp", "pythonw_uwp.vcxproj", "{AB603547-1E2A-45B3-9E09-B04596006393}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -693,6 +701,70 @@ Global {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.Build.0 = Release|Win32 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.ActiveCfg = Release|x64 {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.Build.0 = Release|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|Win32.ActiveCfg = Debug|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|Win32.Build.0 = Debug|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|x64.ActiveCfg = Debug|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Debug|x64.Build.0 = Debug|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|Win32.ActiveCfg = Release|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|Win32.Build.0 = Release|Win32 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|x64.ActiveCfg = Release|x64 + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF}.Release|x64.Build.0 = Release|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|Win32.ActiveCfg = Debug|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|Win32.Build.0 = Debug|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|x64.ActiveCfg = Debug|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Debug|x64.Build.0 = Debug|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|Win32.ActiveCfg = Release|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|Win32.Build.0 = Release|Win32 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|x64.ActiveCfg = Release|x64 + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D}.Release|x64.Build.0 = Release|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|Win32.ActiveCfg = Debug|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|Win32.Build.0 = Debug|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|x64.ActiveCfg = Debug|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Debug|x64.Build.0 = Debug|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|Win32.ActiveCfg = Release|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|Win32.Build.0 = Release|Win32 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|x64.ActiveCfg = Release|x64 + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D}.Release|x64.Build.0 = Release|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|Win32.ActiveCfg = Debug|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|Win32.Build.0 = Debug|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|x64.ActiveCfg = Debug|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Debug|x64.Build.0 = Debug|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|Win32.ActiveCfg = Release|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|Win32.Build.0 = Release|Win32 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|x64.ActiveCfg = Release|x64 + {AB603547-1E2A-45B3-9E09-B04596006393}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PCbuild/python.props b/PCbuild/python.props index 09f11d3bba8c..6dbb503b3243 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -77,7 +77,8 @@ --> <_RegistryVersion>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0 at ProductVersion) <_RegistryVersion Condition="$(_RegistryVersion) == ''">$(Registry:HKEY_LOCAL_MACHINE\WOW6432Node\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0 at ProductVersion) - 10.0.17134.0 + 10.0.17763.0 + 10.0.17134.0 10.0.16299.0 10.0.15063.0 10.0.14393.0 diff --git a/PCbuild/python_uwp.vcxproj b/PCbuild/python_uwp.vcxproj new file mode 100644 index 000000000000..af187dd4df30 --- /dev/null +++ b/PCbuild/python_uwp.vcxproj @@ -0,0 +1,86 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {9DE9E23D-C8D4-4817-92A9-920A8B1FE5FF} + + + + + Application + false + Unicode + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + %(PreprocessorDefinitions) + /EHsc /std:c++17 %(AdditionalOptions) + + + windowsapp.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + + + + diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index d19b5f5acf89..2ee88225b62c 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -460,4 +460,19 @@ + + + $(VCInstallDir)\Redist\MSVC\$(VCToolsRedistVersion)\ + $(VCRedistDir)x86\ + $(VCRedistDir)$(Platform)\ + + + + + + + + + + diff --git a/PCbuild/pythonw_uwp.vcxproj b/PCbuild/pythonw_uwp.vcxproj new file mode 100644 index 000000000000..79e105877fbe --- /dev/null +++ b/PCbuild/pythonw_uwp.vcxproj @@ -0,0 +1,86 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {AB603547-1E2A-45B3-9E09-B04596006393} + + + + + Application + false + Unicode + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + + + + PYTHONW;%(PreprocessorDefinitions) + /EHsc /std:c++17 %(AdditionalOptions) + + + windowsapp.lib;%(AdditionalDependencies) + Windows + + + + + + + + + + + + + + {cf7ac3d1-e2df-41d2-bea6-1e2556cdea26} + false + + + + + + diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj new file mode 100644 index 000000000000..295b36304733 --- /dev/null +++ b/PCbuild/venvlauncher.vcxproj @@ -0,0 +1,85 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {494BAC80-A60C-43A9-99E7-ACB691CE2C4D} + venvlauncher + venvlauncher + false + + + + + Application + MultiByte + + + + + + ClCompile + + + + + + + + + _CONSOLE;VENV_REDIRECT;%(PreprocessorDefinitions) + MultiThreaded + + + PY_ICON;%(PreprocessorDefinitions) + + + version.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + + + + diff --git a/PCbuild/venvwlauncher.vcxproj b/PCbuild/venvwlauncher.vcxproj new file mode 100644 index 000000000000..e7ba25da41eb --- /dev/null +++ b/PCbuild/venvwlauncher.vcxproj @@ -0,0 +1,85 @@ +? + + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + Win32 + + + Release + x64 + + + + {FDB84CBB-2FB6-47C8-A2D6-091E0833239D} + venvwlauncher + venvwlauncher + false + + + + + Application + MultiByte + + + + + + ClCompile + + + + + + + + + _WINDOWS;VENV_REDIRECT;%(PreprocessorDefinitions) + MultiThreaded + + + PYW_ICON;%(PreprocessorDefinitions) + + + version.lib;%(AdditionalDependencies) + Windows + + + + + + + + + + + + + + + diff --git a/Tools/msi/buildrelease.bat b/Tools/msi/buildrelease.bat index 4178981195ee..45e189b537f6 100644 --- a/Tools/msi/buildrelease.bat +++ b/Tools/msi/buildrelease.bat @@ -37,6 +37,7 @@ set BUILDX64= set TARGET=Rebuild set TESTTARGETDIR= set PGO=-m test -q --pgo +set BUILDMSI=1 set BUILDNUGET=1 set BUILDZIP=1 @@ -61,6 +62,7 @@ if "%1" EQU "--pgo" (set PGO=%~2) && shift && shift && goto CheckOpts if "%1" EQU "--skip-pgo" (set PGO=) && shift && goto CheckOpts if "%1" EQU "--skip-nuget" (set BUILDNUGET=) && shift && goto CheckOpts if "%1" EQU "--skip-zip" (set BUILDZIP=) && shift && goto CheckOpts +if "%1" EQU "--skip-msi" (set BUILDMSI=) && shift && goto CheckOpts if "%1" NEQ "" echo Invalid option: "%1" && exit /B 1 @@ -174,10 +176,12 @@ if "%OUTDIR_PLAT%" EQU "win32" ( ) set BUILDOPTS=/p:Platform=%1 /p:BuildForRelease=true /p:DownloadUrl=%DOWNLOAD_URL% /p:DownloadUrlBase=%DOWNLOAD_URL_BASE% /p:ReleaseUri=%RELEASE_URI% -%MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true -if errorlevel 1 exit /B -%MSBUILD% "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false -if errorlevel 1 exit /B +if defined BUILDMSI ( + %MSBUILD% "%D%bundle\releaselocal.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=true + if errorlevel 1 exit /B + %MSBUILD% "%D%bundle\releaseweb.wixproj" /t:Rebuild %BUILDOPTS% %CERTOPTS% /p:RebuildAll=false + if errorlevel 1 exit /B +) if defined BUILDZIP ( %MSBUILD% "%D%make_zip.proj" /t:Build %BUILDOPTS% %CERTOPTS% /p:OutputPath="%BUILD%en-us" @@ -214,6 +218,7 @@ echo --skip-build (-B) Do not build Python (just do the installers) echo --skip-doc (-D) Do not build documentation echo --pgo Specify PGO command for x64 installers echo --skip-pgo Build x64 installers without using PGO +echo --skip-msi Do not build executable/MSI packages echo --skip-nuget Do not build Nuget packages echo --skip-zip Do not build embeddable package echo --download Specify the full download URL for MSIs diff --git a/Tools/msi/make_appx.ps1 b/Tools/msi/make_appx.ps1 new file mode 100644 index 000000000000..b3f190e07db8 --- /dev/null +++ b/Tools/msi/make_appx.ps1 @@ -0,0 +1,71 @@ +<# +.Synopsis + Compiles and signs an APPX package +.Description + Given the file listing, ensures all the contents are signed + and builds and signs the final package. +.Parameter mapfile + The location on disk of the text mapping file. +.Parameter msix + The path and name to store the APPX/MSIX. +.Parameter sign + When set, signs the APPX/MSIX. Packages to be published to + the store should not be signed. +.Parameter description + Description to embed in the signature (optional). +.Parameter certname + The name of the certificate to sign with (optional). +.Parameter certsha1 + The SHA1 hash of the certificate to sign with (optional). +#> +param( + [Parameter(Mandatory=$true)][string]$layout, + [Parameter(Mandatory=$true)][string]$msix, + [switch]$sign, + [string]$description, + [string]$certname, + [string]$certsha1, + [string]$certfile +) + +$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; +Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force + +Set-Alias makeappx (Find-Tool "makeappx.exe") -Scope Script +Set-Alias makepri (Find-Tool "makepri.exe") -Scope Script + +$msixdir = Split-Path $msix -Parent +if ($msixdir) { + $msixdir = (mkdir -Force $msixdir).FullName +} else { + $msixdir = Get-Location +} +$msix = Join-Path $msixdir (Split-Path $msix -Leaf) + +pushd $layout +try { + if (Test-Path resources.pri) { + del resources.pri + } + $name = ([xml](gc AppxManifest.xml)).Package.Identity.Name + makepri new /pr . /mn AppxManifest.xml /in $name /cf _resources.xml /of _resources.pri /mf appx /o + if (-not $? -or -not (Test-Path _resources.map.txt)) { + throw "makepri step failed" + } + $lines = gc _resources.map.txt + $lines | ?{ -not ($_ -match '"_resources[\w\.]+?"') } | Out-File _resources.map.txt -Encoding utf8 + makeappx pack /f _resources.map.txt /m AppxManifest.xml /o /p $msix + if (-not $?) { + throw "makeappx step failed" + } +} finally { + popd +} + +if ($sign) { + Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files $msix + + if (-not $?) { + throw "Package signing failed" + } +} diff --git a/Tools/msi/make_cat.ps1 b/Tools/msi/make_cat.ps1 new file mode 100644 index 000000000000..70741439869a --- /dev/null +++ b/Tools/msi/make_cat.ps1 @@ -0,0 +1,34 @@ +<# +.Synopsis + Compiles and signs a catalog file. +.Description + Given the CDF definition file, builds and signs a catalog. +.Parameter catalog + The path to the catalog definition file to compile and + sign. It is assumed that the .cat file will be the same + name with a new extension. +.Parameter description + The description to add to the signature (optional). +.Parameter certname + The name of the certificate to sign with (optional). +.Parameter certsha1 + The SHA1 hash of the certificate to sign with (optional). +#> +param( + [Parameter(Mandatory=$true)][string]$catalog, + [string]$description, + [string]$certname, + [string]$certsha1, + [string]$certfile +) + +$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; +Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force + +Set-Alias MakeCat (Find-Tool "makecat.exe") -Scope Script + +MakeCat $catalog +if (-not $?) { + throw "Catalog compilation failed" +} +Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files @($catalog -replace 'cdf$', 'cat') diff --git a/Tools/msi/make_zip.proj b/Tools/msi/make_zip.proj index 214111734219..125a434e51f4 100644 --- a/Tools/msi/make_zip.proj +++ b/Tools/msi/make_zip.proj @@ -15,11 +15,12 @@ .zip $(OutputPath)\$(TargetName)$(TargetExt) rmdir /q/s "$(IntermediateOutputPath)\zip_$(ArchName)" - "$(PythonExe)" "$(MSBuildThisFileDirectory)\make_zip.py" - $(Arguments) -e -o "$(TargetPath)" -t "$(IntermediateOutputPath)\zip_$(ArchName)" -b "$(BuildPath.TrimEnd(`\`))" - set DOC_FILENAME=python$(PythonVersion).chm + "$(PythonExe)" "$(PySourcePath)PC\layout" + $(Arguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" + $(Arguments) -t "$(IntermediateOutputPath)\zip_$(ArchName)" + $(Arguments) --zip "$(TargetPath)" + $(Arguments) --precompile --zip-lib --include-underpth --include-stable --flat-dlls $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - $(Environment)%0D%0Aset VCREDIST_PATH=$(CRTRedist)\$(Platform) diff --git a/Tools/msi/make_zip.py b/Tools/msi/make_zip.py deleted file mode 100644 index 58f3b15ef852..000000000000 --- a/Tools/msi/make_zip.py +++ /dev/null @@ -1,250 +0,0 @@ -import argparse -import py_compile -import re -import sys -import shutil -import stat -import os -import tempfile - -from itertools import chain -from pathlib import Path -from zipfile import ZipFile, ZIP_DEFLATED - - -TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE) -DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe|pdb|lib)$', re.IGNORECASE) -PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE) - -DEBUG_FILES = { - '_ctypes_test', - '_testbuffer', - '_testcapi', - '_testconsole', - '_testimportmultiple', - '_testmultiphase', - 'xxlimited', - 'python3_dstub', -} - -EXCLUDE_FROM_LIBRARY = { - '__pycache__', - 'idlelib', - 'pydoc_data', - 'site-packages', - 'tkinter', - 'turtledemo', -} - -EXCLUDE_FROM_EMBEDDABLE_LIBRARY = { - 'ensurepip', - 'venv', -} - -EXCLUDE_FILE_FROM_LIBRARY = { - 'bdist_wininst.py', -} - -EXCLUDE_FILE_FROM_LIBS = { - 'liblzma', - 'python3stub', -} - -EXCLUDED_FILES = { - 'pyshellext', -} - -def is_not_debug(p): - if DEBUG_RE.search(p.name): - return False - - if TKTCL_RE.search(p.name): - return False - - return p.stem.lower() not in DEBUG_FILES and p.stem.lower() not in EXCLUDED_FILES - -def is_not_debug_or_python(p): - return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name) - -def include_in_lib(p): - name = p.name.lower() - if p.is_dir(): - if name in EXCLUDE_FROM_LIBRARY: - return False - if name == 'test' and p.parts[-2].lower() == 'lib': - return False - if name in {'test', 'tests'} and p.parts[-3].lower() == 'lib': - return False - return True - - if name in EXCLUDE_FILE_FROM_LIBRARY: - return False - - suffix = p.suffix.lower() - return suffix not in {'.pyc', '.pyo', '.exe'} - -def include_in_embeddable_lib(p): - if p.is_dir() and p.name.lower() in EXCLUDE_FROM_EMBEDDABLE_LIBRARY: - return False - - return include_in_lib(p) - -def include_in_libs(p): - if not is_not_debug(p): - return False - - return p.stem.lower() not in EXCLUDE_FILE_FROM_LIBS - -def include_in_tools(p): - if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}: - return True - - return p.suffix.lower() in {'.py', '.pyw', '.txt'} - -BASE_NAME = 'python{0.major}{0.minor}'.format(sys.version_info) - -FULL_LAYOUT = [ - ('/', '$build', 'python.exe', is_not_debug), - ('/', '$build', 'pythonw.exe', is_not_debug), - ('/', '$build', 'python{}.dll'.format(sys.version_info.major), is_not_debug), - ('/', '$build', '{}.dll'.format(BASE_NAME), is_not_debug), - ('DLLs/', '$build', '*.pyd', is_not_debug), - ('DLLs/', '$build', '*.dll', is_not_debug_or_python), - ('include/', 'include', '*.h', None), - ('include/', 'PC', 'pyconfig.h', None), - ('Lib/', 'Lib', '**/*', include_in_lib), - ('libs/', '$build', '*.lib', include_in_libs), - ('Tools/', 'Tools', '**/*', include_in_tools), -] - -EMBED_LAYOUT = [ - ('/', '$build', 'python*.exe', is_not_debug), - ('/', '$build', '*.pyd', is_not_debug), - ('/', '$build', '*.dll', is_not_debug), - ('{}.zip'.format(BASE_NAME), 'Lib', '**/*', include_in_embeddable_lib), -] - -if os.getenv('DOC_FILENAME'): - FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) -if os.getenv('VCREDIST_PATH'): - FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) - EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) - -def copy_to_layout(target, rel_sources): - count = 0 - - if target.suffix.lower() == '.zip': - if target.exists(): - target.unlink() - - with ZipFile(str(target), 'w', ZIP_DEFLATED) as f: - with tempfile.TemporaryDirectory() as tmpdir: - for s, rel in rel_sources: - if rel.suffix.lower() == '.py': - pyc = Path(tmpdir) / rel.with_suffix('.pyc').name - try: - py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2) - except py_compile.PyCompileError: - f.write(str(s), str(rel)) - else: - f.write(str(pyc), str(rel.with_suffix('.pyc'))) - else: - f.write(str(s), str(rel)) - count += 1 - - else: - for s, rel in rel_sources: - dest = target / rel - try: - dest.parent.mkdir(parents=True) - except FileExistsError: - pass - if dest.is_file(): - dest.chmod(stat.S_IWRITE) - shutil.copy(str(s), str(dest)) - if dest.is_file(): - dest.chmod(stat.S_IWRITE) - count += 1 - - return count - -def rglob(root, pattern, condition): - dirs = [root] - recurse = pattern[:3] in {'**/', '**\\'} - while dirs: - d = dirs.pop(0) - for f in d.glob(pattern[3:] if recurse else pattern): - if recurse and f.is_dir() and (not condition or condition(f)): - dirs.append(f) - elif f.is_file() and (not condition or condition(f)): - yield f, f.relative_to(root) - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path) - parser.add_argument('-o', '--out', metavar='file', help='The name of the output archive', type=Path, default=None) - parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None) - parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False) - parser.add_argument('-b', '--build', help='Specify the build directory', type=Path, default=None) - ns = parser.parse_args() - - source = ns.source or (Path(__file__).resolve().parent.parent.parent) - out = ns.out - build = ns.build or Path(sys.exec_prefix) - assert isinstance(source, Path) - assert not out or isinstance(out, Path) - assert isinstance(build, Path) - - if ns.temp: - temp = ns.temp - delete_temp = False - else: - temp = Path(tempfile.mkdtemp()) - delete_temp = True - - if out: - try: - out.parent.mkdir(parents=True) - except FileExistsError: - pass - try: - temp.mkdir(parents=True) - except FileExistsError: - pass - - layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT - - try: - for t, s, p, c in layout: - if s == '$build': - fs = build - else: - fs = source / s - files = rglob(fs, p, c) - extra_files = [] - if s == 'Lib' and p == '**/*': - extra_files.append(( - source / 'tools' / 'msi' / 'distutils.command.bdist_wininst.py', - Path('distutils') / 'command' / 'bdist_wininst.py' - )) - copied = copy_to_layout(temp / t.rstrip('/'), chain(files, extra_files)) - print('Copied {} files'.format(copied)) - - if ns.embed: - with open(str(temp / (BASE_NAME + '._pth')), 'w') as f: - print(BASE_NAME + '.zip', file=f) - print('.', file=f) - print('', file=f) - print('# Uncomment to run site.main() automatically', file=f) - print('#import site', file=f) - - if out: - total = copy_to_layout(out, rglob(temp, '**/*', None)) - print('Wrote {} files to {}'.format(total, out)) - finally: - if delete_temp: - shutil.rmtree(temp, True) - - -if __name__ == "__main__": - sys.exit(int(main() or 0)) diff --git a/Tools/msi/sdktools.psm1 b/Tools/msi/sdktools.psm1 new file mode 100644 index 000000000000..81a74d3679d7 --- /dev/null +++ b/Tools/msi/sdktools.psm1 @@ -0,0 +1,43 @@ +function Find-Tool { + param([string]$toolname) + + $kitroot = (gp 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots\').KitsRoot10 + $tool = (gci -r "$kitroot\Bin\*\x64\$toolname" | sort FullName -Desc | select -First 1) + if (-not $tool) { + throw "$toolname is not available" + } + Write-Host "Found $toolname at $($tool.FullName)" + return $tool.FullName +} + +Set-Alias SignTool (Find-Tool "signtool.exe") -Scope Script + +function Sign-File { + param([string]$certname, [string]$certsha1, [string]$certfile, [string]$description, [string[]]$files) + + if (-not $description) { + $description = $env:SigningDescription; + if (-not $description) { + $description = "Python"; + } + } + if (-not $certname) { + $certname = $env:SigningCertificate; + } + if (-not $certfile) { + $certfile = $env:SigningCertificateFile; + } + + foreach ($a in $files) { + if ($certsha1) { + SignTool sign /sha1 $certsha1 /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } elseif ($certname) { + SignTool sign /n $certname /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } elseif ($certfile) { + SignTool sign /f $certfile /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } else { + SignTool sign /a /fd sha256 /t http://timestamp.verisign.com/scripts/timestamp.dll /d $description $a + } + } +} + diff --git a/Tools/msi/sign_build.ps1 b/Tools/msi/sign_build.ps1 new file mode 100644 index 000000000000..6668eb33a2d1 --- /dev/null +++ b/Tools/msi/sign_build.ps1 @@ -0,0 +1,34 @@ +<# +.Synopsis + Recursively signs the contents of a directory. +.Description + Given the file patterns, code signs the contents. +.Parameter root + The root directory to sign. +.Parameter patterns + The file patterns to sign +.Parameter description + The description to add to the signature (optional). +.Parameter certname + The name of the certificate to sign with (optional). +.Parameter certsha1 + The SHA1 hash of the certificate to sign with (optional). +#> +param( + [Parameter(Mandatory=$true)][string]$root, + [string[]]$patterns=@("*.exe", "*.dll", "*.pyd"), + [string]$description, + [string]$certname, + [string]$certsha1, + [string]$certfile +) + +$tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent; +Import-Module $tools\sdktools.psm1 -WarningAction SilentlyContinue -Force + +pushd $root +try { + Sign-File -certname $certname -certsha1 $certsha1 -certfile $certfile -description $description -files (gci -r $patterns) +} finally { + popd +} \ No newline at end of file diff --git a/Tools/nuget/make_pkg.proj b/Tools/nuget/make_pkg.proj index 9843bc97ccdc..e093a6d0bd76 100644 --- a/Tools/nuget/make_pkg.proj +++ b/Tools/nuget/make_pkg.proj @@ -20,25 +20,28 @@ false $(OutputName).$(NuspecVersion) .nupkg - $(IntermediateOutputPath)\nuget_$(ArchName) + $(IntermediateOutputPath)\nuget_$(ArchName)\ - rmdir /q/s "$(IntermediateOutputPath)" + rmdir /q/s "$(IntermediateOutputPath.TrimEnd(`\`))" - "$(PythonExe)" "$(MSBuildThisFileDirectory)\..\msi\make_zip.py" - $(PythonArguments) -t "$(IntermediateOutputPath)" -b "$(BuildPath.TrimEnd(`\`))" + "$(PythonExe)" "$(PySourcePath)PC\layout" + $(PythonArguments) -b "$(BuildPath.TrimEnd(`\`))" -s "$(PySourcePath.TrimEnd(`\`))" + $(PythonArguments) -t "$(IntermediateOutputPath)obj" + $(PythonArguments) --copy "$(IntermediateOutputPath)pkg" + $(PythonArguments) --include-dev --include-tools --include-pip --include-stable --include-launcher --include-props - "$(IntermediateOutputPath)\python.exe" -B -c "import sys; sys.path.append(r'$(PySourcePath)\Lib'); import ensurepip; ensurepip._main()" - "$(IntermediateOutputPath)\python.exe" -B -m pip install -U $(Packages) + "$(IntermediateOutputPath)pkg\pip.exe" -B -m pip install -U $(Packages) - "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).nuspec" -BasePath "$(IntermediateOutputPath)" + "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).nuspec" -BasePath "$(IntermediateOutputPath)pkg" "$(Nuget)" pack "$(MSBuildThisFileDirectory)\$(OutputName).symbols.nuspec" -BasePath "$(BuildPath.TrimEnd(`\`))" $(NugetArguments) -OutputDirectory "$(OutputPath.Trim(`\`))" $(NugetArguments) -Version "$(NuspecVersion)" $(NugetArguments) -NoPackageAnalysis -NonInteractive - set DOC_FILENAME=python$(PythonVersion).chm $(Environment)%0D%0Aset PYTHONPATH=$(PySourcePath)Lib - $(Environment)%0D%0Aset VCREDIST_PATH=$(CRTRedist)\$(Platform) + $(Environment)%0D%0Aset PYTHON_NUSPEC_VERSION=$(NuspecVersion) + $(Environment)%0D%0Aset PYTHON_PROPS_PLATFORM=$(Platform) + $(Environment)%0D%0Aset PYTHON_PROPS_PLATFORM=Win32 $(Environment)%0D%0Amkdir "$(OutputPath.Trim(`\`))" >nul 2>nul @@ -48,22 +51,7 @@ - - - - - - <_PropsContents>$([System.IO.File]::ReadAllText('python.props')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_TAG$$', '$(MajorVersionNumber).$(MinorVersionNumber)')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_VERSION$$', '$(NuspecVersion)')) - <_PropsContents Condition="$(Platform) == 'x86'">$(_PropsContents.Replace('$$PYTHON_PLATFORM$$', 'Win32')) - <_PropsContents Condition="$(Platform) != 'x86'">$(_PropsContents.Replace('$$PYTHON_PLATFORM$$', '$(Platform)')) - <_PropsContents>$(_PropsContents.Replace('$$PYTHON_TARGET$$', '_GetPythonRuntimeFilesDependsOn$(MajorVersionNumber)$(MinorVersionNumber)_$(Platform)')) - <_ExistingContents Condition="Exists('$(IntermediateOutputPath)\python.props')">$([System.IO.File]::ReadAllText('$(IntermediateOutputPath)\python.props')) - - + From webhook-mailer at python.org Fri Dec 7 00:59:46 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Fri, 07 Dec 2018 05:59:46 -0000 Subject: [Python-checkins] bpo-35434 Fix wrong issue number in what's new in 3.8 (GH-11012) Message-ID: https://github.com/python/cpython/commit/16501b70826695991b3a151dfc538f010be5c765 commit: 16501b70826695991b3a151dfc538f010be5c765 branch: master author: Mariatta committer: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> date: 2018-12-06T21:59:42-08:00 summary: bpo-35434 Fix wrong issue number in what's new in 3.8 (GH-11012) https://bugs.python.org/issue35434 files: M Doc/whatsnew/3.8.rst diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 5492a90aacec..0d0d1ca70e6c 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -275,7 +275,7 @@ Optimizations +26% on Linux, +50% on macOS and +40% on Windows. Also, much less CPU cycles are consumed. See :ref:`shutil-platform-dependent-efficient-copy-operations` section. - (Contributed by Giampaolo Rodola' in :issue:`25427`.) + (Contributed by Giampaolo Rodola' in :issue:`33671`.) * :func:`shutil.copytree` uses :func:`os.scandir` function and all copy functions depending from it use cached :func:`os.stat` values. The speedup From webhook-mailer at python.org Fri Dec 7 01:02:37 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Fri, 07 Dec 2018 06:02:37 -0000 Subject: [Python-checkins] [2.7] bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934). (GH-11001) (GH-11008) Message-ID: https://github.com/python/cpython/commit/324e1790094708538acf2e7795f9c44e3732aaf7 commit: 324e1790094708538acf2e7795f9c44e3732aaf7 branch: 2.7 author: Serhiy Storchaka committer: GitHub date: 2018-12-07T08:02:33+02:00 summary: [2.7] bpo-33023: Fix NotImplemented to NotImplementedError. (GH-10934). (GH-11001) (GH-11008) (cherry picked from commit 42b1d6127bd8595522a78a75166ebb9fba74a6a2) (cherry picked from commit 7a2cf1e7d3bf300e98c702589d405734f4a8fcf8) files: M Lib/ssl.py M Lib/test/test_ssl.py diff --git a/Lib/ssl.py b/Lib/ssl.py index 22d478b56871..087faf95ad99 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -630,8 +630,8 @@ def context(self, ctx): self._sslobj.context = ctx def dup(self): - raise NotImplemented("Can't dup() %s instances" % - self.__class__.__name__) + raise NotImplementedError("Can't dup() %s instances" % + self.__class__.__name__) def _checkClosed(self, msg=None): # raise an exception here if you wish to check for spurious closes diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index dc14e22ad121..e47603170253 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -341,6 +341,7 @@ def test_wrapped_unconnected(self): self.assertRaises(socket.error, ss.recvfrom_into, bytearray(b'x'), 1) self.assertRaises(socket.error, ss.send, b'x') self.assertRaises(socket.error, ss.sendto, b'x', ('0.0.0.0', 0)) + self.assertRaises(NotImplementedError, ss.dup) def test_timeout(self): # Issue #8524: when creating an SSL socket, the timeout of the @@ -2645,6 +2646,7 @@ def _recvfrom_into(): self.assertEqual(s.read(-1, buffer), len(data)) self.assertEqual(buffer, data) + self.assertRaises(NotImplementedError, s.dup) s.write(b"over\n") self.assertRaises(ValueError, s.recv, -1) From webhook-mailer at python.org Fri Dec 7 01:32:25 2018 From: webhook-mailer at python.org (Terry Jan Reedy) Date: Fri, 07 Dec 2018 06:32:25 -0000 Subject: [Python-checkins] bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" (#10464) Message-ID: https://github.com/python/cpython/commit/9ebe8794f003dadfff578a066ea503a3e37ffe1d commit: 9ebe8794f003dadfff578a066ea503a3e37ffe1d branch: master author: Tal Einat committer: Terry Jan Reedy date: 2018-12-07T01:32:21-05:00 summary: bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" (#10464) * bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" * add NEWS entry * address code review comments * address second code review comments * Add entry for idlelib/NEWS.txt. files: A Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst M Lib/idlelib/NEWS.txt M Lib/idlelib/macosx.py M Lib/idlelib/outwin.py M Lib/idlelib/pyshell.py diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 9a16ece3bbe3..6e11cab58600 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,12 @@ Released on 2019-10-20? ====================================== +bpo-34864: When starting IDLE on MacOS, warn if the system setting +"Prefer tabs when opening documents" is "Always". As previous +documented for this issue, running IDLE with this setting causes +problems. If the setting is changed while IDLE is running, +there will be no warning until IDLE is restarted. + bpo-35213: Where appropriate, use 'macOS' in idlelib. bpo-34864: Document two IDLE on MacOS issues. The System Preferences diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index 8f8484a37015..9be4ed2ec411 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -1,6 +1,8 @@ """ A number of functions that enhance IDLE on macOS. """ +from os.path import expanduser +import plistlib from sys import platform # Used in _init_tk_type, changed by test. import tkinter @@ -79,14 +81,47 @@ def tkVersionWarning(root): patchlevel = root.tk.call('info', 'patchlevel') if patchlevel not in ('8.5.7', '8.5.9'): return False - return (r"WARNING: The version of Tcl/Tk ({0}) in use may" - r" be unstable.\n" - r"Visit http://www.python.org/download/mac/tcltk/" - r" for current information.".format(patchlevel)) + return ("WARNING: The version of Tcl/Tk ({0}) in use may" + " be unstable.\n" + "Visit http://www.python.org/download/mac/tcltk/" + " for current information.".format(patchlevel)) else: return False +def readSystemPreferences(): + """ + Fetch the macOS system preferences. + """ + if platform != 'darwin': + return None + + plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist') + try: + with open(plist_path, 'rb') as plist_file: + return plistlib.load(plist_file) + except OSError: + return None + + +def preferTabsPreferenceWarning(): + """ + Warn if "Prefer tabs when opening documents" is set to "Always". + """ + if platform != 'darwin': + return None + + prefs = readSystemPreferences() + if prefs and prefs.get('AppleWindowTabbingMode') == 'always': + return ( + 'WARNING: The system preference "Prefer tabs when opening' + ' documents" is set to "Always". This will cause various problems' + ' with IDLE. For the best experience, change this setting when' + ' running IDLE (via System Preferences -> Dock).' + ) + return None + + ## Fix the menu and related functions. def addOpenEventSupport(root, flist): diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index 4af9f1afaed5..e962142498df 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -109,7 +109,7 @@ def write(self, s, tags=(), mark="insert"): Return: Length of text inserted. """ - if isinstance(s, (bytes, bytes)): + if isinstance(s, bytes): s = s.decode(iomenu.encoding, "replace") self.text.insert(mark, s, tags) self.text.see(mark) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 47eef4399ce6..81a97ef6d6bc 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -38,6 +38,7 @@ import re import socket import subprocess +from textwrap import TextWrapper import threading import time import tokenize @@ -1273,6 +1274,14 @@ def showprompt(self): self.set_line_and_column() self.io.reset_undo() + def show_warning(self, msg): + width = self.interp.tkconsole.width + wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True) + wrapped_msg = '\n'.join(wrapper.wrap(msg)) + if not wrapped_msg.endswith('\n'): + wrapped_msg += '\n' + self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr") + def resetoutput(self): source = self.text.get("iomark", "end-1c") if self.history: @@ -1541,12 +1550,20 @@ def main(): shell.interp.execfile(script) elif shell: # If there is a shell window and no cmd or script in progress, - # check for problematic OS X Tk versions and print a warning - # message in the IDLE shell window; this is less intrusive - # than always opening a separate window. + # check for problematic issues and print warning message(s) in + # the IDLE shell window; this is less intrusive than always + # opening a separate window. + + # Warn if using a problematic OS X Tk version. tkversionwarning = macosx.tkVersionWarning(root) if tkversionwarning: - shell.interp.runcommand("print('%s')" % tkversionwarning) + shell.show_warning(tkversionwarning) + + # Warn if the "Prefer tabs when opening documents" system + # preference is set to "Always". + prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning() + if prefer_tabs_preference_warning: + shell.show_warning(prefer_tabs_preference_warning) while flist.inversedict: # keep IDLE running while files are open. root.mainloop() diff --git a/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst b/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst new file mode 100644 index 000000000000..8d2b61d599ff --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst @@ -0,0 +1,2 @@ +On macOS, warn if the system preference "Prefer tabs when opening documents" +is set to "Always". \ No newline at end of file From webhook-mailer at python.org Fri Dec 7 01:51:13 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Fri, 07 Dec 2018 06:51:13 -0000 Subject: [Python-checkins] bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" (GH-10464) Message-ID: https://github.com/python/cpython/commit/2db190bb356d00422087e1286637887efb8d97c5 commit: 2db190bb356d00422087e1286637887efb8d97c5 branch: 3.7 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T22:51:10-08:00 summary: bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" (GH-10464) * bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" * add NEWS entry * address code review comments * address second code review comments * Add entry for idlelib/NEWS.txt. (cherry picked from commit 9ebe8794f003dadfff578a066ea503a3e37ffe1d) Co-authored-by: Tal Einat files: A Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst M Lib/idlelib/NEWS.txt M Lib/idlelib/macosx.py M Lib/idlelib/outwin.py M Lib/idlelib/pyshell.py diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index e34b439319dd..b93769e73bbb 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,12 @@ Released on 2018-07-31? ====================================== +bpo-34864: When starting IDLE on MacOS, warn if the system setting +"Prefer tabs when opening documents" is "Always". As previous +documented for this issue, running IDLE with this setting causes +problems. If the setting is changed while IDLE is running, +there will be no warning until IDLE is restarted. + bpo-35213: Where appropriate, use 'macOS' in idlelib. bpo-34864: Document two IDLE on MacOS issues. The System Preferences diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index 8f8484a37015..9be4ed2ec411 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -1,6 +1,8 @@ """ A number of functions that enhance IDLE on macOS. """ +from os.path import expanduser +import plistlib from sys import platform # Used in _init_tk_type, changed by test. import tkinter @@ -79,14 +81,47 @@ def tkVersionWarning(root): patchlevel = root.tk.call('info', 'patchlevel') if patchlevel not in ('8.5.7', '8.5.9'): return False - return (r"WARNING: The version of Tcl/Tk ({0}) in use may" - r" be unstable.\n" - r"Visit http://www.python.org/download/mac/tcltk/" - r" for current information.".format(patchlevel)) + return ("WARNING: The version of Tcl/Tk ({0}) in use may" + " be unstable.\n" + "Visit http://www.python.org/download/mac/tcltk/" + " for current information.".format(patchlevel)) else: return False +def readSystemPreferences(): + """ + Fetch the macOS system preferences. + """ + if platform != 'darwin': + return None + + plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist') + try: + with open(plist_path, 'rb') as plist_file: + return plistlib.load(plist_file) + except OSError: + return None + + +def preferTabsPreferenceWarning(): + """ + Warn if "Prefer tabs when opening documents" is set to "Always". + """ + if platform != 'darwin': + return None + + prefs = readSystemPreferences() + if prefs and prefs.get('AppleWindowTabbingMode') == 'always': + return ( + 'WARNING: The system preference "Prefer tabs when opening' + ' documents" is set to "Always". This will cause various problems' + ' with IDLE. For the best experience, change this setting when' + ' running IDLE (via System Preferences -> Dock).' + ) + return None + + ## Fix the menu and related functions. def addOpenEventSupport(root, flist): diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index 4af9f1afaed5..e962142498df 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -109,7 +109,7 @@ def write(self, s, tags=(), mark="insert"): Return: Length of text inserted. """ - if isinstance(s, (bytes, bytes)): + if isinstance(s, bytes): s = s.decode(iomenu.encoding, "replace") self.text.insert(mark, s, tags) self.text.see(mark) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 47eef4399ce6..81a97ef6d6bc 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -38,6 +38,7 @@ import re import socket import subprocess +from textwrap import TextWrapper import threading import time import tokenize @@ -1273,6 +1274,14 @@ def showprompt(self): self.set_line_and_column() self.io.reset_undo() + def show_warning(self, msg): + width = self.interp.tkconsole.width + wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True) + wrapped_msg = '\n'.join(wrapper.wrap(msg)) + if not wrapped_msg.endswith('\n'): + wrapped_msg += '\n' + self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr") + def resetoutput(self): source = self.text.get("iomark", "end-1c") if self.history: @@ -1541,12 +1550,20 @@ def main(): shell.interp.execfile(script) elif shell: # If there is a shell window and no cmd or script in progress, - # check for problematic OS X Tk versions and print a warning - # message in the IDLE shell window; this is less intrusive - # than always opening a separate window. + # check for problematic issues and print warning message(s) in + # the IDLE shell window; this is less intrusive than always + # opening a separate window. + + # Warn if using a problematic OS X Tk version. tkversionwarning = macosx.tkVersionWarning(root) if tkversionwarning: - shell.interp.runcommand("print('%s')" % tkversionwarning) + shell.show_warning(tkversionwarning) + + # Warn if the "Prefer tabs when opening documents" system + # preference is set to "Always". + prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning() + if prefer_tabs_preference_warning: + shell.show_warning(prefer_tabs_preference_warning) while flist.inversedict: # keep IDLE running while files are open. root.mainloop() diff --git a/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst b/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst new file mode 100644 index 000000000000..8d2b61d599ff --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst @@ -0,0 +1,2 @@ +On macOS, warn if the system preference "Prefer tabs when opening documents" +is set to "Always". \ No newline at end of file From webhook-mailer at python.org Fri Dec 7 02:03:36 2018 From: webhook-mailer at python.org (Miss Islington (bot)) Date: Fri, 07 Dec 2018 07:03:36 -0000 Subject: [Python-checkins] bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" (GH-10464) Message-ID: https://github.com/python/cpython/commit/10665544a97b6616898faafc12ac9d06505d0690 commit: 10665544a97b6616898faafc12ac9d06505d0690 branch: 3.6 author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com> committer: GitHub date: 2018-12-06T23:03:32-08:00 summary: bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" (GH-10464) * bpo-34864: warn if "Prefer tabs when opening documents" set to "Always" * add NEWS entry * address code review comments * address second code review comments * Add entry for idlelib/NEWS.txt. (cherry picked from commit 9ebe8794f003dadfff578a066ea503a3e37ffe1d) Co-authored-by: Tal Einat files: A Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst M Lib/idlelib/NEWS.txt M Lib/idlelib/macosx.py M Lib/idlelib/outwin.py M Lib/idlelib/pyshell.py diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 0868b1f1a3c3..e2951b0e6d7b 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -4,6 +4,12 @@ Released on 2018-12-20? ====================================== +bpo-34864: When starting IDLE on MacOS, warn if the system setting +"Prefer tabs when opening documents" is "Always". As previous +documented for this issue, running IDLE with this setting causes +problems. If the setting is changed while IDLE is running, +there will be no warning until IDLE is restarted. + bpo-35213: Where appropriate, use 'macOS' in idlelib. bpo-34864: Document two IDLE on MacOS issues. The System Preferences diff --git a/Lib/idlelib/macosx.py b/Lib/idlelib/macosx.py index 8f8484a37015..9be4ed2ec411 100644 --- a/Lib/idlelib/macosx.py +++ b/Lib/idlelib/macosx.py @@ -1,6 +1,8 @@ """ A number of functions that enhance IDLE on macOS. """ +from os.path import expanduser +import plistlib from sys import platform # Used in _init_tk_type, changed by test. import tkinter @@ -79,14 +81,47 @@ def tkVersionWarning(root): patchlevel = root.tk.call('info', 'patchlevel') if patchlevel not in ('8.5.7', '8.5.9'): return False - return (r"WARNING: The version of Tcl/Tk ({0}) in use may" - r" be unstable.\n" - r"Visit http://www.python.org/download/mac/tcltk/" - r" for current information.".format(patchlevel)) + return ("WARNING: The version of Tcl/Tk ({0}) in use may" + " be unstable.\n" + "Visit http://www.python.org/download/mac/tcltk/" + " for current information.".format(patchlevel)) else: return False +def readSystemPreferences(): + """ + Fetch the macOS system preferences. + """ + if platform != 'darwin': + return None + + plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist') + try: + with open(plist_path, 'rb') as plist_file: + return plistlib.load(plist_file) + except OSError: + return None + + +def preferTabsPreferenceWarning(): + """ + Warn if "Prefer tabs when opening documents" is set to "Always". + """ + if platform != 'darwin': + return None + + prefs = readSystemPreferences() + if prefs and prefs.get('AppleWindowTabbingMode') == 'always': + return ( + 'WARNING: The system preference "Prefer tabs when opening' + ' documents" is set to "Always". This will cause various problems' + ' with IDLE. For the best experience, change this setting when' + ' running IDLE (via System Preferences -> Dock).' + ) + return None + + ## Fix the menu and related functions. def addOpenEventSupport(root, flist): diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index 4af9f1afaed5..e962142498df 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -109,7 +109,7 @@ def write(self, s, tags=(), mark="insert"): Return: Length of text inserted. """ - if isinstance(s, (bytes, bytes)): + if isinstance(s, bytes): s = s.decode(iomenu.encoding, "replace") self.text.insert(mark, s, tags) self.text.see(mark) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 47eef4399ce6..81a97ef6d6bc 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -38,6 +38,7 @@ import re import socket import subprocess +from textwrap import TextWrapper import threading import time import tokenize @@ -1273,6 +1274,14 @@ def showprompt(self): self.set_line_and_column() self.io.reset_undo() + def show_warning(self, msg): + width = self.interp.tkconsole.width + wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True) + wrapped_msg = '\n'.join(wrapper.wrap(msg)) + if not wrapped_msg.endswith('\n'): + wrapped_msg += '\n' + self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr") + def resetoutput(self): source = self.text.get("iomark", "end-1c") if self.history: @@ -1541,12 +1550,20 @@ def main(): shell.interp.execfile(script) elif shell: # If there is a shell window and no cmd or script in progress, - # check for problematic OS X Tk versions and print a warning - # message in the IDLE shell window; this is less intrusive - # than always opening a separate window. + # check for problematic issues and print warning message(s) in + # the IDLE shell window; this is less intrusive than always + # opening a separate window. + + # Warn if using a problematic OS X Tk version. tkversionwarning = macosx.tkVersionWarning(root) if tkversionwarning: - shell.interp.runcommand("print('%s')" % tkversionwarning) + shell.show_warning(tkversionwarning) + + # Warn if the "Prefer tabs when opening documents" system + # preference is set to "Always". + prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning() + if prefer_tabs_preference_warning: + shell.show_warning(prefer_tabs_preference_warning) while flist.inversedict: # keep IDLE running while files are open. root.mainloop() diff --git a/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst b/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst new file mode 100644 index 000000000000..8d2b61d599ff --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2018-11-11-17-13-50.bpo-34864.cw0PvO.rst @@ -0,0 +1,2 @@ +On macOS, warn if the system preference "Prefer tabs when opening documents" +is set to "Always". \ No newline at end of file From solipsis at pitrou.net Fri Dec 7 04:08:31 2018 From: solipsis at pitrou.net (solipsis at pitrou.net) Date: Fri, 07 Dec 2018 09:08:31 +0000 Subject: [Python-checkins] Daily reference leaks (4243df51fe43): sum=70 Message-ID: <20181207090831.1.C55D4D6245939479@psf.io> results for 4243df51fe43 on branch "default" -------------------------------------------- test_functools leaked [0, 3, 1] memory blocks, sum=4 test_multiprocessing_fork leaked [0, 0, 44] references, sum=44 test_multiprocessing_fork leaked [0, -1, 20] memory blocks, sum=19 test_multiprocessing_fork leaked [0, 0, 2] file descriptors, sum=2 test_multiprocessing_forkserver leaked [2, 0, -1] memory blocks, sum=1 Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogxLTjZf', '--timeout', '7200'] From webhook-mailer at python.org Fri Dec 7 05:10:40 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Fri, 07 Dec 2018 10:10:40 -0000 Subject: [Python-checkins] bpo-35346, platform: replace os.popen() with subprocess (GH-10786) Message-ID: https://github.com/python/cpython/commit/3a521f0b6167628f975c773b56c7daf8d33d6f40 commit: 3a521f0b6167628f975c773b56c7daf8d33d6f40 branch: master author: Victor Stinner committer: GitHub date: 2018-12-07T11:10:33+01:00 summary: bpo-35346, platform: replace os.popen() with subprocess (GH-10786) Replace os.popen() with subprocess.check_output() in the platform module: * platform.uname() (its _syscmd_ver() helper function) now redirects stderr to DEVNULL. * Remove platform.DEV_NULL. * _syscmd_uname() and _syscmd_file() no longer catch AttributeError. The "except AttributeError:" was only needed in Python 2, when os.popen() was not always available. In Python 3, subprocess.check_output() is always available. files: A Misc/NEWS.d/next/Library/2018-11-29-12-42-13.bpo-35346.OmTY5c.rst M Lib/platform.py M Lib/test/test_platform.py diff --git a/Lib/platform.py b/Lib/platform.py index d8455256bb9a..5f9491819169 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -119,19 +119,6 @@ ### Globals & Constants -# Determine the platform's /dev/null device -try: - DEV_NULL = os.devnull -except AttributeError: - # os.devnull was added in Python 2.4, so emulate it for earlier - # Python versions - if sys.platform in ('dos', 'win32', 'win16'): - # Use the old CP/M NUL as device name - DEV_NULL = 'NUL' - else: - # Standard Unix uses /dev/null - DEV_NULL = '/dev/null' - # Helper for comparing two version number strings. # Based on the description of the PHP's version_compare(): # http://php.net/manual/en/function.version-compare.php @@ -288,16 +275,15 @@ def _syscmd_ver(system='', release='', version='', return system, release, version # Try some common cmd strings + import subprocess for cmd in ('ver', 'command /c ver', 'cmd /c ver'): try: - pipe = os.popen(cmd) - info = pipe.read() - if pipe.close(): - raise OSError('command failed') - # XXX How can I suppress shell errors from being written - # to stderr ? - except OSError as why: - #print 'Command %s failed: %s' % (cmd, why) + info = subprocess.check_output(cmd, + stderr=subprocess.DEVNULL, + text=True, + shell=True) + except (OSError, subprocess.CalledProcessError) as why: + #print('Command %s failed: %s' % (cmd, why)) continue else: break @@ -602,16 +588,15 @@ def _syscmd_uname(option, default=''): if sys.platform in ('dos', 'win32', 'win16'): # XXX Others too ? return default + + import subprocess try: - f = os.popen('uname %s 2> %s' % (option, DEV_NULL)) - except (AttributeError, OSError): - return default - output = f.read().strip() - rc = f.close() - if not output or rc: + output = subprocess.check_output(('uname', option), + stderr=subprocess.DEVNULL, + text=True) + except (OSError, subprocess.CalledProcessError): return default - else: - return output + return (output.strip() or default) def _syscmd_file(target, default=''): @@ -629,17 +614,12 @@ def _syscmd_file(target, default=''): import subprocess target = _follow_symlinks(target) try: - proc = subprocess.Popen(['file', target], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - except (AttributeError, OSError): + output = subprocess.check_output(['file', target], + stderr=subprocess.DEVNULL, + encoding='latin-1') + except (OSError, subprocess.CalledProcessError): return default - output = proc.communicate()[0].decode('latin-1') - rc = proc.wait() - if not output or rc: - return default - else: - return output + return (output or default) ### Information about the used architecture diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index c1a7e3407934..9cf17726d92e 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -222,16 +222,16 @@ def test_mac_ver(self): res = platform.mac_ver() if platform.uname().system == 'Darwin': - # We're on a MacOSX system, check that - # the right version information is returned - fd = os.popen('sw_vers', 'r') - real_ver = None - for ln in fd: - if ln.startswith('ProductVersion:'): - real_ver = ln.strip().split()[-1] + # We are on a macOS system, check that the right version + # information is returned + output = subprocess.check_output(['sw_vers'], text=True) + for line in output.splitlines(): + if line.startswith('ProductVersion:'): + real_ver = line.strip().split()[-1] break - fd.close() - self.assertFalse(real_ver is None) + else: + self.fail(f"failed to parse sw_vers output: {output!r}") + result_list = res[0].split('.') expect_list = real_ver.split('.') len_diff = len(result_list) - len(expect_list) diff --git a/Misc/NEWS.d/next/Library/2018-11-29-12-42-13.bpo-35346.OmTY5c.rst b/Misc/NEWS.d/next/Library/2018-11-29-12-42-13.bpo-35346.OmTY5c.rst new file mode 100644 index 000000000000..f6d28feab78c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-29-12-42-13.bpo-35346.OmTY5c.rst @@ -0,0 +1,2 @@ +:func:`platform.uname` now redirects ``stderr`` to :data:`os.devnull` when +running external programs like ``cmd /c ver``. From webhook-mailer at python.org Fri Dec 7 05:11:34 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Fri, 07 Dec 2018 10:11:34 -0000 Subject: [Python-checkins] bpo-35436: Add missing PyErr_NoMemory() calls and other minor bug fixes. (GH-11015) Message-ID: https://github.com/python/cpython/commit/4c49da0cb7434c676d70b9ccf38aca82ac0d64a9 commit: 4c49da0cb7434c676d70b9ccf38aca82ac0d64a9 branch: master author: Zackery Spytz committer: Serhiy Storchaka date: 2018-12-07T12:11:30+02:00 summary: bpo-35436: Add missing PyErr_NoMemory() calls and other minor bug fixes. (GH-11015) Set MemoryError when appropriate, add missing failure checks, and fix some potential leaks. files: A Misc/NEWS.d/next/Core and Builtins/2018-12-07-02-38-01.bpo-35436.0VW7p9.rst M Modules/_abc.c M Modules/_ctypes/_ctypes.c M Modules/_ctypes/callbacks.c M Modules/_io/winconsoleio.c M Modules/_multiprocessing/semaphore.c M Modules/_ssl.c M Modules/mathmodule.c M Modules/posixmodule.c M Objects/capsule.c M PC/getpathp.c M PC/launcher.c M Parser/myreadline.c M Parser/tokenizer.c M Python/ast.c M Python/marshal.c M Python/pystrtod.c diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-12-07-02-38-01.bpo-35436.0VW7p9.rst b/Misc/NEWS.d/next/Core and Builtins/2018-12-07-02-38-01.bpo-35436.0VW7p9.rst new file mode 100644 index 000000000000..542fe93a00eb --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-12-07-02-38-01.bpo-35436.0VW7p9.rst @@ -0,0 +1,2 @@ +Fix various issues with memory allocation error handling. Patch by Zackery +Spytz. diff --git a/Modules/_abc.c b/Modules/_abc.c index 9de199fa143f..36c1757b5fd3 100644 --- a/Modules/_abc.c +++ b/Modules/_abc.c @@ -728,6 +728,10 @@ subclasscheck_check_registry(_abc_data *impl, PyObject *subclass, // Weakref callback may remove entry from set. // So we take snapshot of registry first. PyObject **copy = PyMem_Malloc(sizeof(PyObject*) * registry_size); + if (copy == NULL) { + PyErr_NoMemory(); + return -1; + } PyObject *key; Py_ssize_t pos = 0; Py_hash_t hash; diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 3debe3ace695..163b3e3b6c78 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -305,8 +305,10 @@ _ctypes_alloc_format_string_for_type(char code, int big_endian) } result = PyMem_Malloc(3); - if (result == NULL) + if (result == NULL) { + PyErr_NoMemory(); return NULL; + } result[0] = big_endian ? '>' : '<'; result[1] = pep_code; @@ -366,8 +368,10 @@ _ctypes_alloc_format_string_with_shape(int ndim, const Py_ssize_t *shape, if (prefix) prefix_len += strlen(prefix); new_prefix = PyMem_Malloc(prefix_len); - if (new_prefix == NULL) + if (new_prefix == NULL) { + PyErr_NoMemory(); return NULL; + } new_prefix[0] = '\0'; if (prefix) strcpy(new_prefix, prefix); @@ -1899,6 +1903,10 @@ static PyObject *CreateSwappedType(PyTypeObject *type, PyObject *args, PyObject #else suffix = PyUnicode_InternFromString("_be"); #endif + if (suffix == NULL) { + Py_DECREF(swapped_args); + return NULL; + } newname = PyUnicode_Concat(name, suffix); if (newname == NULL) { diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c index 871bc4f49458..c1e9b723aac6 100644 --- a/Modules/_ctypes/callbacks.c +++ b/Modules/_ctypes/callbacks.c @@ -310,7 +310,6 @@ static CThunkObject* CThunkObject_new(Py_ssize_t nArgs) p = PyObject_GC_NewVar(CThunkObject, &PyCThunk_Type, nArgs); if (p == NULL) { - PyErr_NoMemory(); return NULL; } diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c index 824690ff58d6..dd0997a10580 100644 --- a/Modules/_io/winconsoleio.c +++ b/Modules/_io/winconsoleio.c @@ -816,11 +816,13 @@ _io__WindowsConsoleIO_readall_impl(winconsoleio *self) } bufsize = newsize; - buf = PyMem_Realloc(buf, (bufsize + 1) * sizeof(wchar_t)); - if (!buf) { + wchar_t *tmp = PyMem_Realloc(buf, + (bufsize + 1) * sizeof(wchar_t)); + if (tmp == NULL) { PyMem_Free(buf); return NULL; } + buf = tmp; } subbuf = read_console_w(self->handle, bufsize - len, &n); diff --git a/Modules/_multiprocessing/semaphore.c b/Modules/_multiprocessing/semaphore.c index e15adfbd6b19..bb7219e13070 100644 --- a/Modules/_multiprocessing/semaphore.c +++ b/Modules/_multiprocessing/semaphore.c @@ -449,8 +449,9 @@ semlock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!unlink) { name_copy = PyMem_Malloc(strlen(name) + 1); - if (name_copy == NULL) - goto failure; + if (name_copy == NULL) { + return PyErr_NoMemory(); + } strcpy(name_copy, name); } @@ -473,7 +474,9 @@ semlock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (handle != SEM_FAILED) SEM_CLOSE(handle); PyMem_Free(name_copy); - _PyMp_SetError(NULL, MP_STANDARD_ERROR); + if (!PyErr_Occurred()) { + _PyMp_SetError(NULL, MP_STANDARD_ERROR); + } return NULL; } diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 85819f5b0509..269f003e0df9 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -911,6 +911,11 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock, PySSL_BEGIN_ALLOW_THREADS self->ssl = SSL_new(ctx); PySSL_END_ALLOW_THREADS + if (self->ssl == NULL) { + Py_DECREF(self); + _setSSLError(NULL, 0, __FILE__, __LINE__); + return NULL; + } SSL_set_app_data(self->ssl, self); if (sock) { SSL_set_fd(self->ssl, Py_SAFE_DOWNCAST(sock->sock_fd, SOCKET_T, int)); @@ -1240,6 +1245,10 @@ _get_peer_alt_names (X509 *certificate) { /* get a memory buffer */ biobuf = BIO_new(BIO_s_mem()); + if (biobuf == NULL) { + PyErr_SetString(PySSLErrorObject, "failed to allocate BIO"); + return NULL; + } names = (GENERAL_NAMES *)X509_get_ext_d2i( certificate, NID_subject_alt_name, NULL, NULL); @@ -1592,6 +1601,10 @@ _decode_certificate(X509 *certificate) { /* get a memory buffer */ biobuf = BIO_new(BIO_s_mem()); + if (biobuf == NULL) { + PyErr_SetString(PySSLErrorObject, "failed to allocate BIO"); + goto fail0; + } (void) BIO_reset(biobuf); serialNumber = X509_get_serialNumber(certificate); diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index b83befbd3194..d56c91cedc26 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2142,7 +2142,7 @@ math_dist_impl(PyObject *module, PyObject *p, PyObject *q) if (n > NUM_STACK_ELEMS) { diffs = (double *) PyObject_Malloc(n * sizeof(double)); if (diffs == NULL) { - return NULL; + return PyErr_NoMemory(); } } for (i=0 ; i NUM_STACK_ELEMS) { coordinates = (double *) PyObject_Malloc(n * sizeof(double)); - if (coordinates == NULL) - return NULL; + if (coordinates == NULL) { + return PyErr_NoMemory(); + } } for (i=0 ; i bufsiz) { bufsiz += MAXPATHLEN; - buf = (wchar_t*)PyMem_RawRealloc(buf, (bufsiz + 1) * sizeof(wchar_t)); - if (!buf) { + wchar_t *tmp = (wchar_t*)PyMem_RawRealloc(buf, (bufsiz + 1) * + sizeof(wchar_t)); + if (tmp == NULL) { PyMem_RawFree(wline); goto error; } + buf = tmp; } if (usedsiz) { diff --git a/PC/launcher.c b/PC/launcher.c index 0242f2639119..4c620dab7c09 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -1763,6 +1763,9 @@ process(int argc, wchar_t ** argv) } cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */ executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); + if (executable == NULL) { + error(RC_NO_MEMORY, L"A memory allocation failed"); + } cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch); if (!cch_actual) { error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", diff --git a/Parser/myreadline.c b/Parser/myreadline.c index acb4d01fdf92..43e5583b8bcc 100644 --- a/Parser/myreadline.c +++ b/Parser/myreadline.c @@ -153,20 +153,37 @@ _PyOS_WindowsConsoleReadline(HANDLE hStdIn) wbuf = (wchar_t*)PyMem_RawMalloc(wbuflen * sizeof(wchar_t)); if (wbuf) wcscpy_s(wbuf, wbuflen, wbuf_local); + else { + PyErr_NoMemory(); + goto exit; + } + } + else { + wchar_t *tmp = PyMem_RawRealloc(wbuf, wbuflen * sizeof(wchar_t)); + if (tmp == NULL) { + PyErr_NoMemory(); + goto exit; + } + wbuf = tmp; } - else - wbuf = (wchar_t*)PyMem_RawRealloc(wbuf, wbuflen * sizeof(wchar_t)); } if (wbuf[0] == '\x1a') { buf = PyMem_RawMalloc(1); if (buf) buf[0] = '\0'; + else { + PyErr_NoMemory(); + } goto exit; } u8len = WideCharToMultiByte(CP_UTF8, 0, wbuf, total_read, NULL, 0, NULL, NULL); buf = PyMem_RawMalloc(u8len + 1); + if (buf == NULL) { + PyErr_NoMemory(); + goto exit; + } u8len = WideCharToMultiByte(CP_UTF8, 0, wbuf, total_read, buf, u8len, NULL, NULL); buf[u8len] = '\0'; @@ -211,8 +228,12 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) int wlen; wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1, NULL, 0); - if (wlen && - (wbuf = PyMem_RawMalloc(wlen * sizeof(wchar_t)))) { + if (wlen) { + wbuf = PyMem_RawMalloc(wlen * sizeof(wchar_t)); + if (wbuf == NULL) { + PyErr_NoMemory(); + return NULL; + } wlen = MultiByteToWideChar(CP_UTF8, 0, prompt, -1, wbuf, wlen); if (wlen) { @@ -236,8 +257,10 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) n = 100; p = (char *)PyMem_RawMalloc(n); - if (p == NULL) + if (p == NULL) { + PyErr_NoMemory(); return NULL; + } fflush(sys_stdout); if (prompt) @@ -314,6 +337,10 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) if (_PyOS_ReadlineLock == NULL) { _PyOS_ReadlineLock = PyThread_allocate_lock(); + if (_PyOS_ReadlineLock == NULL) { + PyErr_SetString(PyExc_MemoryError, "can't allocate lock"); + return NULL; + } } _PyOS_ReadlineTState = _PyThreadState_GET(); @@ -341,8 +368,12 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) len = strlen(rv) + 1; res = PyMem_Malloc(len); - if (res != NULL) + if (res != NULL) { memcpy(res, rv, len); + } + else { + PyErr_NoMemory(); + } PyMem_RawFree(rv); return res; diff --git a/Parser/tokenizer.c b/Parser/tokenizer.c index fc75bae53766..d319a4c90a9e 100644 --- a/Parser/tokenizer.c +++ b/Parser/tokenizer.c @@ -953,6 +953,11 @@ tok_nextc(struct tok_state *tok) buflen = PyBytes_GET_SIZE(u); buf = PyBytes_AS_STRING(u); newtok = PyMem_MALLOC(buflen+1); + if (newtok == NULL) { + Py_DECREF(u); + tok->done = E_NOMEM; + return EOF; + } strcpy(newtok, buf); Py_DECREF(u); } diff --git a/Python/ast.c b/Python/ast.c index 24d5843aabd2..8a305a80ffac 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -4097,6 +4097,9 @@ parsenumber(struct compiling *c, const char *s) } /* Create a duplicate without underscores. */ dup = PyMem_Malloc(strlen(s) + 1); + if (dup == NULL) { + return PyErr_NoMemory(); + } end = dup; for (; *s; s++) { if (*s != '_') { @@ -4325,8 +4328,10 @@ fstring_compile_expr(const char *expr_start, const char *expr_end, len = expr_end - expr_start; /* Allocate 3 extra bytes: open paren, close paren, null byte. */ str = PyMem_RawMalloc(len + 3); - if (str == NULL) + if (str == NULL) { + PyErr_NoMemory(); return NULL; + } str[0] = '('; memcpy(str+1, expr_start, len); diff --git a/Python/marshal.c b/Python/marshal.c index 21cdd60c7e13..52932af225a4 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -670,11 +670,12 @@ r_string(Py_ssize_t n, RFILE *p) p->buf_size = n; } else if (p->buf_size < n) { - p->buf = PyMem_REALLOC(p->buf, n); - if (p->buf == NULL) { + char *tmp = PyMem_REALLOC(p->buf, n); + if (tmp == NULL) { PyErr_NoMemory(); return NULL; } + p->buf = tmp; p->buf_size = n; } diff --git a/Python/pystrtod.c b/Python/pystrtod.c index 98aa9ba48bda..02a3fb57805c 100644 --- a/Python/pystrtod.c +++ b/Python/pystrtod.c @@ -398,6 +398,9 @@ _Py_string_to_number_with_underscores( } dup = PyMem_Malloc(orig_len + 1); + if (dup == NULL) { + return PyErr_NoMemory(); + } end = dup; prev = '\0'; last = s + orig_len; From webhook-mailer at python.org Fri Dec 7 06:42:15 2018 From: webhook-mailer at python.org (Serhiy Storchaka) Date: Fri, 07 Dec 2018 11:42:15 -0000 Subject: [Python-checkins] bpo-22005: Fixed unpickling instances of datetime classes pickled by Python 2. (GH-11017) Message-ID: https://github.com/python/cpython/commit/8452ca15f41061c8a6297d7956df22ab476d4df4 commit: 8452ca15f41061c8a6297d7956df22ab476d4df4 branch: master author: Serhiy Storchaka committer: GitHub date: 2018-12-07T13:42:10+02:00 summary: bpo-22005: Fixed unpickling instances of datetime classes pickled by Python 2. (GH-11017) encoding='latin1' should be used for successful decoding. files: A Misc/NEWS.d/next/Library/2017-10-12-22-39-55.bpo-22005.lGP-sc.rst M Doc/library/pickle.rst M Lib/datetime.py M Lib/test/datetimetester.py M Modules/_datetimemodule.c diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index 4f9d3596b649..5fe49a013bc4 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -243,6 +243,9 @@ process more convenient: *errors* tell pickle how to decode 8-bit string instances pickled by Python 2; these default to 'ASCII' and 'strict', respectively. The *encoding* can be 'bytes' to read these 8-bit string instances as bytes objects. + Using ``encoding='latin1'`` is required for unpickling NumPy arrays and + instances of :class:`~datetime.datetime`, :class:`~datetime.date` and + :class:`~datetime.time` pickled by Python 2. .. function:: loads(bytes_object, \*, fix_imports=True, encoding="ASCII", errors="strict") @@ -260,6 +263,9 @@ process more convenient: *errors* tell pickle how to decode 8-bit string instances pickled by Python 2; these default to 'ASCII' and 'strict', respectively. The *encoding* can be 'bytes' to read these 8-bit string instances as bytes objects. + Using ``encoding='latin1'`` is required for unpickling NumPy arrays and + instances of :class:`~datetime.datetime`, :class:`~datetime.date` and + :class:`~datetime.time` pickled by Python 2. The :mod:`pickle` module defines three exceptions: diff --git a/Lib/datetime.py b/Lib/datetime.py index 292919fd7988..4780b6df8f9b 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -808,9 +808,19 @@ def __new__(cls, year, month=None, day=None): year, month, day (required, base 1) """ - if month is None and isinstance(year, bytes) and len(year) == 4 and \ - 1 <= year[2] <= 12: + if (month is None and + isinstance(year, (bytes, str)) and len(year) == 4 and + 1 <= ord(year[2:3]) <= 12): # Pickle support + if isinstance(year, str): + try: + year = year.encode('latin1') + except UnicodeEncodeError: + # More informative error message. + raise ValueError( + "Failed to encode latin1 string when unpickling " + "a date object. " + "pickle.load(data, encoding='latin1') is assumed.") self = object.__new__(cls) self.__setstate(year) self._hashcode = -1 @@ -1184,8 +1194,18 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold tzinfo (default to None) fold (keyword only, default to zero) """ - if isinstance(hour, bytes) and len(hour) == 6 and hour[0]&0x7F < 24: + if (isinstance(hour, (bytes, str)) and len(hour) == 6 and + ord(hour[0:1])&0x7F < 24): # Pickle support + if isinstance(hour, str): + try: + hour = hour.encode('latin1') + except UnicodeEncodeError: + # More informative error message. + raise ValueError( + "Failed to encode latin1 string when unpickling " + "a time object. " + "pickle.load(data, encoding='latin1') is assumed.") self = object.__new__(cls) self.__setstate(hour, minute or None) self._hashcode = -1 @@ -1496,8 +1516,18 @@ class datetime(date): def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0): - if isinstance(year, bytes) and len(year) == 10 and 1 <= year[2]&0x7F <= 12: + if (isinstance(year, (bytes, str)) and len(year) == 10 and + 1 <= ord(year[2:3])&0x7F <= 12): # Pickle support + if isinstance(year, str): + try: + year = bytes(year, 'latin1') + except UnicodeEncodeError: + # More informative error message. + raise ValueError( + "Failed to encode latin1 string when unpickling " + "a datetime object. " + "pickle.load(data, encoding='latin1') is assumed.") self = object.__new__(cls) self.__setstate(year, month) self._hashcode = -1 diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 78b123f5b118..2f838c445555 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -38,6 +38,7 @@ import _strptime # +pickle_loads = {pickle.loads, pickle._loads} pickle_choices = [(pickle, pickle, proto) for proto in range(pickle.HIGHEST_PROTOCOL + 1)] @@ -1434,6 +1435,19 @@ def test_pickling(self): self.assertEqual(orig, derived) self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2)) + def test_compat_unpickle(self): + tests = [ + b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.", + b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.', + b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.', + ] + args = 2015, 11, 27 + expected = self.theclass(*args) + for data in tests: + for loads in pickle_loads: + derived = loads(data, encoding='latin1') + self.assertEqual(derived, expected) + def test_compare(self): t1 = self.theclass(2, 3, 4) t2 = self.theclass(2, 3, 4) @@ -2098,6 +2112,24 @@ def test_pickling_subclass_datetime(self): derived = unpickler.loads(green) self.assertEqual(orig, derived) + def test_compat_unpickle(self): + tests = [ + b'cdatetime\ndatetime\n(' + b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.", + + b'cdatetime\ndatetime\n(' + b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.', + + b'\x80\x02cdatetime\ndatetime\n' + b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.', + ] + args = 2015, 11, 27, 20, 59, 1, 64**2 + expected = self.theclass(*args) + for data in tests: + for loads in pickle_loads: + derived = loads(data, encoding='latin1') + self.assertEqual(derived, expected) + def test_more_compare(self): # The test_compare() inherited from TestDate covers the error cases. # We just want to test lexicographic ordering on the members datetime @@ -3069,6 +3101,19 @@ def test_pickling_subclass_time(self): derived = unpickler.loads(green) self.assertEqual(orig, derived) + def test_compat_unpickle(self): + tests = [ + b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.", + b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.', + b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.', + ] + args = 20, 59, 16, 64**2 + expected = self.theclass(*args) + for data in tests: + for loads in pickle_loads: + derived = loads(data, encoding='latin1') + self.assertEqual(derived, expected) + def test_bool(self): # time is always True. cls = self.theclass @@ -3441,6 +3486,40 @@ def test_pickling(self): self.assertEqual(derived.tzname(), 'cookie') self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2)) + def test_compat_unpickle(self): + tests = [ + b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n" + b"ctest.datetimetester\nPicklableFixedOffset\n(tR" + b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n" + b"(I-1\nI68400\nI0\ntRs" + b"S'_FixedOffset__dstoffset'\nNs" + b"S'_FixedOffset__name'\nS'cookie'\nsbtR.", + + b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@' + b'ctest.datetimetester\nPicklableFixedOffset\n)R' + b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n' + b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR' + b'U\x17_FixedOffset__dstoffsetN' + b'U\x12_FixedOffset__nameU\x06cookieubtR.', + + b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@' + b'ctest.datetimetester\nPicklableFixedOffset\n)R' + b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n' + b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R' + b'U\x17_FixedOffset__dstoffsetN' + b'U\x12_FixedOffset__nameU\x06cookieub\x86R.', + ] + + tinfo = PicklableFixedOffset(-300, 'cookie') + expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo) + for data in tests: + for loads in pickle_loads: + derived = loads(data, encoding='latin1') + self.assertEqual(derived, expected, repr(data)) + self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) + self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) + self.assertEqual(derived.tzname(), 'cookie') + def test_more_bool(self): # time is always True. cls = self.theclass @@ -3789,6 +3868,43 @@ def test_pickling(self): self.assertEqual(derived.tzname(), 'cookie') self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2)) + def test_compat_unpickle(self): + tests = [ + b'cdatetime\ndatetime\n' + b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n" + b'ctest.datetimetester\nPicklableFixedOffset\n(tR' + b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n" + b'(I-1\nI68400\nI0\ntRs' + b"S'_FixedOffset__dstoffset'\nNs" + b"S'_FixedOffset__name'\nS'cookie'\nsbtR.", + + b'cdatetime\ndatetime\n' + b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@' + b'ctest.datetimetester\nPicklableFixedOffset\n)R' + b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n' + b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR' + b'U\x17_FixedOffset__dstoffsetN' + b'U\x12_FixedOffset__nameU\x06cookieubtR.', + + b'\x80\x02cdatetime\ndatetime\n' + b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@' + b'ctest.datetimetester\nPicklableFixedOffset\n)R' + b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n' + b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R' + b'U\x17_FixedOffset__dstoffsetN' + b'U\x12_FixedOffset__nameU\x06cookieub\x86R.', + ] + args = 2015, 11, 27, 20, 59, 1, 123456 + tinfo = PicklableFixedOffset(-300, 'cookie') + expected = self.theclass(*args, **{'tzinfo': tinfo}) + for data in tests: + for loads in pickle_loads: + derived = loads(data, encoding='latin1') + self.assertEqual(derived, expected) + self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) + self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) + self.assertEqual(derived.tzname(), 'cookie') + def test_extreme_hashes(self): # If an attempt is made to hash these via subtracting the offset # then hashing a datetime object, OverflowError results. The diff --git a/Misc/NEWS.d/next/Library/2017-10-12-22-39-55.bpo-22005.lGP-sc.rst b/Misc/NEWS.d/next/Library/2017-10-12-22-39-55.bpo-22005.lGP-sc.rst new file mode 100644 index 000000000000..951098d0a7a3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-10-12-22-39-55.bpo-22005.lGP-sc.rst @@ -0,0 +1,3 @@ +Implemented unpickling instances of :class:`~datetime.datetime`, +:class:`~datetime.date` and :class:`~datetime.time` pickled by Python 2. +``encoding='latin1'`` should be used for successful decoding. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb9c35d02d4e..87a88be6090c 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -2788,31 +2788,60 @@ static PyGetSetDef date_getset[] = { static char *date_kws[] = {"year", "month", "day", NULL}; +static PyObject * +date_from_pickle(PyTypeObject *type, PyObject *state) +{ + PyDateTime_Date *me; + + me = (PyDateTime_Date *) (type->tp_alloc(type, 0)); + if (me != NULL) { + const char *pdata = PyBytes_AS_STRING(state); + memcpy(me->data, pdata, _PyDateTime_DATE_DATASIZE); + me->hashcode = -1; + } + return (PyObject *)me; +} + static PyObject * date_new(PyTypeObject *type, PyObject *args, PyObject *kw) { PyObject *self = NULL; - PyObject *state; int year; int month; int day; /* Check for invocation from pickle with __getstate__ state */ - if (PyTuple_GET_SIZE(args) == 1) { - state = PyTuple_GET_ITEM(args, 0); - if (PyBytes_Check(state) && - PyBytes_GET_SIZE(state) == _PyDateTime_DATE_DATASIZE && - MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) - { - PyDateTime_Date *me; - - me = (PyDateTime_Date *) (type->tp_alloc(type, 0)); - if (me != NULL) { - char *pdata = PyBytes_AS_STRING(state); - memcpy(me->data, pdata, _PyDateTime_DATE_DATASIZE); - me->hashcode = -1; + if (PyTuple_GET_SIZE(args) >= 1) { + PyObject *state = PyTuple_GET_ITEM(args, 0); + if (PyBytes_Check(state)) { + if (PyBytes_GET_SIZE(state) == _PyDateTime_DATE_DATASIZE && + MONTH_IS_SANE(PyBytes_AS_STRING(state)[2])) + { + return date_from_pickle(type, state); + } + } + else if (PyUnicode_Check(state)) { + if (PyUnicode_READY(state)) { + return NULL; + } + if (PyUnicode_GET_LENGTH(state) == _PyDateTime_DATE_DATASIZE && + MONTH_IS_SANE(PyUnicode_READ_CHAR(state, 2))) + { + state = PyUnicode_AsLatin1String(state); + if (state == NULL) { + if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) { + /* More informative error message. */ + PyErr_SetString(PyExc_ValueError, + "Failed to encode latin1 string when unpickling " + "a date object. " + "pickle.load(data, encoding='latin1') is assumed."); + } + return NULL; + } + self = date_from_pickle(type, state); + Py_DECREF(state); + return self; } - return (PyObject *)me; } } @@ -3901,11 +3930,43 @@ static PyGetSetDef time_getset[] = { static char *time_kws[] = {"hour", "minute", "second", "microsecond", "tzinfo", "fold", NULL}; +static PyObject * +time_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) +{ + PyDateTime_Time *me; + char aware = (char)(tzinfo != Py_None); + + if (aware && check_tzinfo_subclass(tzinfo) < 0) { + PyErr_SetString(PyExc_TypeError, "bad tzinfo state arg"); + return NULL; + } + + me = (PyDateTime_Time *) (type->tp_alloc(type, aware)); + if (me != NULL) { + const char *pdata = PyBytes_AS_STRING(state); + + memcpy(me->data, pdata, _PyDateTime_TIME_DATASIZE); + me->hashcode = -1; + me->hastzinfo = aware; + if (aware) { + Py_INCREF(tzinfo); + me->tzinfo = tzinfo; + } + if (pdata[0] & (1 << 7)) { + me->data[0] -= 128; + me->fold = 1; + } + else { + me->fold = 0; + } + } + return (PyObject *)me; +} + static PyObject * time_new(PyTypeObject *type, PyObject *args, PyObject *kw) { PyObject *self = NULL; - PyObject *state; int hour = 0; int minute = 0; int second = 0; @@ -3914,47 +3975,42 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw) int fold = 0; /* Check for invocation from pickle with __getstate__ state */ - if (PyTuple_GET_SIZE(args) >= 1 && - PyTuple_GET_SIZE(args) <= 2) - { - state = PyTuple_GET_ITEM(args, 0); - if (PyBytes_Check(state) && - PyBytes_GET_SIZE(state) == _PyDateTime_TIME_DATASIZE && - (0x7F & ((unsigned char) (PyBytes_AS_STRING(state)[0]))) < 24) - { - PyDateTime_Time *me; - char aware; - - if (PyTuple_GET_SIZE(args) == 2) { - tzinfo = PyTuple_GET_ITEM(args, 1); - if (check_tzinfo_subclass(tzinfo) < 0) { - PyErr_SetString(PyExc_TypeError, "bad " - "tzinfo state arg"); - return NULL; - } + if (PyTuple_GET_SIZE(args) >= 1 && PyTuple_GET_SIZE(args) <= 2) { + PyObject *state = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) == 2) { + tzinfo = PyTuple_GET_ITEM(args, 1); + } + if (PyBytes_Check(state)) { + if (PyBytes_GET_SIZE(state) == _PyDateTime_TIME_DATASIZE && + (0x7F & ((unsigned char) (PyBytes_AS_STRING(state)[0]))) < 24) + { + return time_from_pickle(type, state, tzinfo); } - aware = (char)(tzinfo != Py_None); - me = (PyDateTime_Time *) (type->tp_alloc(type, aware)); - if (me != NULL) { - char *pdata = PyBytes_AS_STRING(state); - - memcpy(me->data, pdata, _PyDateTime_TIME_DATASIZE); - me->hashcode = -1; - me->hastzinfo = aware; - if (aware) { - Py_INCREF(tzinfo); - me->tzinfo = tzinfo; - } - if (pdata[0] & (1 << 7)) { - me->data[0] -= 128; - me->fold = 1; - } - else { - me->fold = 0; + } + else if (PyUnicode_Check(state)) { + if (PyUnicode_READY(state)) { + return NULL; + } + if (PyUnicode_GET_LENGTH(state) == _PyDateTime_TIME_DATASIZE && + (0x7F & PyUnicode_READ_CHAR(state, 2)) < 24) + { + state = PyUnicode_AsLatin1String(state); + if (state == NULL) { + if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) { + /* More informative error message. */ + PyErr_SetString(PyExc_ValueError, + "Failed to encode latin1 string when unpickling " + "a time object. " + "pickle.load(data, encoding='latin1') is assumed."); + } + return NULL; } + self = time_from_pickle(type, state, tzinfo); + Py_DECREF(state); + return self; } - return (PyObject *)me; } + tzinfo = Py_None; } if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i", time_kws, @@ -4540,11 +4596,43 @@ static char *datetime_kws[] = { "microsecond", "tzinfo", "fold", NULL }; +static PyObject * +datetime_from_pickle(PyTypeObject *type, PyObject *state, PyObject *tzinfo) +{ + PyDateTime_DateTime *me; + char aware = (char)(tzinfo != Py_None); + + if (aware && check_tzinfo_subclass(tzinfo) < 0) { + PyErr_SetString(PyExc_TypeError, "bad tzinfo state arg"); + return NULL; + } + + me = (PyDateTime_DateTime *) (type->tp_alloc(type , aware)); + if (me != NULL) { + const char *pdata = PyBytes_AS_STRING(state); + + memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE); + me->hashcode = -1; + me->hastzinfo = aware; + if (aware) { + Py_INCREF(tzinfo); + me->tzinfo = tzinfo; + } + if (pdata[2] & (1 << 7)) { + me->data[2] -= 128; + me->fold = 1; + } + else { + me->fold = 0; + } + } + return (PyObject *)me; +} + static PyObject * datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) { PyObject *self = NULL; - PyObject *state; int year; int month; int day; @@ -4556,47 +4644,42 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) PyObject *tzinfo = Py_None; /* Check for invocation from pickle with __getstate__ state */ - if (PyTuple_GET_SIZE(args) >= 1 && - PyTuple_GET_SIZE(args) <= 2) - { - state = PyTuple_GET_ITEM(args, 0); - if (PyBytes_Check(state) && - PyBytes_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && - MONTH_IS_SANE(PyBytes_AS_STRING(state)[2] & 0x7F)) - { - PyDateTime_DateTime *me; - char aware; - - if (PyTuple_GET_SIZE(args) == 2) { - tzinfo = PyTuple_GET_ITEM(args, 1); - if (check_tzinfo_subclass(tzinfo) < 0) { - PyErr_SetString(PyExc_TypeError, "bad " - "tzinfo state arg"); - return NULL; - } + if (PyTuple_GET_SIZE(args) >= 1 && PyTuple_GET_SIZE(args) <= 2) { + PyObject *state = PyTuple_GET_ITEM(args, 0); + if (PyTuple_GET_SIZE(args) == 2) { + tzinfo = PyTuple_GET_ITEM(args, 1); + } + if (PyBytes_Check(state)) { + if (PyBytes_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && + MONTH_IS_SANE(PyBytes_AS_STRING(state)[2] & 0x7F)) + { + return datetime_from_pickle(type, state, tzinfo); } - aware = (char)(tzinfo != Py_None); - me = (PyDateTime_DateTime *) (type->tp_alloc(type , aware)); - if (me != NULL) { - char *pdata = PyBytes_AS_STRING(state); - - memcpy(me->data, pdata, _PyDateTime_DATETIME_DATASIZE); - me->hashcode = -1; - me->hastzinfo = aware; - if (aware) { - Py_INCREF(tzinfo); - me->tzinfo = tzinfo; - } - if (pdata[2] & (1 << 7)) { - me->data[2] -= 128; - me->fold = 1; - } - else { - me->fold = 0; + } + else if (PyUnicode_Check(state)) { + if (PyUnicode_READY(state)) { + return NULL; + } + if (PyUnicode_GET_LENGTH(state) == _PyDateTime_DATETIME_DATASIZE && + MONTH_IS_SANE(PyUnicode_READ_CHAR(state, 2) & 0x7F)) + { + state = PyUnicode_AsLatin1String(state); + if (state == NULL) { + if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) { + /* More informative error message. */ + PyErr_SetString(PyExc_ValueError, + "Failed to encode latin1 string when unpickling " + "a datetime object. " + "pickle.load(data, encoding='latin1') is assumed."); + } + return NULL; } + self = datetime_from_pickle(type, state, tzinfo); + Py_DECREF(state); + return self; } - return (PyObject *)me; } + tzinfo = Py_None; } if (PyArg_ParseTupleAndKeywords(args, kw, "iii|iiiiO$i", datetime_kws, From webhook-mailer at python.org Fri Dec 7 06:57:47 2018 From: webhook-mailer at python.org (Victor Stinner) Date: Fri, 07 Dec 2018 11:57:47 -0000 Subject: [Python-checkins] Revert "bpo-34977: Add Windows App Store package (GH-10245)" (GH-11019) Message-ID: https://github.com/python/cpython/commit/cb0b78a070ea3b704416e74f64046178ae0dff3e commit: cb0b78a070ea3b704416e74f64046178ae0dff3e branch: master author: Victor Stinner committer: GitHub date: 2018-12-07T12:57:43+01:00 summary: Revert "bpo-34977: Add Windows App Store package (GH-10245)" (GH-11019) This reverts commit 468a15aaf9206448a744fc5eab3fc21f51966aad. files: A Tools/msi/make_zip.py A Tools/nuget/python.props D .azure-pipelines/windows-appx-test.yml D Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst D PC/classicAppCompat.can.xml D PC/classicAppCompat.cat D PC/classicAppCompat.sccd D PC/icons/pythonwx150.png D PC/icons/pythonwx44.png D PC/icons/pythonx150.png D PC/icons/pythonx44.png D PC/icons/pythonx50.png D PC/layout/__init__.py D PC/layout/__main__.py D PC/layout/main.py D PC/layout/support/__init__.py D PC/layout/support/appxmanifest.py D PC/layout/support/catalog.py D PC/layout/support/constants.py D PC/layout/support/distutils.command.bdist_wininst.py D PC/layout/support/filesets.py D PC/layout/support/logging.py D PC/layout/support/options.py D PC/layout/support/pip.py D PC/layout/support/props.py D PC/layout/support/python.props D PC/python_uwp.cpp D PC/store_info.txt D PCbuild/python_uwp.vcxproj D PCbuild/pythonw_uwp.vcxproj D PCbuild/venvlauncher.vcxproj D PCbuild/venvwlauncher.vcxproj D Tools/msi/make_appx.ps1 D Tools/msi/make_cat.ps1 D Tools/msi/sdktools.psm1 D Tools/msi/sign_build.ps1 M .gitattributes M Doc/make.bat M Lib/test/test_pathlib.py M Lib/test/test_venv.py M Lib/venv/__init__.py M PC/getpathp.c M PC/launcher.c M PC/pylauncher.rc M PCbuild/_tkinter.vcxproj M PCbuild/find_msbuild.bat M PCbuild/pcbuild.proj M PCbuild/pcbuild.sln M PCbuild/python.props M PCbuild/pythoncore.vcxproj M Tools/msi/buildrelease.bat M Tools/msi/make_zip.proj M Tools/nuget/make_pkg.proj diff --git a/.azure-pipelines/windows-appx-test.yml b/.azure-pipelines/windows-appx-test.yml deleted file mode 100644 index 9840c0a1221f..000000000000 --- a/.azure-pipelines/windows-appx-test.yml +++ /dev/null @@ -1,65 +0,0 @@ -jobs: -- job: Prebuild - displayName: Pre-build checks - - pool: - vmImage: ubuntu-16.04 - - steps: - - template: ./prebuild-checks.yml - - -- job: Windows_Appx_Tests - displayName: Windows Appx Tests - dependsOn: Prebuild - condition: and(succeeded(), eq(dependencies.Prebuild.outputs['tests.run'], 'true')) - - pool: - vmImage: vs2017-win2016 - - strategy: - matrix: - win64: - arch: amd64 - buildOpt: '-p x64' - testRunTitle: '$(Build.SourceBranchName)-win64-appx' - testRunPlatform: win64 - maxParallel: 2 - - steps: - - checkout: self - clean: true - fetchDepth: 5 - - - powershell: | - # Relocate build outputs outside of source directory to make cleaning faster - Write-Host '##vso[task.setvariable variable=Py_IntDir]$(Build.BinariesDirectory)\obj' - # UNDONE: Do not build to a different directory because of broken tests - Write-Host '##vso[task.setvariable variable=Py_OutDir]$(Build.SourcesDirectory)\PCbuild' - Write-Host '##vso[task.setvariable variable=EXTERNAL_DIR]$(Build.BinariesDirectory)\externals' - displayName: Update build locations - - - script: PCbuild\build.bat -e $(buildOpt) - displayName: 'Build CPython' - - - script: python.bat PC\layout -vv -s "$(Build.SourcesDirectory)" -b "$(Py_OutDir)\$(arch)" -t "$(Py_IntDir)\layout-tmp-$(arch)" --copy "$(Py_IntDir)\layout-$(arch)" --precompile --preset-appx --include-tests - displayName: 'Create APPX layout' - - - script: .\python.exe -m test.pythoninfo - workingDirectory: $(Py_IntDir)\layout-$(arch) - displayName: 'Display build info' - - - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results.xml" --tempdir "$(Py_IntDir)\tmp-$(arch)" - workingDirectory: $(Py_IntDir)\layout-$(arch) - displayName: 'Tests' - env: - PREFIX: $(Py_IntDir)\layout-$(arch) - - - task: PublishTestResults at 2 - displayName: 'Publish Test Results' - inputs: - testResultsFiles: '$(Build.BinariesDirectory)\test-results.xml' - mergeTestResults: true - testRunTitle: $(testRunTitle) - platform: $(testRunPlatform) - condition: succeededOrFailed() diff --git a/.gitattributes b/.gitattributes index 16237bb2b3ac..4a487c3c2a14 100644 --- a/.gitattributes +++ b/.gitattributes @@ -19,7 +19,6 @@ # Specific binary files Lib/test/sndhdrdata/sndhdr.* binary -PC/classicAppCompat.* binary # Text files that should not be subject to eol conversion Lib/test/cjkencodings/* -text diff --git a/Doc/make.bat b/Doc/make.bat index a8b32375810d..d28dae78e86d 100644 --- a/Doc/make.bat +++ b/Doc/make.bat @@ -117,12 +117,10 @@ if not exist "%BUILDDIR%" mkdir "%BUILDDIR%" if exist ..\Misc\NEWS ( echo.Copying Misc\NEWS to build\NEWS - if not exist build mkdir build copy ..\Misc\NEWS build\NEWS > nul ) else if exist ..\Misc\NEWS.D ( if defined BLURB ( echo.Merging Misc/NEWS with %BLURB% - if not exist build mkdir build %BLURB% merge -f build\NEWS ) else ( echo.No Misc/NEWS file and Blurb is not available. diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index d3fd4bd9e6b7..876eecccfd5f 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1521,7 +1521,7 @@ def test_resolve_common(self): # resolves to 'dirB/..' first before resolving to parent of dirB. self._check_resolve_relative(p, P(BASE, 'foo', 'in', 'spam'), False) # Now create absolute symlinks - d = support._longpath(tempfile.mkdtemp(suffix='-dirD', dir=os.getcwd())) + d = support._longpath(tempfile.mkdtemp(suffix='-dirD')) self.addCleanup(support.rmtree, d) os.symlink(os.path.join(d), join('dirA', 'linkX')) os.symlink(join('dirB'), os.path.join(d, 'linkY')) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 22a3b78852f8..461fe7afd213 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -243,7 +243,6 @@ def test_isolation(self): self.assertIn('include-system-site-packages = %s\n' % s, data) @unittest.skipUnless(can_symlink(), 'Needs symlinks') - @unittest.skipIf(os.name == 'nt', 'Symlinks are never used on Windows') def test_symlinking(self): """ Test symlinking works as expected diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 5438b0d4e508..043420897e47 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -64,11 +64,10 @@ def create(self, env_dir): self.system_site_packages = False self.create_configuration(context) self.setup_python(context) - if not self.upgrade: - self.setup_scripts(context) if self.with_pip: self._setup_pip(context) if not self.upgrade: + self.setup_scripts(context) self.post_setup(context) if true_system_site_packages: # We had set it to False before, now @@ -159,6 +158,14 @@ def create_configuration(self, context): f.write('include-system-site-packages = %s\n' % incl) f.write('version = %d.%d.%d\n' % sys.version_info[:3]) + if os.name == 'nt': + def include_binary(self, f): + if f.endswith(('.pyd', '.dll')): + result = True + else: + result = f.startswith('python') and f.endswith('.exe') + return result + def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): """ Try symlinking a file, and if that fails, fall back to copying. @@ -188,9 +195,9 @@ def setup_python(self, context): binpath = context.bin_path path = context.env_exe copier = self.symlink_or_copy + copier(context.executable, path) dirname = context.python_dir if os.name != 'nt': - copier(context.executable, path) if not os.path.islink(path): os.chmod(path, 0o755) for suffix in ('python', 'python3'): @@ -202,22 +209,26 @@ def setup_python(self, context): if not os.path.islink(path): os.chmod(path, 0o755) else: - # For normal cases, the venvlauncher will be copied from - # our scripts folder. For builds, we need to copy it - # manually. - if sysconfig.is_python_build(True): - suffix = '.exe' - if context.python_exe.lower().endswith('_d.exe'): - suffix = '_d.exe' - - src = os.path.join(dirname, "venvlauncher" + suffix) - dst = os.path.join(binpath, context.python_exe) - copier(src, dst) - - src = os.path.join(dirname, "venvwlauncher" + suffix) - dst = os.path.join(binpath, "pythonw" + suffix) - copier(src, dst) + # See bpo-34011. When using a proper install, we should only need to + # copy the top-level of DLLs. + include = self.include_binary + files = [f for f in os.listdir(dirname) if include(f)] + for f in files: + src = os.path.join(dirname, f) + dst = os.path.join(binpath, f) + if dst != context.env_exe: # already done, above + copier(src, dst) + # When creating from a build directory, we continue to copy all files. + if sysconfig.is_python_build(True): + subdir = 'DLLs' + dirname = os.path.join(dirname, subdir) + if os.path.isdir(dirname): + files = [f for f in os.listdir(dirname) if include(f)] + for f in files: + src = os.path.join(dirname, f) + dst = os.path.join(binpath, f) + copier(src, dst) # copy init.tcl over for root, dirs, files in os.walk(context.python_dir): if 'init.tcl' in files: @@ -315,7 +326,7 @@ def install_scripts(self, context, path): dstfile = os.path.join(dstdir, f) with open(srcfile, 'rb') as f: data = f.read() - if not srcfile.endswith(('.exe', '.pdb')): + if not srcfile.endswith('.exe'): try: data = data.decode('utf-8') data = self.replace_variables(data, context) diff --git a/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst b/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst deleted file mode 100644 index 8e1a4ba84880..000000000000 --- a/Misc/NEWS.d/next/Windows/2018-10-30-13-39-17.bpo-34977.0l7_QV.rst +++ /dev/null @@ -1 +0,0 @@ -Adds support for building a Windows App Store package diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml deleted file mode 100644 index f00475c8da31..000000000000 --- a/PC/classicAppCompat.can.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat deleted file mode 100644 index 3d213596accf..000000000000 Binary files a/PC/classicAppCompat.cat and /dev/null differ diff --git a/PC/classicAppCompat.sccd b/PC/classicAppCompat.sccd deleted file mode 100644 index 97648985a2cc..000000000000 --- a/PC/classicAppCompat.sccd +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 diff --git a/PC/getpathp.c b/PC/getpathp.c index 76d3a081b363..35c31bf29bdf 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -536,16 +536,10 @@ static _PyInitError get_program_full_path(const _PyCoreConfig *core_config, PyCalculatePath *calculate, _PyPathConfig *config) { - const wchar_t *pyvenv_launcher; wchar_t program_full_path[MAXPATHLEN+1]; memset(program_full_path, 0, sizeof(program_full_path)); - /* The launcher may need to force the executable path to a - * different environment, so override it here. */ - pyvenv_launcher = _wgetenv(L"__PYVENV_LAUNCHER__"); - if (pyvenv_launcher && pyvenv_launcher[0]) { - wcscpy_s(program_full_path, MAXPATHLEN+1, pyvenv_launcher); - } else if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { + if (!GetModuleFileNameW(NULL, program_full_path, MAXPATHLEN)) { /* GetModuleFileName should never fail when passed NULL */ return _Py_INIT_ERR("Cannot determine program path"); } diff --git a/PC/icons/pythonwx150.png b/PC/icons/pythonwx150.png deleted file mode 100644 index 4c3eb316739c..000000000000 Binary files a/PC/icons/pythonwx150.png and /dev/null differ diff --git a/PC/icons/pythonwx44.png b/PC/icons/pythonwx44.png deleted file mode 100644 index e3b32a871f90..000000000000 Binary files a/PC/icons/pythonwx44.png and /dev/null differ diff --git a/PC/icons/pythonx150.png b/PC/icons/pythonx150.png deleted file mode 100644 index 5f8d30418386..000000000000 Binary files a/PC/icons/pythonx150.png and /dev/null differ diff --git a/PC/icons/pythonx44.png b/PC/icons/pythonx44.png deleted file mode 100644 index 3881daaef233..000000000000 Binary files a/PC/icons/pythonx44.png and /dev/null differ diff --git a/PC/icons/pythonx50.png b/PC/icons/pythonx50.png deleted file mode 100644 index 7cc3aecd0242..000000000000 Binary files a/PC/icons/pythonx50.png and /dev/null differ diff --git a/PC/launcher.c b/PC/launcher.c index 4c620dab7c09..2c2da76f6146 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -28,7 +28,7 @@ #define RC_NO_PYTHON 103 #define RC_NO_MEMORY 104 /* - * SCRIPT_WRAPPER is used to choose one of the variants of an executable built + * SCRIPT_WRAPPER is used to choose between two variants of an executable built * from this source file. If not defined, the PEP 397 Python launcher is built; * if defined, a script launcher of the type used by setuptools is built, which * looks for a script name related to the executable name and runs that script @@ -40,15 +40,6 @@ #if defined(SCRIPT_WRAPPER) #define RC_NO_SCRIPT 105 #endif -/* - * VENV_REDIRECT is used to choose the variant that looks for an adjacent or - * one-level-higher pyvenv.cfg, and uses its "home" property to locate and - * launch the original python.exe. - */ -#if defined(VENV_REDIRECT) -#define RC_NO_VENV_CFG 106 -#define RC_BAD_VENV_CFG 107 -#endif /* Just for now - static definition */ @@ -106,7 +97,7 @@ error(int rc, wchar_t * format, ... ) #if !defined(_WINDOWS) fwprintf(stderr, L"%ls\n", message); #else - MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...", + MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."), MB_OK); #endif exit(rc); @@ -140,17 +131,6 @@ static wchar_t * get_env(wchar_t * key) return buf; } -#if defined(_DEBUG) -#if defined(_WINDOWS) - -#define PYTHON_EXECUTABLE L"pythonw_d.exe" - -#else - -#define PYTHON_EXECUTABLE L"python_d.exe" - -#endif -#else #if defined(_WINDOWS) #define PYTHON_EXECUTABLE L"pythonw.exe" @@ -159,7 +139,6 @@ static wchar_t * get_env(wchar_t * key) #define PYTHON_EXECUTABLE L"python.exe" -#endif #endif #define MAX_VERSION_SIZE 4 @@ -1478,87 +1457,6 @@ show_python_list(wchar_t ** argv) return FALSE; /* If this has been called we cannot continue */ } -#if defined(VENV_REDIRECT) - -static int -find_home_value(const char *buffer, const char **start, DWORD *length) -{ - for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) { - if (*s == '\n') { - ++s; - } - for (int i = 4; i > 0 && *s; --i, ++s); - - while (*s && iswspace(*s)) { - ++s; - } - if (*s != L'=') { - continue; - } - - do { - ++s; - } while (*s && iswspace(*s)); - - *start = s; - char *nl = strchr(s, '\n'); - if (nl) { - *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s); - } else { - *length = (DWORD)strlen(s); - } - return 1; - } - return 0; -} -#endif - -static wchar_t * -wcsdup_pad(const wchar_t *s, int padding, int *newlen) -{ - size_t len = wcslen(s); - len += 1 + padding; - wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t)); - if (!r) { - return NULL; - } - if (wcscpy_s(r, len, s)) { - free(r); - return NULL; - } - *newlen = len < MAXINT ? (int)len : MAXINT; - return r; -} - -static wchar_t * -get_process_name() -{ - DWORD bufferLen = MAX_PATH; - DWORD len = bufferLen; - wchar_t *r = NULL; - - while (!r) { - r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t)); - if (!r) { - error(RC_NO_MEMORY, L"out of memory"); - return NULL; - } - len = GetModuleFileNameW(NULL, r, bufferLen); - if (len == 0) { - free(r); - error(0, L"Failed to get module name"); - return NULL; - } else if (len == bufferLen && - GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - free(r); - r = NULL; - bufferLen *= 2; - } - } - - return r; -} - static int process(int argc, wchar_t ** argv) { @@ -1566,27 +1464,21 @@ process(int argc, wchar_t ** argv) wchar_t * command; wchar_t * executable; wchar_t * p; - wchar_t * argv0; int rc = 0; + size_t plen; INSTALLED_PYTHON * ip; BOOL valid; DWORD size, attrs; + HRESULT hr; wchar_t message[MSGSIZE]; void * version_data; VS_FIXEDFILEINFO * file_info; UINT block_size; -#if defined(VENV_REDIRECT) - wchar_t * venv_cfg_path; + int index; +#if defined(SCRIPT_WRAPPER) int newlen; -#elif defined(SCRIPT_WRAPPER) wchar_t * newcommand; wchar_t * av[2]; - int newlen; - HRESULT hr; - int index; -#else - HRESULT hr; - int index; #endif setvbuf(stderr, (char *)NULL, _IONBF, 0); @@ -1604,7 +1496,6 @@ process(int argc, wchar_t ** argv) #else debug(L"launcher executable: Console\n"); #endif -#if !defined(VENV_REDIRECT) /* Get the local appdata folder (non-roaming) */ hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, appdata_ini_path); @@ -1613,7 +1504,9 @@ process(int argc, wchar_t ** argv) appdata_ini_path[0] = L'\0'; } else { - wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE); + plen = wcslen(appdata_ini_path); + p = &appdata_ini_path[plen]; + wcsncpy_s(p, MAX_PATH - plen, L"\\py.ini", _TRUNCATE); attrs = GetFileAttributesW(appdata_ini_path); if (attrs == INVALID_FILE_ATTRIBUTES) { debug(L"File '%ls' non-existent\n", appdata_ini_path); @@ -1622,9 +1515,8 @@ process(int argc, wchar_t ** argv) debug(L"Using local configuration file '%ls'\n", appdata_ini_path); } } -#endif - argv0 = get_process_name(); - size = GetFileVersionInfoSizeW(argv0, &size); + plen = GetModuleFileNameW(NULL, launcher_ini_path, MAX_PATH); + size = GetFileVersionInfoSizeW(launcher_ini_path, &size); if (size == 0) { winerror(GetLastError(), message, MSGSIZE); debug(L"GetFileVersionInfoSize failed: %ls\n", message); @@ -1632,7 +1524,7 @@ process(int argc, wchar_t ** argv) else { version_data = malloc(size); if (version_data) { - valid = GetFileVersionInfoW(argv0, 0, size, + valid = GetFileVersionInfoW(launcher_ini_path, 0, size, version_data); if (!valid) debug(L"GetFileVersionInfo failed: %X\n", GetLastError()); @@ -1649,51 +1541,15 @@ process(int argc, wchar_t ** argv) free(version_data); } } - -#if defined(VENV_REDIRECT) - /* Allocate some extra space for new filenames */ - venv_cfg_path = wcsdup_pad(argv0, 32, &newlen); - if (!venv_cfg_path) { - error(RC_NO_MEMORY, L"Failed to copy module name"); - } - p = wcsrchr(venv_cfg_path, L'\\'); - - if (p == NULL) { - error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); - } - p[0] = L'\0'; - wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); - attrs = GetFileAttributesW(venv_cfg_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", venv_cfg_path); - p[0] = '\0'; - p = wcsrchr(venv_cfg_path, L'\\'); - if (p != NULL) { - p[0] = '\0'; - wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg"); - attrs = GetFileAttributesW(venv_cfg_path); - if (attrs == INVALID_FILE_ATTRIBUTES) { - debug(L"File '%ls' non-existent\n", venv_cfg_path); - error(RC_NO_VENV_CFG, L"No pyvenv.cfg file"); - } - } - } - debug(L"Using venv configuration file '%ls'\n", venv_cfg_path); -#else - /* Allocate some extra space for new filenames */ - if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) { - error(RC_NO_MEMORY, L"Failed to copy module name"); - } p = wcsrchr(launcher_ini_path, L'\\'); - if (p == NULL) { debug(L"GetModuleFileNameW returned value has no backslash: %ls\n", launcher_ini_path); launcher_ini_path[0] = L'\0'; } else { - p[0] = L'\0'; - wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini"); + wcsncpy_s(p, MAX_PATH - (p - launcher_ini_path), L"\\py.ini", + _TRUNCATE); attrs = GetFileAttributesW(launcher_ini_path); if (attrs == INVALID_FILE_ATTRIBUTES) { debug(L"File '%ls' non-existent\n", launcher_ini_path); @@ -1702,7 +1558,6 @@ process(int argc, wchar_t ** argv) debug(L"Using global configuration file '%ls'\n", launcher_ini_path); } } -#endif command = skip_me(GetCommandLineW()); debug(L"Called with command line: %ls\n", command); @@ -1738,55 +1593,6 @@ process(int argc, wchar_t ** argv) command = newcommand; valid = FALSE; } -#elif defined(VENV_REDIRECT) - { - FILE *f; - char buffer[4096]; /* 4KB should be enough for anybody */ - char *start; - DWORD len, cch, cch_actual; - size_t cb; - if (_wfopen_s(&f, venv_cfg_path, L"r")) { - error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path); - } - cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]), - sizeof(buffer) / sizeof(buffer[0]), f); - fclose(f); - - if (!find_home_value(buffer, &start, &len)) { - error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'", - venv_cfg_path); - } - - cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0); - if (!cch) { - error(0, L"Cannot determine memory for home path"); - } - cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 1 + 1; /* include sep and null */ - executable = (wchar_t *)malloc(cch * sizeof(wchar_t)); - if (executable == NULL) { - error(RC_NO_MEMORY, L"A memory allocation failed"); - } - cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, executable, cch); - if (!cch_actual) { - error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'", - venv_cfg_path); - } - if (executable[cch_actual - 1] != L'\\') { - executable[cch_actual++] = L'\\'; - executable[cch_actual] = L'\0'; - } - if (wcscat_s(executable, cch, PYTHON_EXECUTABLE)) { - error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'", - venv_cfg_path); - } - if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) { - error(RC_NO_PYTHON, L"No Python at '%ls'", executable); - } - if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) { - error(0, L"Failed to set launcher environment"); - } - valid = 1; - } #else if (argc <= 1) { valid = FALSE; @@ -1794,6 +1600,7 @@ process(int argc, wchar_t ** argv) } else { p = argv[1]; + plen = wcslen(p); if ((argc == 2) && // list version args (!wcsncmp(p, L"-0", wcslen(L"-0")) || !wcsncmp(p, L"--list", wcslen(L"--list")))) diff --git a/PC/layout/__init__.py b/PC/layout/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/PC/layout/__main__.py b/PC/layout/__main__.py deleted file mode 100644 index f7aa1e6d261f..000000000000 --- a/PC/layout/__main__.py +++ /dev/null @@ -1,14 +0,0 @@ -import sys - -try: - import layout -except ImportError: - # Failed to import our package, which likely means we were started directly - # Add the additional search path needed to locate our module. - from pathlib import Path - - sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from layout.main import main - -sys.exit(int(main() or 0)) diff --git a/PC/layout/main.py b/PC/layout/main.py deleted file mode 100644 index 82d0536ca920..000000000000 --- a/PC/layout/main.py +++ /dev/null @@ -1,612 +0,0 @@ -""" -Generates a layout of Python for Windows from a build. - -See python make_layout.py --help for usage. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - -import argparse -import functools -import os -import re -import shutil -import subprocess -import sys -import tempfile -import zipfile - -from pathlib import Path - -if __name__ == "__main__": - # Started directly, so enable relative imports - __path__ = [str(Path(__file__).resolve().parent)] - -from .support.appxmanifest import * -from .support.catalog import * -from .support.constants import * -from .support.filesets import * -from .support.logging import * -from .support.options import * -from .support.pip import * -from .support.props import * - -BDIST_WININST_FILES_ONLY = FileNameSet("wininst-*", "bdist_wininst.py") -BDIST_WININST_STUB = "PC/layout/support/distutils.command.bdist_wininst.py" - -TEST_PYDS_ONLY = FileStemSet("xxlimited", "_ctypes_test", "_test*") -TEST_DIRS_ONLY = FileNameSet("test", "tests") - -IDLE_DIRS_ONLY = FileNameSet("idlelib") - -TCLTK_PYDS_ONLY = FileStemSet("tcl*", "tk*", "_tkinter") -TCLTK_DIRS_ONLY = FileNameSet("tkinter", "turtledemo") -TCLTK_FILES_ONLY = FileNameSet("turtle.py") - -VENV_DIRS_ONLY = FileNameSet("venv", "ensurepip") - -EXCLUDE_FROM_PYDS = FileStemSet("python*", "pyshellext") -EXCLUDE_FROM_LIB = FileNameSet("*.pyc", "__pycache__", "*.pickle") -EXCLUDE_FROM_PACKAGED_LIB = FileNameSet("readme.txt") -EXCLUDE_FROM_COMPILE = FileNameSet("badsyntax_*", "bad_*") -EXCLUDE_FROM_CATALOG = FileSuffixSet(".exe", ".pyd", ".dll") - -REQUIRED_DLLS = FileStemSet("libcrypto*", "libssl*") - -LIB2TO3_GRAMMAR_FILES = FileNameSet("Grammar.txt", "PatternGrammar.txt") - -PY_FILES = FileSuffixSet(".py") -PYC_FILES = FileSuffixSet(".pyc") -CAT_FILES = FileSuffixSet(".cat") -CDF_FILES = FileSuffixSet(".cdf") - -DATA_DIRS = FileNameSet("data") - -TOOLS_DIRS = FileNameSet("scripts", "i18n", "pynche", "demo", "parser") -TOOLS_FILES = FileSuffixSet(".py", ".pyw", ".txt") - - -def get_lib_layout(ns): - def _c(f): - if f in EXCLUDE_FROM_LIB: - return False - if f.is_dir(): - if f in TEST_DIRS_ONLY: - return ns.include_tests - if f in TCLTK_DIRS_ONLY: - return ns.include_tcltk - if f in IDLE_DIRS_ONLY: - return ns.include_idle - if f in VENV_DIRS_ONLY: - return ns.include_venv - else: - if f in TCLTK_FILES_ONLY: - return ns.include_tcltk - if f in BDIST_WININST_FILES_ONLY: - return ns.include_bdist_wininst - return True - - for dest, src in rglob(ns.source / "Lib", "**/*", _c): - yield dest, src - - if not ns.include_bdist_wininst: - src = ns.source / BDIST_WININST_STUB - yield Path("distutils/command/bdist_wininst.py"), src - - -def get_tcltk_lib(ns): - if not ns.include_tcltk: - return - - tcl_lib = os.getenv("TCL_LIBRARY") - if not tcl_lib or not os.path.isdir(tcl_lib): - try: - with open(ns.build / "TCL_LIBRARY.env", "r", encoding="utf-8-sig") as f: - tcl_lib = f.read().strip() - except FileNotFoundError: - pass - if not tcl_lib or not os.path.isdir(tcl_lib): - warn("Failed to find TCL_LIBRARY") - return - - for dest, src in rglob(Path(tcl_lib).parent, "**/*"): - yield "tcl/{}".format(dest), src - - -def get_layout(ns): - def in_build(f, dest="", new_name=None): - n, _, x = f.rpartition(".") - n = new_name or n - src = ns.build / f - if ns.debug and src not in REQUIRED_DLLS: - if not src.stem.endswith("_d"): - src = src.parent / (src.stem + "_d" + src.suffix) - if not n.endswith("_d"): - n += "_d" - f = n + "." + x - yield dest + n + "." + x, src - if ns.include_symbols: - pdb = src.with_suffix(".pdb") - if pdb.is_file(): - yield dest + n + ".pdb", pdb - if ns.include_dev: - lib = src.with_suffix(".lib") - if lib.is_file(): - yield "libs/" + n + ".lib", lib - - yield from in_build("python_uwp.exe", new_name="python") - yield from in_build("pythonw_uwp.exe", new_name="pythonw") - - yield from in_build(PYTHON_DLL_NAME) - - if ns.include_launchers: - if ns.include_pip: - yield from in_build("python_uwp.exe", new_name="pip") - if ns.include_idle: - yield from in_build("pythonw_uwp.exe", new_name="idle") - - if ns.include_stable: - yield from in_build(PYTHON_STABLE_DLL_NAME) - - for dest, src in rglob(ns.build, "vcruntime*.dll"): - yield dest, src - - for dest, src in rglob(ns.build, ("*.pyd", "*.dll")): - if src.stem.endswith("_d") != bool(ns.debug) and src not in REQUIRED_DLLS: - continue - if src in EXCLUDE_FROM_PYDS: - continue - if src in TEST_PYDS_ONLY and not ns.include_tests: - continue - if src in TCLTK_PYDS_ONLY and not ns.include_tcltk: - continue - - yield from in_build(src.name, dest="" if ns.flat_dlls else "DLLs/") - - if ns.zip_lib: - zip_name = PYTHON_ZIP_NAME - yield zip_name, ns.temp / zip_name - else: - for dest, src in get_lib_layout(ns): - yield "Lib/{}".format(dest), src - - if ns.include_venv: - yield from in_build("venvlauncher.exe", "Lib/venv/scripts/nt/", "python") - yield from in_build("venvwlauncher.exe", "Lib/venv/scripts/nt/", "pythonw") - - if ns.include_tools: - - def _c(d): - if d.is_dir(): - return d in TOOLS_DIRS - return d in TOOLS_FILES - - for dest, src in rglob(ns.source / "Tools", "**/*", _c): - yield "Tools/{}".format(dest), src - - if ns.include_underpth: - yield PYTHON_PTH_NAME, ns.temp / PYTHON_PTH_NAME - - if ns.include_dev: - - def _c(d): - if d.is_dir(): - return d.name != "internal" - return True - - for dest, src in rglob(ns.source / "Include", "**/*.h", _c): - yield "include/{}".format(dest), src - src = ns.source / "PC" / "pyconfig.h" - yield "include/pyconfig.h", src - - for dest, src in get_tcltk_lib(ns): - yield dest, src - - if ns.include_pip: - pip_dir = get_pip_dir(ns) - if not pip_dir.is_dir(): - log_warning("Failed to find {} - pip will not be included", pip_dir) - else: - pkg_root = "packages/{}" if ns.zip_lib else "Lib/site-packages/{}" - for dest, src in rglob(pip_dir, "**/*"): - if src in EXCLUDE_FROM_LIB or src in EXCLUDE_FROM_PACKAGED_LIB: - continue - yield pkg_root.format(dest), src - - if ns.include_chm: - for dest, src in rglob(ns.doc_build / "htmlhelp", PYTHON_CHM_NAME): - yield "Doc/{}".format(dest), src - - if ns.include_html_doc: - for dest, src in rglob(ns.doc_build / "html", "**/*"): - yield "Doc/html/{}".format(dest), src - - if ns.include_props: - for dest, src in get_props_layout(ns): - yield dest, src - - for dest, src in get_appx_layout(ns): - yield dest, src - - if ns.include_cat: - if ns.flat_dlls: - yield ns.include_cat.name, ns.include_cat - else: - yield "DLLs/{}".format(ns.include_cat.name), ns.include_cat - - -def _compile_one_py(src, dest, name, optimize): - import py_compile - - if dest is not None: - dest = str(dest) - - try: - return Path( - py_compile.compile( - str(src), - dest, - str(name), - doraise=True, - optimize=optimize, - invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH, - ) - ) - except py_compile.PyCompileError: - log_warning("Failed to compile {}", src) - return None - - -def _py_temp_compile(src, ns, dest_dir=None): - if not ns.precompile or src not in PY_FILES or src.parent in DATA_DIRS: - return None - - dest = (dest_dir or ns.temp) / (src.stem + ".py") - return _compile_one_py(src, dest.with_suffix(".pyc"), dest, optimize=2) - - -def _write_to_zip(zf, dest, src, ns): - pyc = _py_temp_compile(src, ns) - if pyc: - try: - zf.write(str(pyc), dest.with_suffix(".pyc")) - finally: - try: - pyc.unlink() - except: - log_exception("Failed to delete {}", pyc) - return - - if src in LIB2TO3_GRAMMAR_FILES: - from lib2to3.pgen2.driver import load_grammar - - tmp = ns.temp / src.name - try: - shutil.copy(src, tmp) - load_grammar(str(tmp)) - for f in ns.temp.glob(src.stem + "*.pickle"): - zf.write(str(f), str(dest.parent / f.name)) - try: - f.unlink() - except: - log_exception("Failed to delete {}", f) - except: - log_exception("Failed to compile {}", src) - finally: - try: - tmp.unlink() - except: - log_exception("Failed to delete {}", tmp) - - zf.write(str(src), str(dest)) - - -def generate_source_files(ns): - if ns.zip_lib: - zip_name = PYTHON_ZIP_NAME - zip_path = ns.temp / zip_name - if zip_path.is_file(): - zip_path.unlink() - elif zip_path.is_dir(): - log_error( - "Cannot create zip file because a directory exists by the same name" - ) - return - log_info("Generating {} in {}", zip_name, ns.temp) - ns.temp.mkdir(parents=True, exist_ok=True) - with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: - for dest, src in get_lib_layout(ns): - _write_to_zip(zf, dest, src, ns) - - if ns.include_underpth: - log_info("Generating {} in {}", PYTHON_PTH_NAME, ns.temp) - ns.temp.mkdir(parents=True, exist_ok=True) - with open(ns.temp / PYTHON_PTH_NAME, "w", encoding="utf-8") as f: - if ns.zip_lib: - print(PYTHON_ZIP_NAME, file=f) - if ns.include_pip: - print("packages", file=f) - else: - print("Lib", file=f) - print("Lib/site-packages", file=f) - if not ns.flat_dlls: - print("DLLs", file=f) - print(".", file=f) - print(file=f) - print("# Uncomment to run site.main() automatically", file=f) - print("#import site", file=f) - - if ns.include_appxmanifest: - log_info("Generating AppxManifest.xml in {}", ns.temp) - ns.temp.mkdir(parents=True, exist_ok=True) - - with open(ns.temp / "AppxManifest.xml", "wb") as f: - f.write(get_appxmanifest(ns)) - - with open(ns.temp / "_resources.xml", "wb") as f: - f.write(get_resources_xml(ns)) - - if ns.include_pip: - pip_dir = get_pip_dir(ns) - if not (pip_dir / "pip").is_dir(): - log_info("Extracting pip to {}", pip_dir) - pip_dir.mkdir(parents=True, exist_ok=True) - extract_pip_files(ns) - - if ns.include_props: - log_info("Generating {} in {}", PYTHON_PROPS_NAME, ns.temp) - ns.temp.mkdir(parents=True, exist_ok=True) - with open(ns.temp / PYTHON_PROPS_NAME, "wb") as f: - f.write(get_props(ns)) - - -def _create_zip_file(ns): - if not ns.zip: - return None - - if ns.zip.is_file(): - try: - ns.zip.unlink() - except OSError: - log_exception("Unable to remove {}", ns.zip) - sys.exit(8) - elif ns.zip.is_dir(): - log_error("Cannot create ZIP file because {} is a directory", ns.zip) - sys.exit(8) - - ns.zip.parent.mkdir(parents=True, exist_ok=True) - return zipfile.ZipFile(ns.zip, "w", zipfile.ZIP_DEFLATED) - - -def copy_files(files, ns): - if ns.copy: - ns.copy.mkdir(parents=True, exist_ok=True) - - try: - total = len(files) - except TypeError: - total = None - count = 0 - - zip_file = _create_zip_file(ns) - try: - need_compile = [] - in_catalog = [] - - for dest, src in files: - count += 1 - if count % 10 == 0: - if total: - log_info("Processed {:>4} of {} files", count, total) - else: - log_info("Processed {} files", count) - log_debug("Processing {!s}", src) - - if ( - ns.precompile - and src in PY_FILES - and src not in EXCLUDE_FROM_COMPILE - and src.parent not in DATA_DIRS - and os.path.normcase(str(dest)).startswith(os.path.normcase("Lib")) - ): - if ns.copy: - need_compile.append((dest, ns.copy / dest)) - else: - (ns.temp / "Lib" / dest).parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(src, ns.temp / "Lib" / dest) - need_compile.append((dest, ns.temp / "Lib" / dest)) - - if src not in EXCLUDE_FROM_CATALOG: - in_catalog.append((src.name, src)) - - if ns.copy: - log_debug("Copy {} -> {}", src, ns.copy / dest) - (ns.copy / dest).parent.mkdir(parents=True, exist_ok=True) - try: - shutil.copy2(src, ns.copy / dest) - except shutil.SameFileError: - pass - - if ns.zip: - log_debug("Zip {} into {}", src, ns.zip) - zip_file.write(src, str(dest)) - - if need_compile: - for dest, src in need_compile: - compiled = [ - _compile_one_py(src, None, dest, optimize=0), - _compile_one_py(src, None, dest, optimize=1), - _compile_one_py(src, None, dest, optimize=2), - ] - for c in compiled: - if not c: - continue - cdest = Path(dest).parent / Path(c).relative_to(src.parent) - if ns.zip: - log_debug("Zip {} into {}", c, ns.zip) - zip_file.write(c, str(cdest)) - in_catalog.append((cdest.name, cdest)) - - if ns.catalog: - # Just write out the CDF now. Compilation and signing is - # an extra step - log_info("Generating {}", ns.catalog) - ns.catalog.parent.mkdir(parents=True, exist_ok=True) - write_catalog(ns.catalog, in_catalog) - - finally: - if zip_file: - zip_file.close() - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("-v", help="Increase verbosity", action="count") - parser.add_argument( - "-s", - "--source", - metavar="dir", - help="The directory containing the repository root", - type=Path, - default=None, - ) - parser.add_argument( - "-b", "--build", metavar="dir", help="Specify the build directory", type=Path - ) - parser.add_argument( - "--doc-build", - metavar="dir", - help="Specify the docs build directory", - type=Path, - default=None, - ) - parser.add_argument( - "--copy", - metavar="directory", - help="The name of the directory to copy an extracted layout to", - type=Path, - default=None, - ) - parser.add_argument( - "--zip", - metavar="file", - help="The ZIP file to write all files to", - type=Path, - default=None, - ) - parser.add_argument( - "--catalog", - metavar="file", - help="The CDF file to write catalog entries to", - type=Path, - default=None, - ) - parser.add_argument( - "--log", - metavar="file", - help="Write all operations to the specified file", - type=Path, - default=None, - ) - parser.add_argument( - "-t", - "--temp", - metavar="file", - help="A temporary working directory", - type=Path, - default=None, - ) - parser.add_argument( - "-d", "--debug", help="Include debug build", action="store_true" - ) - parser.add_argument( - "-p", - "--precompile", - help="Include .pyc files instead of .py", - action="store_true", - ) - parser.add_argument( - "-z", "--zip-lib", help="Include library in a ZIP file", action="store_true" - ) - parser.add_argument( - "--flat-dlls", help="Does not create a DLLs directory", action="store_true" - ) - parser.add_argument( - "-a", - "--include-all", - help="Include all optional components", - action="store_true", - ) - parser.add_argument( - "--include-cat", - metavar="file", - help="Specify the catalog file to include", - type=Path, - default=None, - ) - for opt, help in get_argparse_options(): - parser.add_argument(opt, help=help, action="store_true") - - ns = parser.parse_args() - update_presets(ns) - - ns.source = ns.source or (Path(__file__).resolve().parent.parent.parent) - ns.build = ns.build or Path(sys.executable).parent - ns.temp = ns.temp or Path(tempfile.mkdtemp()) - ns.doc_build = ns.doc_build or (ns.source / "Doc" / "build") - if not ns.source.is_absolute(): - ns.source = (Path.cwd() / ns.source).resolve() - if not ns.build.is_absolute(): - ns.build = (Path.cwd() / ns.build).resolve() - if not ns.temp.is_absolute(): - ns.temp = (Path.cwd() / ns.temp).resolve() - if not ns.doc_build.is_absolute(): - ns.doc_build = (Path.cwd() / ns.doc_build).resolve() - if ns.include_cat and not ns.include_cat.is_absolute(): - ns.include_cat = (Path.cwd() / ns.include_cat).resolve() - - if ns.copy and not ns.copy.is_absolute(): - ns.copy = (Path.cwd() / ns.copy).resolve() - if ns.zip and not ns.zip.is_absolute(): - ns.zip = (Path.cwd() / ns.zip).resolve() - if ns.catalog and not ns.catalog.is_absolute(): - ns.catalog = (Path.cwd() / ns.catalog).resolve() - - configure_logger(ns) - - log_info( - """OPTIONS -Source: {ns.source} -Build: {ns.build} -Temp: {ns.temp} - -Copy to: {ns.copy} -Zip to: {ns.zip} -Catalog: {ns.catalog}""", - ns=ns, - ) - - if ns.include_idle and not ns.include_tcltk: - log_warning("Assuming --include-tcltk to support --include-idle") - ns.include_tcltk = True - - try: - generate_source_files(ns) - files = list(get_layout(ns)) - copy_files(files, ns) - except KeyboardInterrupt: - log_info("Interrupted by Ctrl+C") - return 3 - except SystemExit: - raise - except: - log_exception("Unhandled error") - - if error_was_logged(): - log_error("Errors occurred.") - return 1 - - -if __name__ == "__main__": - sys.exit(int(main() or 0)) diff --git a/PC/layout/support/__init__.py b/PC/layout/support/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py deleted file mode 100644 index c5dda70c7ef8..000000000000 --- a/PC/layout/support/appxmanifest.py +++ /dev/null @@ -1,487 +0,0 @@ -""" -File generation for APPX/MSIX manifests. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - - -import collections -import ctypes -import io -import os -import sys - -from pathlib import Path, PureWindowsPath -from xml.etree import ElementTree as ET - -from .constants import * - -__all__ = [] - - -def public(f): - __all__.append(f.__name__) - return f - - -APPX_DATA = dict( - Name="PythonSoftwareFoundation.Python.{}".format(VER_DOT), - Version="{}.{}.{}.0".format(VER_MAJOR, VER_MINOR, VER_FIELD3), - Publisher=os.getenv( - "APPX_DATA_PUBLISHER", "CN=4975D53F-AA7E-49A5-8B49-EA4FDC1BB66B" - ), - DisplayName="Python {}".format(VER_DOT), - Description="The Python {} runtime and console.".format(VER_DOT), - ProcessorArchitecture="x64" if IS_X64 else "x86", -) - -PYTHON_VE_DATA = dict( - DisplayName="Python {}".format(VER_DOT), - Description="Python interactive console", - Square150x150Logo="_resources/pythonx150.png", - Square44x44Logo="_resources/pythonx44.png", - BackgroundColor="transparent", -) - -PYTHONW_VE_DATA = dict( - DisplayName="Python {} (Windowed)".format(VER_DOT), - Description="Python windowed app launcher", - Square150x150Logo="_resources/pythonwx150.png", - Square44x44Logo="_resources/pythonwx44.png", - BackgroundColor="transparent", - AppListEntry="none", -) - -PIP_VE_DATA = dict( - DisplayName="pip (Python {})".format(VER_DOT), - Description="pip package manager for Python {}".format(VER_DOT), - Square150x150Logo="_resources/pythonx150.png", - Square44x44Logo="_resources/pythonx44.png", - BackgroundColor="transparent", - AppListEntry="none", -) - -IDLE_VE_DATA = dict( - DisplayName="IDLE (Python {})".format(VER_DOT), - Description="IDLE editor for Python {}".format(VER_DOT), - Square150x150Logo="_resources/pythonwx150.png", - Square44x44Logo="_resources/pythonwx44.png", - BackgroundColor="transparent", -) - -APPXMANIFEST_NS = { - "": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", - "m": "http://schemas.microsoft.com/appx/manifest/foundation/windows10", - "uap": "http://schemas.microsoft.com/appx/manifest/uap/windows10", - "rescap": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities", - "rescap4": "http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/4", - "desktop4": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/4", - "desktop6": "http://schemas.microsoft.com/appx/manifest/desktop/windows10/6", - "uap3": "http://schemas.microsoft.com/appx/manifest/uap/windows10/3", - "uap4": "http://schemas.microsoft.com/appx/manifest/uap/windows10/4", - "uap5": "http://schemas.microsoft.com/appx/manifest/uap/windows10/5", -} - -APPXMANIFEST_TEMPLATE = """ - - - - - Python Software Foundation - - _resources/pythonx50.png - - - - - - - - - - - - - - -""" - - -RESOURCES_XML_TEMPLATE = r""" - - - - - - - - - - - - - - - - - - - - - - - - - - - -""" - - -SCCD_FILENAME = "PC/classicAppCompat.sccd" - -REGISTRY = { - "HKCU\\Software\\Python\\PythonCore": { - VER_DOT: { - "DisplayName": APPX_DATA["DisplayName"], - "SupportUrl": "https://www.python.org/", - "SysArchitecture": "64bit" if IS_X64 else "32bit", - "SysVersion": VER_DOT, - "Version": "{}.{}.{}".format(VER_MAJOR, VER_MINOR, VER_MICRO), - "InstallPath": { - # I have no idea why the trailing spaces are needed, but they seem to be needed. - "": "[{AppVPackageRoot}][ ]", - "ExecutablePath": "[{AppVPackageRoot}]python.exe[ ]", - "WindowedExecutablePath": "[{AppVPackageRoot}]pythonw.exe[ ]", - }, - "Help": { - "Main Python Documentation": { - "_condition": lambda ns: ns.include_chm, - "": "[{{AppVPackageRoot}}]Doc\\{}[ ]".format( - PYTHON_CHM_NAME - ), - }, - "Local Python Documentation": { - "_condition": lambda ns: ns.include_html_doc, - "": "[{AppVPackageRoot}]Doc\\html\\index.html[ ]", - }, - "Online Python Documentation": { - "": "https://docs.python.org/{}".format(VER_DOT) - }, - }, - "Idle": { - "_condition": lambda ns: ns.include_idle, - "": "[{AppVPackageRoot}]Lib\\idlelib\\idle.pyw[ ]", - }, - } - } -} - - -def get_packagefamilyname(name, publisher_id): - class PACKAGE_ID(ctypes.Structure): - _fields_ = [ - ("reserved", ctypes.c_uint32), - ("processorArchitecture", ctypes.c_uint32), - ("version", ctypes.c_uint64), - ("name", ctypes.c_wchar_p), - ("publisher", ctypes.c_wchar_p), - ("resourceId", ctypes.c_wchar_p), - ("publisherId", ctypes.c_wchar_p), - ] - _pack_ = 4 - - pid = PACKAGE_ID(0, 0, 0, name, publisher_id, None, None) - result = ctypes.create_unicode_buffer(256) - result_len = ctypes.c_uint32(256) - r = ctypes.windll.kernel32.PackageFamilyNameFromId( - pid, ctypes.byref(result_len), result - ) - if r: - raise OSError(r, "failed to get package family name") - return result.value[: result_len.value] - - -def _fixup_sccd(ns, sccd, new_hash=None): - if not new_hash: - return sccd - - NS = dict(s="http://schemas.microsoft.com/appx/2016/sccd") - with open(sccd, "rb") as f: - xml = ET.parse(f) - - pfn = get_packagefamilyname(APPX_DATA["Name"], APPX_DATA["Publisher"]) - - ae = xml.find("s:AuthorizedEntities", NS) - ae.clear() - - e = ET.SubElement(ae, ET.QName(NS["s"], "AuthorizedEntity")) - e.set("AppPackageFamilyName", pfn) - e.set("CertificateSignatureHash", new_hash) - - for e in xml.findall("s:Catalog", NS): - e.text = "FFFF" - - sccd = ns.temp / sccd.name - sccd.parent.mkdir(parents=True, exist_ok=True) - with open(sccd, "wb") as f: - xml.write(f, encoding="utf-8") - - return sccd - - - at public -def get_appx_layout(ns): - if not ns.include_appxmanifest: - return - - yield "AppxManifest.xml", ns.temp / "AppxManifest.xml" - yield "_resources.xml", ns.temp / "_resources.xml" - icons = ns.source / "PC" / "icons" - yield "_resources/pythonx44.png", icons / "pythonx44.png" - yield "_resources/pythonx44$targetsize-44_altform-unplated.png", icons / "pythonx44.png" - yield "_resources/pythonx50.png", icons / "pythonx50.png" - yield "_resources/pythonx50$targetsize-50_altform-unplated.png", icons / "pythonx50.png" - yield "_resources/pythonx150.png", icons / "pythonx150.png" - yield "_resources/pythonx150$targetsize-150_altform-unplated.png", icons / "pythonx150.png" - yield "_resources/pythonwx44.png", icons / "pythonwx44.png" - yield "_resources/pythonwx44$targetsize-44_altform-unplated.png", icons / "pythonwx44.png" - yield "_resources/pythonwx150.png", icons / "pythonwx150.png" - yield "_resources/pythonwx150$targetsize-150_altform-unplated.png", icons / "pythonwx150.png" - sccd = ns.source / SCCD_FILENAME - if sccd.is_file(): - # This should only be set for side-loading purposes. - sccd = _fixup_sccd(ns, sccd, os.getenv("APPX_DATA_SHA256")) - yield sccd.name, sccd - - -def find_or_add(xml, element, attr=None, always_add=False): - if always_add: - e = None - else: - q = element - if attr: - q += "[@{}='{}']".format(*attr) - e = xml.find(q, APPXMANIFEST_NS) - if e is None: - prefix, _, name = element.partition(":") - name = ET.QName(APPXMANIFEST_NS[prefix or ""], name) - e = ET.SubElement(xml, name) - if attr: - e.set(*attr) - return e - - -def _get_app(xml, appid): - if appid: - app = xml.find( - "m:Applications/m:Application[@Id='{}']".format(appid), APPXMANIFEST_NS - ) - if app is None: - raise LookupError(appid) - else: - app = xml - return app - - -def add_visual(xml, appid, data): - app = _get_app(xml, appid) - e = find_or_add(app, "uap:VisualElements") - for i in data.items(): - e.set(*i) - return e - - -def add_alias(xml, appid, alias, subsystem="windows"): - app = _get_app(xml, appid) - e = find_or_add(app, "m:Extensions") - e = find_or_add(e, "uap5:Extension", ("Category", "windows.appExecutionAlias")) - e = find_or_add(e, "uap5:AppExecutionAlias") - e.set(ET.QName(APPXMANIFEST_NS["desktop4"], "Subsystem"), subsystem) - e = find_or_add(e, "uap5:ExecutionAlias", ("Alias", alias)) - - -def add_file_type(xml, appid, name, suffix, parameters='"%1"'): - app = _get_app(xml, appid) - e = find_or_add(app, "m:Extensions") - e = find_or_add(e, "uap3:Extension", ("Category", "windows.fileTypeAssociation")) - e = find_or_add(e, "uap3:FileTypeAssociation", ("Name", name)) - e.set("Parameters", parameters) - e = find_or_add(e, "uap:SupportedFileTypes") - if isinstance(suffix, str): - suffix = [suffix] - for s in suffix: - ET.SubElement(e, ET.QName(APPXMANIFEST_NS["uap"], "FileType")).text = s - - -def add_application( - ns, xml, appid, executable, aliases, visual_element, subsystem, file_types -): - node = xml.find("m:Applications", APPXMANIFEST_NS) - suffix = "_d.exe" if ns.debug else ".exe" - app = ET.SubElement( - node, - ET.QName(APPXMANIFEST_NS[""], "Application"), - { - "Id": appid, - "Executable": executable + suffix, - "EntryPoint": "Windows.FullTrustApplication", - ET.QName(APPXMANIFEST_NS["desktop4"], "SupportsMultipleInstances"): "true", - }, - ) - if visual_element: - add_visual(app, None, visual_element) - for alias in aliases: - add_alias(app, None, alias + suffix, subsystem) - if file_types: - add_file_type(app, None, *file_types) - return app - - -def _get_registry_entries(ns, root="", d=None): - r = root if root else PureWindowsPath("") - if d is None: - d = REGISTRY - for key, value in d.items(): - if key == "_condition": - continue - elif isinstance(value, dict): - cond = value.get("_condition") - if cond and not cond(ns): - continue - fullkey = r - for part in PureWindowsPath(key).parts: - fullkey /= part - if len(fullkey.parts) > 1: - yield str(fullkey), None, None - yield from _get_registry_entries(ns, fullkey, value) - elif len(r.parts) > 1: - yield str(r), key, value - - -def add_registry_entries(ns, xml): - e = find_or_add(xml, "m:Extensions") - e = find_or_add(e, "rescap4:Extension") - e.set("Category", "windows.classicAppCompatKeys") - e.set("EntryPoint", "Windows.FullTrustApplication") - e = ET.SubElement(e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKeys")) - for name, valuename, value in _get_registry_entries(ns): - k = ET.SubElement( - e, ET.QName(APPXMANIFEST_NS["rescap4"], "ClassicAppCompatKey") - ) - k.set("Name", name) - if value: - k.set("ValueName", valuename) - k.set("Value", value) - k.set("ValueType", "REG_SZ") - - -def disable_registry_virtualization(xml): - e = find_or_add(xml, "m:Properties") - e = find_or_add(e, "desktop6:RegistryWriteVirtualization") - e.text = "disabled" - e = find_or_add(xml, "m:Capabilities") - e = find_or_add(e, "rescap:Capability", ("Name", "unvirtualizedResources")) - - - at public -def get_appxmanifest(ns): - for k, v in APPXMANIFEST_NS.items(): - ET.register_namespace(k, v) - ET.register_namespace("", APPXMANIFEST_NS["m"]) - - xml = ET.parse(io.StringIO(APPXMANIFEST_TEMPLATE)) - NS = APPXMANIFEST_NS - QN = ET.QName - - node = xml.find("m:Identity", NS) - for k in node.keys(): - value = APPX_DATA.get(k) - if value: - node.set(k, value) - - for node in xml.find("m:Properties", NS): - value = APPX_DATA.get(node.tag.rpartition("}")[2]) - if value: - node.text = value - - winver = sys.getwindowsversion()[:3] - if winver < (10, 0, 17763): - winver = 10, 0, 17763 - find_or_add(xml, "m:Dependencies/m:TargetDeviceFamily").set( - "MaxVersionTested", "{}.{}.{}.0".format(*winver) - ) - - if winver > (10, 0, 17763): - disable_registry_virtualization(xml) - - app = add_application( - ns, - xml, - "Python", - "python", - ["python", "python{}".format(VER_MAJOR), "python{}".format(VER_DOT)], - PYTHON_VE_DATA, - "console", - ("python.file", [".py"]), - ) - - add_application( - ns, - xml, - "PythonW", - "pythonw", - ["pythonw", "pythonw{}".format(VER_MAJOR), "pythonw{}".format(VER_DOT)], - PYTHONW_VE_DATA, - "windows", - ("python.windowedfile", [".pyw"]), - ) - - if ns.include_pip and ns.include_launchers: - add_application( - ns, - xml, - "Pip", - "pip", - ["pip", "pip{}".format(VER_MAJOR), "pip{}".format(VER_DOT)], - PIP_VE_DATA, - "console", - ("python.wheel", [".whl"], 'install "%1"'), - ) - - if ns.include_idle and ns.include_launchers: - add_application( - ns, - xml, - "Idle", - "idle", - ["idle", "idle{}".format(VER_MAJOR), "idle{}".format(VER_DOT)], - IDLE_VE_DATA, - "windows", - None, - ) - - if (ns.source / SCCD_FILENAME).is_file(): - add_registry_entries(ns, xml) - node = xml.find("m:Capabilities", NS) - node = ET.SubElement(node, QN(NS["uap4"], "CustomCapability")) - node.set("Name", "Microsoft.classicAppCompat_8wekyb3d8bbwe") - - buffer = io.BytesIO() - xml.write(buffer, encoding="utf-8", xml_declaration=True) - return buffer.getbuffer() - - - at public -def get_resources_xml(ns): - return RESOURCES_XML_TEMPLATE.encode("utf-8") diff --git a/PC/layout/support/catalog.py b/PC/layout/support/catalog.py deleted file mode 100644 index 43121187ed18..000000000000 --- a/PC/layout/support/catalog.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -File generation for catalog signing non-binary contents. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - - -import sys - -__all__ = ["PYTHON_CAT_NAME", "PYTHON_CDF_NAME"] - - -def public(f): - __all__.append(f.__name__) - return f - - -PYTHON_CAT_NAME = "python.cat" -PYTHON_CDF_NAME = "python.cdf" - - -CATALOG_TEMPLATE = r"""[CatalogHeader] -Name={target.stem}.cat -ResultDir={target.parent} -PublicVersion=1 -CatalogVersion=2 -HashAlgorithms=SHA256 -PageHashes=false -EncodingType= - -[CatalogFiles] -""" - - -def can_sign(file): - return file.is_file() and file.stat().st_size - - - at public -def write_catalog(target, files): - with target.open("w", encoding="utf-8") as cat: - cat.write(CATALOG_TEMPLATE.format(target=target)) - cat.writelines("{}={}\n".format(n, f) for n, f in files if can_sign(f)) diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py deleted file mode 100644 index 88ea410b340e..000000000000 --- a/PC/layout/support/constants.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Constants for generating the layout. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - -import struct -import sys - -VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = struct.pack(">i", sys.hexversion) -VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4 -VER_NAME = {"alpha": "a", "beta": "b", "rc": "rc"}.get( - sys.version_info.releaselevel, "" -) -VER_SERIAL = sys.version_info.serial if VER_NAME else "" -VER_DOT = "{}.{}".format(VER_MAJOR, VER_MINOR) - -PYTHON_DLL_NAME = "python{}{}.dll".format(VER_MAJOR, VER_MINOR) -PYTHON_STABLE_DLL_NAME = "python{}.dll".format(VER_MAJOR) -PYTHON_ZIP_NAME = "python{}{}.zip".format(VER_MAJOR, VER_MINOR) -PYTHON_PTH_NAME = "python{}{}._pth".format(VER_MAJOR, VER_MINOR) - -PYTHON_CHM_NAME = "python{}{}{}{}{}.chm".format( - VER_MAJOR, VER_MINOR, VER_MICRO, VER_NAME, VER_SERIAL -) - -IS_X64 = sys.maxsize > 2 ** 32 diff --git a/PC/layout/support/distutils.command.bdist_wininst.py b/PC/layout/support/distutils.command.bdist_wininst.py deleted file mode 100644 index 6e9b49fe42df..000000000000 --- a/PC/layout/support/distutils.command.bdist_wininst.py +++ /dev/null @@ -1,25 +0,0 @@ -"""distutils.command.bdist_wininst - -Suppress the 'bdist_wininst' command, while still allowing -setuptools to import it without breaking.""" - -from distutils.core import Command -from distutils.errors import DistutilsPlatformError - - -class bdist_wininst(Command): - description = "create an executable installer for MS Windows" - - # Marker for tests that we have the unsupported bdist_wininst - _unsupported = True - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - raise DistutilsPlatformError( - "bdist_wininst is not supported in this Python distribution" - ) diff --git a/PC/layout/support/filesets.py b/PC/layout/support/filesets.py deleted file mode 100644 index 47f727c05784..000000000000 --- a/PC/layout/support/filesets.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -File sets and globbing helper for make_layout. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - -import os - - -class FileStemSet: - def __init__(self, *patterns): - self._names = set() - self._prefixes = [] - self._suffixes = [] - for p in map(os.path.normcase, patterns): - if p.endswith("*"): - self._prefixes.append(p[:-1]) - elif p.startswith("*"): - self._suffixes.append(p[1:]) - else: - self._names.add(p) - - def _make_name(self, f): - return os.path.normcase(f.stem) - - def __contains__(self, f): - bn = self._make_name(f) - return ( - bn in self._names - or any(map(bn.startswith, self._prefixes)) - or any(map(bn.endswith, self._suffixes)) - ) - - -class FileNameSet(FileStemSet): - def _make_name(self, f): - return os.path.normcase(f.name) - - -class FileSuffixSet: - def __init__(self, *patterns): - self._names = set() - self._prefixes = [] - self._suffixes = [] - for p in map(os.path.normcase, patterns): - if p.startswith("*."): - self._names.add(p[1:]) - elif p.startswith("*"): - self._suffixes.append(p[1:]) - elif p.endswith("*"): - self._prefixes.append(p[:-1]) - elif p.startswith("."): - self._names.add(p) - else: - self._names.add("." + p) - - def _make_name(self, f): - return os.path.normcase(f.suffix) - - def __contains__(self, f): - bn = self._make_name(f) - return ( - bn in self._names - or any(map(bn.startswith, self._prefixes)) - or any(map(bn.endswith, self._suffixes)) - ) - - -def _rglob(root, pattern, condition): - dirs = [root] - recurse = pattern[:3] in {"**/", "**\\"} - if recurse: - pattern = pattern[3:] - - while dirs: - d = dirs.pop(0) - if recurse: - dirs.extend( - filter( - condition, (type(root)(f2) for f2 in os.scandir(d) if f2.is_dir()) - ) - ) - yield from ( - (f.relative_to(root), f) - for f in d.glob(pattern) - if f.is_file() and condition(f) - ) - - -def _return_true(f): - return True - - -def rglob(root, patterns, condition=None): - if isinstance(patterns, tuple): - for p in patterns: - yield from _rglob(root, p, condition or _return_true) - else: - yield from _rglob(root, patterns, condition or _return_true) diff --git a/PC/layout/support/logging.py b/PC/layout/support/logging.py deleted file mode 100644 index 30869b949a1c..000000000000 --- a/PC/layout/support/logging.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Logging support for make_layout. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - -import logging -import sys - -__all__ = [] - -LOG = None -HAS_ERROR = False - - -def public(f): - __all__.append(f.__name__) - return f - - - at public -def configure_logger(ns): - global LOG - if LOG: - return - - LOG = logging.getLogger("make_layout") - LOG.level = logging.DEBUG - - if ns.v: - s_level = max(logging.ERROR - ns.v * 10, logging.DEBUG) - f_level = max(logging.WARNING - ns.v * 10, logging.DEBUG) - else: - s_level = logging.ERROR - f_level = logging.INFO - - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(logging.Formatter("{levelname:8s} {message}", style="{")) - handler.setLevel(s_level) - LOG.addHandler(handler) - - if ns.log: - handler = logging.FileHandler(ns.log, encoding="utf-8", delay=True) - handler.setFormatter( - logging.Formatter("[{asctime}]{levelname:8s}: {message}", style="{") - ) - handler.setLevel(f_level) - LOG.addHandler(handler) - - -class BraceMessage: - def __init__(self, fmt, *args, **kwargs): - self.fmt = fmt - self.args = args - self.kwargs = kwargs - - def __str__(self): - return self.fmt.format(*self.args, **self.kwargs) - - - at public -def log_debug(msg, *args, **kwargs): - return LOG.debug(BraceMessage(msg, *args, **kwargs)) - - - at public -def log_info(msg, *args, **kwargs): - return LOG.info(BraceMessage(msg, *args, **kwargs)) - - - at public -def log_warning(msg, *args, **kwargs): - return LOG.warning(BraceMessage(msg, *args, **kwargs)) - - - at public -def log_error(msg, *args, **kwargs): - global HAS_ERROR - HAS_ERROR = True - return LOG.error(BraceMessage(msg, *args, **kwargs)) - - - at public -def log_exception(msg, *args, **kwargs): - global HAS_ERROR - HAS_ERROR = True - return LOG.exception(BraceMessage(msg, *args, **kwargs)) - - - at public -def error_was_logged(): - return HAS_ERROR diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py deleted file mode 100644 index 76d9e34e1f46..000000000000 --- a/PC/layout/support/options.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -List of optional components. -""" - -__author__ = "Steve Dower " -__version__ = "3.8" - - -__all__ = [] - - -def public(f): - __all__.append(f.__name__) - return f - - -OPTIONS = { - "stable": {"help": "stable ABI stub"}, - "pip": {"help": "pip"}, - "distutils": {"help": "distutils"}, - "tcltk": {"help": "Tcl, Tk and tkinter"}, - "idle": {"help": "Idle"}, - "tests": {"help": "test suite"}, - "tools": {"help": "tools"}, - "venv": {"help": "venv"}, - "dev": {"help": "headers and libs"}, - "symbols": {"help": "symbols"}, - "bdist-wininst": {"help": "bdist_wininst support"}, - "underpth": {"help": "a python._pth file", "not-in-all": True}, - "launchers": {"help": "specific launchers"}, - "appxmanifest": {"help": "an appxmanifest"}, - "props": {"help": "a python.props file"}, - "chm": {"help": "the CHM documentation"}, - "html-doc": {"help": "the HTML documentation"}, -} - - -PRESETS = { - "appx": { - "help": "APPX package", - "options": [ - "stable", - "pip", - "distutils", - "tcltk", - "idle", - "venv", - "dev", - "launchers", - "appxmanifest", - # XXX: Disabled for now "precompile", - ], - }, - "nuget": { - "help": "nuget package", - "options": ["stable", "pip", "distutils", "dev", "props"], - }, - "default": { - "help": "development kit package", - "options": [ - "stable", - "pip", - "distutils", - "tcltk", - "idle", - "tests", - "tools", - "venv", - "dev", - "symbols", - "bdist-wininst", - "chm", - ], - }, - "embed": { - "help": "embeddable package", - "options": ["stable", "zip-lib", "flat-dlls", "underpth", "precompile"], - }, -} - - - at public -def get_argparse_options(): - for opt, info in OPTIONS.items(): - help = "When specified, includes {}".format(info["help"]) - if info.get("not-in-all"): - help = "{}. Not affected by --include-all".format(help) - - yield "--include-{}".format(opt), help - - for opt, info in PRESETS.items(): - help = "When specified, includes default options for {}".format(info["help"]) - yield "--preset-{}".format(opt), help - - -def ns_get(ns, key, default=False): - return getattr(ns, key.replace("-", "_"), default) - - -def ns_set(ns, key, value=True): - k1 = key.replace("-", "_") - k2 = "include_{}".format(k1) - if hasattr(ns, k2): - setattr(ns, k2, value) - elif hasattr(ns, k1): - setattr(ns, k1, value) - else: - raise AttributeError("no argument named '{}'".format(k1)) - - - at public -def update_presets