[Distutils] distutils & Cygwin compiler on Win32

Rene Liebscher R.Liebscher@gmx.de
Tue, 06 Jun 2000 13:45:35 +0200


This is a multi-part message in MIME format.
--------------D883EB9E16BD4C83A8F2C8C5
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Greg Ward wrote: 
> > Second, the patch which provides the help-compiler
> > option (and help-format, help-formats whatever
> > you need.) This is against the cvs from last monday.
> 
> Oh good.
I used the current cvs to create an up-to-date patch.
It only introduces this help-option option, nothing else.
( Did you have a problem with bdist, I found the current cvs
wasn't working, in the run method you wrote self.format 
which I had to change to format. )
file: help-option.patch

> > Finally, while I was working on this help patch, I
> > found that everyone who creates a new compiler has
> > to change ccompiler.py, which contains the base class
> > for all other compilers. Wouldn't it be better, if
> > we had a separate file for the compiler mapping, so
> > no-one ever has to touch our base file?
> 
> I'm not convinced.  If we actually had to change the CCompiler class,
> you'd have a point -- that would be a sign of inadequate design.  But we
> just have to change the tables that drive the factory function, and
> those tables and the factory really belong in the same module as the
> base class.  What's the big deal with tweaking that file occasionally?

You could introduce some other things like building the 
mapping table at runtime, so you only can choose compilers
which are really available on your machine. I think such things 
don't belong in ccompiler.py, it would be to platform-specific.
Look at this piece of code to understand what I mean.
-----------------------------------
+# Map a platform ('posix', 'nt') to the default compiler type for
+# that platform.
+default_compiler = { 'posix': 'unix',
+                     'nt': 'msvc',
+                   }
+
+# Map compiler types to (module_name, class_name) pairs -- ie. where to
+# find the code that implements an interface to this compiler.  (The
module
+# is assumed to be in the 'distutils' package.)
+compiler_class = { 
+                  # standard for all platforms 
+                  'unix': ('unixccompiler', 'UnixCCompiler',"standard
UNIX-style compiler"),
+                 }
+                
+# on NT we have some compilers more
+if os.name=='nt':
+    compiler_class.update({
+                  'msvc': ('msvccompiler', 'MSVCCompiler',"Microsoft
Visual C++"),
+                   'cygwin':  ('cygwinccompiler',
'CygwinCCompiler',"Cygwin-Gnu-Win32-C-Compiler"),
+                   'mingw32': ('cygwinccompiler',
'Mingw32CCompiler',"MinGW32-C-Compiler (or cygwin in this mode)"),
+                 })          
+                
+# One could also check if the compiler is really available              
+# for example: (doesn't really work, because msvc's find_exe return
always a name) 
+# import msvccompiler
+# if msvccompiler.find_exe(...):
+#      compiler_class.update( ... msvc ... )
+
-----------------------------------------
(Perhaps, we could divide it in ccompiler.py which contains only
the class CCompiler and a file compiler_util.py or 
compiler_factory.py? for the rest.?)


And now some words to your second email.

>  * constructing a fake Makefile fragment to set sysconfig globals is 
>    excessively baroque: why not do it like this:
(I took it directly from a older version of cygwinccompiler.py)
>      g['CC'] = "cc"                      # not gcc?
I used 'cc' because it is the standard name for a C compiler.
I overwrite UnixCCompilers self.cc to change it to 'gcc'.
I think as a default assignment it should be set to the standard
name 'cc'. (There are more free compilers for Windows out there,
what they call their compiler? cc?)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  * I'd rather do nothing about guessing the user's home directory
    on Windows -- my innocent question started a surprisingly lively
    thread on the topic in python-dev, and it seems possible that some
    sort of "userdir" module will wind up in Python 1.6.  I'd rather use
    whatever code comes out of that discussion.  However, it does sound
    reasonable to respect os.environ['HOME'], in which case Mac OS will
    be the odd man out in 'find_config_files()' instead of Unix.  That
    way, people who really care about having a personal directory on
    Windows can communicate it to Distutils unambigiously through HOME
    -- at least until someone figures out the "right way" to find a home 
    directory on Windows.
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Ok, they could set HOME in their login scripts.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    Can you redo your modification of dist.py to unify the "posix" and
    "nt" branches of 'find_config_files()', and drop the HOME-setting
    code in util.py?  Thanks.
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Ok.  file: dist_home.patch
(It contains only this patch.)



>Finally, what's your thought on simplifying the CCompiler API and
>relying more on instance attributes?  I'd rather do this now so you and
>Lyle Johnson (who's doing the Borland C++ port) have to adapt your code
>before I check it in.  >evil smirk<  Any feelings?

It sounds good, but if you subclass a compiler class, as I do,
only to (ex)change some parameters for the call of its base
class method, then you probably had to save instance variables,
call the base class method and then restore the instance
variables. (Ok, for my class this isn't a big problem,
because I could set most changes in __init__().
But it is worth to think about, which parameters
are really constant for all cases.) 
If you have a new version of UnixCCompiler, send me an email
and I will change my classes, too. (I will send you then the
complete file, not an patch.)

I also found some problems with building extensions using my classes.
I tried to build a more complex extension (pygtk-0.6.5). This works
without problems if you use msvc, but the resulting dll crashs if
I use cygwin or mingw32, it is probably a problem with initialization of
the
dll's. If someone else has more experience with cygwin/mingw32, 
he could find out if there is a problem with my link parameters
in the compiler classes. 


> I've just gone over your patch, and I think I'd like it better if you'd
> "cvs update" and regenerate it.  There's at least one bug that we both
> fixed, so there may be other conflicts -- and those are easier to deal
> with using "cvs" than using "patch".  ;-)
Most of it is either already checked in or attached to this mail.
The rest concerns only my cygwinccompiler.py file, and I will send
it as file not as patch. (later this week)


I found some problems with sdist.
If you add a new package in setup.py (I mean one more entry in the
packages list), 
then it is not included
in the resulting source distribution. I think this happens
because sdist doesn't check the date of MANIFEST against
the date of setup.py (or however this file is called, __file__
would give you the name.) I will see if I can change this.
Another problem: If I have an empty package (which contains
only an empty __init__.py file, file length=0), then it is included
in the zip-file, but you can't find it in the tar.gz-file.
Is this a problem of tar and it is worth to be treated specially?
(makes it ever sense to have such a module)



Kind regards

Rene Liebscher
--------------D883EB9E16BD4C83A8F2C8C5
Content-Type: text/plain; charset=us-ascii;
 name="dist_home.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="dist_home.patch"

diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/dist.py distutils.patched/distutils/dist.py
--- distutils.orig/distutils/dist.py	Tue Jun  6 11:17:02 2000
+++ distutils.patched/distutils/dist.py	Tue Jun  6 12:47:42 2000
@@ -263,24 +263,25 @@
         and setup.cfg in the current directory.
         """
         files = []
-        if os.name == "posix":
-            check_environ()
+        check_environ()
 
-            sys_dir = os.path.dirname(sys.modules['distutils'].__file__)
-            sys_file = os.path.join(sys_dir, "pydistutils.cfg")
-            if os.path.isfile(sys_file):
-                files.append(sys_file)
+        if os.name=='posix':
+	    sys_dir = os.path.dirname(sys.modules['distutils'].__file__)
+	    user_filename = ".pydistutils.cfg"
+	else:
+	    sys_dir = sysconfig.PREFIX
+	    user_filename = "pydistutils.cfg"
+	
+        sys_file = os.path.join(sys_dir, "pydistutils.cfg")
+        if os.path.isfile(sys_file):
+            files.append(sys_file)
 
-            user_file = os.path.join(os.environ.get('HOME'),
-                                     ".pydistutils.cfg")
+	if os.environ.has_key('HOME'):
+    	    user_file = os.path.join(os.environ.get('HOME'),
+                                     user_filename)
             if os.path.isfile(user_file):
                 files.append(user_file)
 
-        else:
-            sys_file = os.path.join (sysconfig.PREFIX, "pydistutils.cfg")
-            if os.path.isfile(sys_file):
-                files.append(sys_file)
-
         # All platforms support local setup.cfg
         local_file = "setup.cfg"
         if os.path.isfile(local_file):

--------------D883EB9E16BD4C83A8F2C8C5
Content-Type: text/plain; charset=us-ascii;
 name="help_option.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="help_option.patch"

diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/archive_util.py distutils.patched/distutils/archive_util.py
--- distutils.orig/distutils/archive_util.py	Tue Jun  6 11:17:01 2000
+++ distutils.patched/distutils/archive_util.py	Tue Jun  6 11:20:24 2000
@@ -110,11 +110,11 @@
 
 
 ARCHIVE_FORMATS = {
-    'gztar': (make_tarball, [('compress', 'gzip')]),
-    'bztar': (make_tarball, [('compress', 'bzip2')]),
-    'ztar':  (make_tarball, [('compress', 'compress')]),
-    'tar':   (make_tarball, [('compress', None)]),
-    'zip':   (make_zipfile, [])
+    'gztar': (make_tarball, [('compress', 'gzip')],"gzipped tar-file"),
+    'bztar': (make_tarball, [('compress', 'bzip2')],"bzip2-ed tar-file"),
+    'ztar':  (make_tarball, [('compress', 'compress')],"compressed tar-file"),
+    'tar':   (make_tarball, [('compress', None)],"uncompressed tar-file"),
+    'zip':   (make_zipfile, [],"zip-file")
     }
 
 def check_archive_formats (formats):
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/ccompiler.py distutils.patched/distutils/ccompiler.py
--- distutils.orig/distutils/ccompiler.py	Tue Jun  6 11:17:02 2000
+++ distutils.patched/distutils/ccompiler.py	Tue Jun  6 11:22:15 2000
@@ -726,10 +726,22 @@
 # Map compiler types to (module_name, class_name) pairs -- ie. where to
 # find the code that implements an interface to this compiler.  (The module
 # is assumed to be in the 'distutils' package.)
-compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler'),
-                   'msvc': ('msvccompiler', 'MSVCCompiler'),
+compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler',"standard UNIX-style compiler"),
+                   'msvc': ('msvccompiler', 'MSVCCompiler',"Microsoft Visual C++"),
+                   'cygwin':  ('cygwinccompiler', 'CygwinCCompiler',"Cygwin-Gnu-Win32-C-Compiler"),
+                   'mingw32': ('cygwinccompiler', 'Mingw32CCompiler',"MinGW32-C-Compiler (or cygwin in this mode)"),
                  }
 
+# prints all possible arguments to --compiler
+def show_compilers():
+    from distutils.fancy_getopt import FancyGetopt 
+    list_of_compilers=[]
+    for compiler in compiler_class.keys():
+	list_of_compilers.append(("compiler="+compiler,None,compiler_class[compiler][2]))
+    list_of_compilers.sort()
+    pretty_printer=FancyGetopt(list_of_compilers)
+    pretty_printer.print_help("List of available compilers:")
+    
 
 def new_compiler (plat=None,
                   compiler=None,
@@ -755,7 +767,7 @@
         if compiler is None:
             compiler = default_compiler[plat]
         
-        (module_name, class_name) = compiler_class[compiler]
+        (module_name, class_name,long_description) = compiler_class[compiler]
     except KeyError:
         msg = "don't know how to compile C/C++ code on platform '%s'" % plat
         if compiler is not None:
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/command/bdist.py distutils.patched/distutils/command/bdist.py
--- distutils.orig/distutils/command/bdist.py	Tue Jun  6 11:17:03 2000
+++ distutils.patched/distutils/command/bdist.py	Tue Jun  6 11:35:12 2000
@@ -21,8 +21,7 @@
     user_options = [('bdist-base=', 'b',
                      "temporary directory for creating built distributions"),
                     ('formats=', None,
-                     "formats for distribution " +
-                     "(gztar, bztar, zip, rpm, ... )"),
+                     "formats for distribution"),
                    ]
 
     # The following commands do not take a format option from bdist
@@ -33,12 +32,28 @@
     default_format = { 'posix': 'gztar',
                        'nt': 'zip', }
 
-    format_command = { 'gztar': 'bdist_dumb',
-                       'bztar': 'bdist_dumb',
-                       'ztar':  'bdist_dumb',
-                       'tar':   'bdist_dumb',
-                       'rpm':   'bdist_rpm',
-                       'zip':   'bdist_dumb', }
+    format_command = { 'gztar': ('bdist_dumb',"gzipped tar-file"),
+                       'bztar': ('bdist_dumb',"bzip2-ed tar-file"),
+                       'ztar':  ('bdist_dumb',"compressed tar-file"),
+                       'tar':   ('bdist_dumb',"tar-file"),
+                       'rpm':   ('bdist_rpm',"rpm distribution"),
+                       'zip':   ('bdist_dumb',"zip-file"),
+		        }
+
+    # prints all possible arguments to --format
+    def show_formats():
+	from distutils.fancy_getopt import FancyGetopt 
+	list_of_formats=[]
+	for format in bdist.format_command.keys():
+	    list_of_formats.append(("formats="+format,None,bdist.format_command[format][1]))
+	list_of_formats.sort()
+	pretty_printer=FancyGetopt(list_of_formats)
+	pretty_printer.print_help("List of available distribution formats:")
+
+    help_options = [
+        ('help-formats', None,
+         "lists available distribution formats",show_formats),
+	]
 
 
     def initialize_options (self):
@@ -74,14 +89,14 @@
         for format in self.formats:
 
             try:
-                cmd_name = self.format_command[self.format]
+                cmd_name = self.format_command[format][0]
             except KeyError:
                 raise DistutilsOptionError, \
-                      "invalid format '%s'" % self.format
+                      "invalid format '%s'" % format
 
             sub_cmd = self.reinitialize_command(cmd_name)
             if cmd_name not in self.no_format_option:
-                sub_cmd.format = self.format
+                sub_cmd.format = format
             self.run_command (cmd_name)
 
     # run()
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/command/build.py distutils.patched/distutils/command/build.py
--- distutils.orig/distutils/command/build.py	Mon May 29 10:26:26 2000
+++ distutils.patched/distutils/command/build.py	Tue Jun  6 11:20:24 2000
@@ -9,6 +9,7 @@
 import sys, os
 from distutils.core import Command
 from distutils.util import get_platform
+from distutils.ccompiler import show_compilers
 
 class build (Command):
 
@@ -35,6 +36,10 @@
         ('force', 'f',
          "forcibly build everything (ignore file timestamps)"),
         ]
+    help_options = [
+        ('help-compiler', None,
+         "lists available compilers",show_compilers),
+	]
 
     def initialize_options (self):
         self.build_base = 'build'
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/command/build_clib.py distutils.patched/distutils/command/build_clib.py
--- distutils.orig/distutils/command/build_clib.py	Thu May 25 03:10:04 2000
+++ distutils.patched/distutils/command/build_clib.py	Tue Jun  6 11:20:24 2000
@@ -23,7 +23,7 @@
 from types import *
 from distutils.core import Command
 from distutils.errors import *
-from distutils.ccompiler import new_compiler
+from distutils.ccompiler import new_compiler,show_compilers
 
 
 class build_clib (Command):
@@ -42,6 +42,10 @@
         ('compiler=', 'c',
          "specify the compiler type"),
         ]
+    help_options = [
+        ('help-compiler', None,
+         "lists available compilers",show_compilers),
+	]
 
     def initialize_options (self):
         self.build_clib = None
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/command/build_ext.py distutils.patched/distutils/command/build_ext.py
--- distutils.orig/distutils/command/build_ext.py	Tue Jun  6 11:17:04 2000
+++ distutils.patched/distutils/command/build_ext.py	Tue Jun  6 11:25:34 2000
@@ -14,6 +14,7 @@
 from distutils.errors import *
 from distutils.dep_util import newer_group
 from distutils.extension import Extension
+from distutils.ccompiler import show_compilers
 
 # An extension name is just a dot-separated list of Python NAMEs (ie.
 # the same as a fully-qualified module name).
@@ -72,6 +73,10 @@
         ('compiler=', 'c',
          "specify the compiler type"),
         ]
+    help_options = [
+        ('help-compiler', None,
+         "lists available compilers",show_compilers),
+	]
 
 
     def initialize_options (self):
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/command/sdist.py distutils.patched/distutils/command/sdist.py
--- distutils.orig/distutils/command/sdist.py	Tue Jun  6 11:17:06 2000
+++ distutils.patched/distutils/command/sdist.py	Tue Jun  6 11:20:24 2000
@@ -13,7 +13,7 @@
 from distutils.core import Command
 from distutils.util import newer, create_tree, remove_tree, convert_path, \
      write_file
-from distutils.archive_util import check_archive_formats
+from distutils.archive_util import check_archive_formats,ARCHIVE_FORMATS
 from distutils.text_file import TextFile
 from distutils.errors import DistutilsExecError, DistutilsOptionError
 
@@ -35,11 +35,26 @@
         ('force-manifest', 'f',
          "forcibly regenerate the manifest and carry on as usual"),
         ('formats=', None,
-         "formats for source distribution (tar, ztar, gztar, bztar, or zip)"),
+         "formats for source distribution"),
         ('keep-tree', 'k',
          "keep the distribution tree around after creating " +
          "archive file(s)"),
         ]
+    # prints all possible arguments to --formats
+    def show_formats():
+	from distutils.fancy_getopt import FancyGetopt 
+	list_of_formats=[]
+	for format in ARCHIVE_FORMATS.keys():
+	    list_of_formats.append(("formats="+format,None,ARCHIVE_FORMATS[format][2]))
+	list_of_formats.sort()
+	pretty_printer=FancyGetopt(list_of_formats)
+	pretty_printer.print_help("List of available distribution formats:")
+
+    help_options = [
+        ('help-formats', None,
+         "lists available distribution formats",show_formats),
+	]
+
     negative_opts = {'use-defaults': 'no-defaults'}
 
     default_format = { 'posix': 'gztar',
diff -BurN --minimal --exclude=*.pyc distutils.orig/distutils/dist.py distutils.patched/distutils/dist.py
--- distutils.orig/distutils/dist.py	Tue Jun  6 11:17:02 2000
+++ distutils.patched/distutils/dist.py	Tue Jun  6 11:20:24 2000
@@ -433,16 +433,38 @@
             negative_opt = copy (negative_opt)
             negative_opt.update (cmd_class.negative_opt)
 
+	# Check for help_options in command class
+	# They have a different format (tuple of four) so we need to preprocess them here 
+	help_options = []
+	if hasattr(cmd_class,"help_options") and type (cmd_class.help_options) is ListType:
+	    help_options = map(lambda x:(x[0],x[1],x[2]),cmd_class.help_options)
+
         # All commands support the global options too, just by adding
         # in 'global_options'.
         parser.set_option_table (self.global_options +
-                                 cmd_class.user_options)
+                                 cmd_class.user_options + help_options)
         parser.set_negative_aliases (negative_opt)
         (args, opts) = parser.getopt (args[1:])
         if hasattr(opts, 'help') and opts.help:
             self._show_help(parser, display_options=0, commands=[cmd_class])
             return
 
+	if hasattr(cmd_class,"help_options") and type (cmd_class.help_options) is ListType:
+	    help_option_found=0
+	    for help_option in cmd_class.help_options:
+		if hasattr(opts, parser.get_attr_name(help_option[0])):
+		    help_option_found=1
+		    #print "showing help for option %s of command %s" % (help_option[0],cmd_class)
+		    if callable(help_option[3]):
+			help_option[3]()
+		    else:
+        		raise DistutilsClassError, \
+                          ("command class %s must provide " +
+                           "a callable object for help_option '%s'") % \
+                          (cmd_class,help_option[0])
+	    if help_option_found: 
+		return
+
         # Put the options from the command-line into their official
         # holding pen, the 'command_options' dictionary.
         opt_dict = self.get_option_dict(command)
@@ -492,7 +514,11 @@
                 klass = command
             else:
                 klass = self.get_command_class (command)
-            parser.set_option_table (klass.user_options)
+	    if hasattr(klass,"help_options") and type (klass.help_options) is ListType:
+		parser.set_option_table (klass.user_options+
+					 map(lambda x:(x[0],x[1],x[2]),klass.help_options))
+	    else:
+        	parser.set_option_table (klass.user_options)
             parser.print_help ("Options for '%s' command:" % klass.__name__)
             print
 
@@ -500,7 +526,7 @@
         return
 
     # _show_help ()
-
+	
 
     def handle_display_options (self, option_order):
         """If there were any non-global "display-only" options

--------------D883EB9E16BD4C83A8F2C8C5--