From c6ad87fe2a84c38ab3353404d7dc3713bc9d6542 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Sun, 21 Nov 2021 12:35:21 +0000 Subject: [PATCH 1/8] Ensure run_setup handles `if __name__ == '__main__'` Adds a unit test for that scenario --- distutils/tests/test_core.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/distutils/tests/test_core.py b/distutils/tests/test_core.py index 666ff4a3..3f6c98c2 100644 --- a/distutils/tests/test_core.py +++ b/distutils/tests/test_core.py @@ -10,6 +10,7 @@ import unittest from distutils.tests import support from distutils import log +from distutils.dist import Distribution # setup script that uses __file__ setup_using___file__ = """\ @@ -45,6 +46,16 @@ class install(_install): setup(cmdclass={'install': install}) """ +setup_within_if_main = """\ +from distutils.core import setup + +def main(): + return setup(name="setup_within_if_main") + +if __name__ == "__main__": + main() +""" + class CoreTestCase(support.EnvironGuard, unittest.TestCase): def setUp(self): @@ -115,6 +126,12 @@ def test_run_setup_uses_current_dir(self): output = output[:-1] self.assertEqual(cwd, output) + def test_run_setup_within_if_main(self): + dist = distutils.core.run_setup( + self.write_setup(setup_within_if_main), stop_after="config") + self.assertIsInstance(dist, Distribution) + self.assertEqual(dist.get_name(), "setup_within_if_main") + def test_debug_mode(self): # this covers the code called when DEBUG is set sys.argv = ['setup.py', '--name'] From 07dcc795707952c0f78c6d3631cc9e33173670fb Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Sun, 21 Nov 2021 12:37:08 +0000 Subject: [PATCH 2/8] Adopt setuptools.build_meta approach for run_setup `setuptools.build_meta` uses `tokenize.open` instead of open (and replaces `\r\n` with `\n`) when exec-ing `setup.py` files. It seems that the advantage of using `tokenize.open` is that it supports automatic encoding detection. --- distutils/core.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/distutils/core.py b/distutils/core.py index d603d4a4..2634e604 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -8,6 +8,7 @@ import os import sys +import tokenize from distutils.debug import DEBUG from distutils.errors import * @@ -205,14 +206,17 @@ def run_setup (script_name, script_args=None, stop_after="run"): _setup_stop_after = stop_after save_argv = sys.argv.copy() - g = {'__file__': script_name} + g = {'__file__': script_name, '__name__': '__main__'} try: try: sys.argv[0] = script_name if script_args is not None: sys.argv[1:] = script_args - with open(script_name, 'rb') as f: - exec(f.read(), g) + _open = getattr(tokenize, 'open', open) + # ^-- tokenize.open supports automatic encoding detection + with _open(script_name) as f: + code = f.read().replace(r'\r\n', r'\n') + exec(code, g) finally: sys.argv = save_argv _setup_stop_after = None From f23f749943a5988eaf20e489e9501dedbf0549c4 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Sun, 21 Nov 2021 12:45:32 +0000 Subject: [PATCH 3/8] Separate run_commands from distutils.core.setup This allows reusing the function for already made distribution objects. --- distutils/core.py | 50 ++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/distutils/core.py b/distutils/core.py index 2634e604..90efce1c 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -143,31 +143,41 @@ class found in 'cmdclass' is used in place of the default, which is if _setup_stop_after == "commandline": return dist - # And finally, run all the commands found on the command line. if ok: - try: - dist.run_commands() - except KeyboardInterrupt: - raise SystemExit("interrupted") - except OSError as exc: - if DEBUG: - sys.stderr.write("error: %s\n" % (exc,)) - raise - else: - raise SystemExit("error: %s" % (exc,)) - - except (DistutilsError, - CCompilerError) as msg: - if DEBUG: - raise - else: - raise SystemExit("error: " + str(msg)) - - return dist + return run_commands(dist) # setup () +def run_commands (dist): + """Given a Distribution object run all the commands, + raising ``SystemExit`` errors in the case of failure. + + This function assumes that either ``sys.argv`` or ``dist.script_args`` + is already set accordingly. + """ + # And finally, run all the commands found on the command line. + try: + dist.run_commands() + except KeyboardInterrupt: + raise SystemExit("interrupted") + except OSError as exc: + if DEBUG: + sys.stderr.write("error: %s\n" % (exc,)) + raise + else: + raise SystemExit("error: %s" % (exc,)) + + except (DistutilsError, + CCompilerError) as msg: + if DEBUG: + raise + else: + raise SystemExit("error: " + str(msg)) + + return dist + + def run_setup (script_name, script_args=None, stop_after="run"): """Run a setup script in a somewhat controlled environment, and return the Distribution instance that drives things. This is useful From 3efb96cdbfccfd2932101a303a959dac9834eb00 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Sun, 21 Nov 2021 13:27:48 +0000 Subject: [PATCH 4/8] Add test for run_commands --- distutils/dist.py | 4 ++++ distutils/tests/test_core.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/distutils/dist.py b/distutils/dist.py index 37db4d6c..1b558efa 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -168,6 +168,10 @@ def __init__(self, attrs=None): # for the setup script to override command classes self.cmdclass = {} + # Make sure 'commands' is defined, so dist.run_commands can run + # It might be overwritten by parse_command_line() + self.commands = [] + # 'command_packages' is a list of packages in which commands # are searched for. The factory for command 'foo' is expected # to be named 'foo' in the module 'foo' in one of the packages diff --git a/distutils/tests/test_core.py b/distutils/tests/test_core.py index 3f6c98c2..d99cfd26 100644 --- a/distutils/tests/test_core.py +++ b/distutils/tests/test_core.py @@ -132,6 +132,14 @@ def test_run_setup_within_if_main(self): self.assertIsInstance(dist, Distribution) self.assertEqual(dist.get_name(), "setup_within_if_main") + def test_run_commands(self): + sys.argv = ['setup.py', 'build'] + dist = distutils.core.run_setup( + self.write_setup(setup_within_if_main), stop_after="commandline") + self.assertNotIn('build', dist.have_run) + distutils.core.run_commands(dist) + self.assertIn('build', dist.have_run) + def test_debug_mode(self): # this covers the code called when DEBUG is set sys.argv = ['setup.py', '--name'] From a3dab5f330c7a0473ceb73d5a889087318003407 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 22 Nov 2021 14:28:15 +0000 Subject: [PATCH 5/8] Ensure distribution object is returned --- distutils/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/distutils/core.py b/distutils/core.py index 90efce1c..6be58b26 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -146,6 +146,8 @@ class found in 'cmdclass' is used in place of the default, which is if ok: return run_commands(dist) + return dist + # setup () From 46dbfa8711871421d12b4eacc32d9ee5425de218 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 22 Nov 2021 14:29:36 +0000 Subject: [PATCH 6/8] Use tokenize.open directly (available on Python>= 3.2) --- distutils/core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/distutils/core.py b/distutils/core.py index 6be58b26..15ddcf80 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -224,9 +224,8 @@ def run_setup (script_name, script_args=None, stop_after="run"): sys.argv[0] = script_name if script_args is not None: sys.argv[1:] = script_args - _open = getattr(tokenize, 'open', open) - # ^-- tokenize.open supports automatic encoding detection - with _open(script_name) as f: + # tokenize.open supports automatic encoding detection + with tokenize.open(script_name) as f: code = f.read().replace(r'\r\n', r'\n') exec(code, g) finally: From 2dfd6734725f50be8c576ef7b05ec78a4c24028d Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Mon, 22 Nov 2021 14:37:36 +0000 Subject: [PATCH 7/8] Preserve orignal comment position --- distutils/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/core.py b/distutils/core.py index 15ddcf80..f43888ea 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -143,6 +143,7 @@ class found in 'cmdclass' is used in place of the default, which is if _setup_stop_after == "commandline": return dist + # And finally, run all the commands found on the command line. if ok: return run_commands(dist) @@ -158,7 +159,6 @@ def run_commands (dist): This function assumes that either ``sys.argv`` or ``dist.script_args`` is already set accordingly. """ - # And finally, run all the commands found on the command line. try: dist.run_commands() except KeyboardInterrupt: From 405c150c1d5393af2232526f77f39985d37b11fc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 22 Nov 2021 10:03:07 -0500 Subject: [PATCH 8/8] Remove early initialization of 'commands'; unneeded. --- distutils/dist.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/distutils/dist.py b/distutils/dist.py index 1b558efa..37db4d6c 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -168,10 +168,6 @@ def __init__(self, attrs=None): # for the setup script to override command classes self.cmdclass = {} - # Make sure 'commands' is defined, so dist.run_commands can run - # It might be overwritten by parse_command_line() - self.commands = [] - # 'command_packages' is a list of packages in which commands # are searched for. The factory for command 'foo' is expected # to be named 'foo' in the module 'foo' in one of the packages