Repository: pypa/setuptools

Issue:
upload.py failure in python 2.4
Originally reported by: **behackett (Bitbucket: [behackett](http://bitbucket.org/behackett), GitHub: [behackett](http://github.com/behackett))**

---

This is on Windows if that matters at all.

```
#!python

removing 'build\bdist.win32\wininst' (and everything under it)
running upload
Traceback (most recent call last):
  File "setup.py", line 312, in ?
    cmdclass={"build_ext": custom_build_ext,
  File "C:\Python24\lib\distutils\core.py", line 149, in setup
    dist.run_commands()
  File "C:\Python24\lib\distutils\dist.py", line 946, in run_commands
    self.run_command(cmd)
  File "C:\Python24\lib\distutils\dist.py", line 966, in run_command
    cmd_obj.run()
  File "c:\python24\lib\site-packages\setuptools-1.1-py2.4.egg\setuptools\command\upload.
    self.upload_file(command, pyversion, filename)
  File "c:\python24\lib\site-packages\setuptools-1.1-py2.4.egg\setuptools\command\upload.
    body = StringIO.StringIO()
AttributeError: class StringIO has no attribute 'StringIO'
```

---
- Bitbucket: https://bitbucket.org/pypa/setuptools/issue/76



Discussion:
_Original comment by_ **jaraco (Bitbucket: [jaraco](http://bitbucket.org/jaraco), GitHub: [jaraco](http://github.com/jaraco))**:

---

Correct AttributeError in upload command on Python 2.4. Fixes #76


Resolving patch:
diff --git a/setuptools/_distutils/command/install.py b/setuptools/_distutils/command/install.py
--- a/setuptools/_distutils/command/install.py
+++ b/setuptools/_distutils/command/install.py
@@ -81,6 +81,11 @@
         'data'   : '{userbase}',
         }
 
+    INSTALL_SCHEMES['osx_framework_user'] = {
+        'headers':
+            '{userbase}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}',
+    }
+
 # The keys to an installation scheme; if any new types of files are to be
 # installed, be sure to add an entry to every installation scheme above,
 # and to SCHEME_KEYS here.
@@ -445,12 +450,17 @@ def dump_dirs(self, msg):
     def finalize_unix(self):
         """Finalizes options for posix platforms."""
         if self.install_base is not None or self.install_platbase is not None:
-            if ((self.install_lib is None and
-                 self.install_purelib is None and
-                 self.install_platlib is None) or
+            incomplete_scheme = (
+                (
+                    self.install_lib is None and
+                    self.install_purelib is None and
+                    self.install_platlib is None
+                ) or
                 self.install_headers is None or
                 self.install_scripts is None or
-                self.install_data is None):
+                self.install_data is None
+            )
+            if incomplete_scheme:
                 raise DistutilsOptionError(
                       "install-base or install-platbase supplied, but "
                       "installation scheme is incomplete")
@@ -510,21 +520,30 @@ def finalize_other(self):
                       "I don't know how to install stuff on '%s'" % os.name)
 
     def select_scheme(self, name):
+        os_name, sep, key = name.partition('_')
+        try:
+            resolved = sysconfig.get_preferred_scheme(key)
+        except Exception:
+            resolved = self._pypy_hack(name)
+        return self._select_scheme(resolved)
+
+    def _select_scheme(self, name):
         """Sets the install directories by applying the install schemes."""
         # it's the caller's problem if they supply a bad name!
-        if (hasattr(sys, 'pypy_version_info') and
-                sys.version_info < (3, 8) and
-                not name.endswith(('_user', '_home'))):
-            if os.name == 'nt':
-                name = 'pypy_nt'
-            else:
-                name = 'pypy'
         scheme = _load_schemes()[name]
         for key in SCHEME_KEYS:
             attrname = 'install_' + key
             if getattr(self, attrname) is None:
                 setattr(self, attrname, scheme[key])
 
+    @staticmethod
+    def _pypy_hack(name):
+        PY37 = sys.version_info < (3, 8)
+        old_pypy = hasattr(sys, 'pypy_version_info') and PY37
+        prefix = not name.endswith(('_user', '_home'))
+        pypy_name = 'pypy' + '_nt' * (os.name == 'nt')
+        return pypy_name if old_pypy and prefix else name
+
     def _expand_attrs(self, attrs):
         for attr in attrs:
             val = getattr(self, attr)
diff --git a/setuptools/_distutils/cygwinccompiler.py b/setuptools/_distutils/cygwinccompiler.py
--- a/setuptools/_distutils/cygwinccompiler.py
+++ b/setuptools/_distutils/cygwinccompiler.py
@@ -50,6 +50,7 @@
 import os
 import sys
 import copy
+import shlex
 from subprocess import Popen, PIPE, check_output
 import re
 
@@ -421,5 +422,5 @@ def get_versions():
 
 def is_cygwincc(cc):
     '''Try to determine if the compiler that would be used is from cygwin.'''
-    out_string = check_output([cc, '-dumpmachine'])
+    out_string = check_output(shlex.split(cc) + ['-dumpmachine'])
     return out_string.strip().endswith(b'cygwin')
diff --git a/setuptools/_distutils/msvc9compiler.py b/setuptools/_distutils/msvc9compiler.py
--- a/setuptools/_distutils/msvc9compiler.py
+++ b/setuptools/_distutils/msvc9compiler.py
@@ -291,8 +291,6 @@ def query_vcvarsall(version, arch="x86"):
 
 # More globals
 VERSION = get_build_version()
-if VERSION < 8.0:
-    raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION)
 # MACROS = MacroExpander(VERSION)
 
 class MSVCCompiler(CCompiler) :
@@ -339,6 +337,8 @@ def __init__(self, verbose=0, dry_run=0, force=0):
     def initialize(self, plat_name=None):
         # multi-init means we would need to check platform same each time...
         assert not self.initialized, "don't init multiple times"
+        if self.__version < 8.0:
+            raise DistutilsPlatformError("VC %0.1f is not supported by this module" % self.__version)
         if plat_name is None:
             plat_name = get_platform()
         # sanity check for platforms to prevent obscure errors later.


Test patch:
diff --git a/setuptools/_distutils/tests/test_archive_util.py b/setuptools/_distutils/tests/test_archive_util.py
--- a/setuptools/_distutils/tests/test_archive_util.py
+++ b/setuptools/_distutils/tests/test_archive_util.py
@@ -14,16 +14,11 @@
 from distutils.spawn import find_executable, spawn
 from distutils.tests import support
 from test.support import run_unittest, patch
+from .unix_compat import require_unix_id, require_uid_0, grp, pwd, UID_0_SUPPORT
 
 from .py38compat import change_cwd
 from .py38compat import check_warnings
 
-try:
-    import grp
-    import pwd
-    UID_GID_SUPPORT = True
-except ImportError:
-    UID_GID_SUPPORT = False
 
 try:
     import zipfile
@@ -339,7 +334,7 @@ def test_make_archive_xztar(self):
     def test_make_archive_owner_group(self):
         # testing make_archive with owner and group, with various combinations
         # this works even if there's not gid/uid support
-        if UID_GID_SUPPORT:
+        if UID_0_SUPPORT:
             group = grp.getgrgid(0)[0]
             owner = pwd.getpwuid(0)[0]
         else:
@@ -364,7 +359,8 @@ def test_make_archive_owner_group(self):
         self.assertTrue(os.path.exists(res))
 
     @unittest.skipUnless(ZLIB_SUPPORT, "Requires zlib")
-    @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+    @require_unix_id
+    @require_uid_0
     def test_tarfile_root_owner(self):
         tmpdir =  self._create_files()
         base_name = os.path.join(self.mkdtemp(), 'archive')
diff --git a/setuptools/_distutils/tests/test_sdist.py b/setuptools/_distutils/tests/test_sdist.py
--- a/setuptools/_distutils/tests/test_sdist.py
+++ b/setuptools/_distutils/tests/test_sdist.py
@@ -7,6 +7,7 @@
 from os.path import join
 from textwrap import dedent
 from test.support import captured_stdout, run_unittest
+from .unix_compat import require_unix_id, require_uid_0, pwd, grp
 
 from .py38compat import check_warnings
 
@@ -16,13 +17,6 @@
 except ImportError:
     ZLIB_SUPPORT = False
 
-try:
-    import grp
-    import pwd
-    UID_GID_SUPPORT = True
-except ImportError:
-    UID_GID_SUPPORT = False
-
 from distutils.command.sdist import sdist, show_formats
 from distutils.core import Distribution
 from distutils.tests.test_config import BasePyPIRCCommandTestCase
@@ -440,7 +434,8 @@ def test_manual_manifest(self):
                                              'fake-1.0/README.manual'])
 
     @unittest.skipUnless(ZLIB_SUPPORT, "requires zlib")
-    @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+    @require_unix_id
+    @require_uid_0
     @unittest.skipIf(find_executable('tar') is None,
                      "The tar command is not found")
     @unittest.skipIf(find_executable('gzip') is None,
diff --git a/setuptools/_distutils/tests/unix_compat.py b/setuptools/_distutils/tests/unix_compat.py
new file mode 100644
--- /dev/null
+++ b/setuptools/_distutils/tests/unix_compat.py
@@ -0,0 +1,16 @@
+import sys
+import unittest
+
+try:
+    import grp
+    import pwd
+except ImportError:
+    grp = pwd = None
+
+
+UNIX_ID_SUPPORT = grp and pwd
+UID_0_SUPPORT = UNIX_ID_SUPPORT and sys.platform != "cygwin"
+
+require_unix_id = unittest.skipUnless(
+    UNIX_ID_SUPPORT, "Requires grp and pwd support")
+require_uid_0 = unittest.skipUnless(UID_0_SUPPORT, "Requires UID 0 support")