diff --git a/wxPython/distutils/README.txt b/wxPython/distutils/README similarity index 100% rename from wxPython/distutils/README.txt rename to wxPython/distutils/README diff --git a/wxPython/distutils/command/build_scripts.py b/wxPython/distutils/command/build_scripts.py index b7c11d472b..f61ad37d03 100644 --- a/wxPython/distutils/command/build_scripts.py +++ b/wxPython/distutils/command/build_scripts.py @@ -7,6 +7,7 @@ Implements the Distutils 'build_scripts' command.""" __revision__ = "$Id$" import sys, os, re +from stat import ST_MODE from distutils import sysconfig from distutils.core import Command from distutils.dep_util import newer @@ -54,10 +55,12 @@ class build_scripts (Command): line to refer to the current Python interpreter as we copy. """ self.mkpath(self.build_dir) + outfiles = [] for script in self.scripts: adjust = 0 script = convert_path(script) outfile = os.path.join(self.build_dir, os.path.basename(script)) + outfiles.append(outfile) if not self.force and not newer(script, outfile): log.debug("not copying %s (up-to-date)", script) @@ -106,6 +109,18 @@ class build_scripts (Command): f.close() self.copy_file(script, outfile) + if os.name == 'posix': + for file in outfiles: + if self.dry_run: + log.info("changing mode of %s", file) + else: + oldmode = os.stat(file)[ST_MODE] & 07777 + newmode = (oldmode | 0555) & 07777 + if newmode != oldmode: + log.info("changing mode of %s from %o to %o", + file, oldmode, newmode) + os.chmod(file, newmode) + # copy_scripts () # class build_scripts diff --git a/wxPython/distutils/command/config.py b/wxPython/distutils/command/config.py index b6f5ad1dc5..f18c79ff43 100644 --- a/wxPython/distutils/command/config.py +++ b/wxPython/distutils/command/config.py @@ -17,6 +17,7 @@ import sys, os, string, re from types import * from distutils.core import Command from distutils.errors import DistutilsExecError +from distutils.sysconfig import customize_compiler from distutils import log LANG_EXT = {'c': '.c', @@ -104,6 +105,7 @@ class config (Command): if not isinstance(self.compiler, CCompiler): self.compiler = new_compiler(compiler=self.compiler, dry_run=self.dry_run, force=1) + customize_compiler(self.compiler) if self.include_dirs: self.compiler.set_include_dirs(self.include_dirs) if self.libraries: @@ -151,7 +153,8 @@ class config (Command): library_dirs=library_dirs, target_lang=lang) - prog = prog + self.compiler.exe_extension + if self.compiler.exe_extension is not None: + prog = prog + self.compiler.exe_extension self.temp_files.append(prog) return (src, obj, prog) diff --git a/wxPython/distutils/command/register.py b/wxPython/distutils/command/register.py new file mode 100644 index 0000000000..29b76cbfd9 --- /dev/null +++ b/wxPython/distutils/command/register.py @@ -0,0 +1,292 @@ +"""distutils.command.register + +Implements the Distutils 'register' command (register with the repository). +""" + +# created 2002/10/21, Richard Jones + +__revision__ = "$Id$" + +import sys, os, string, urllib2, getpass, urlparse +import StringIO, ConfigParser + +from distutils.core import Command +from distutils.errors import * + +class register(Command): + + description = "register the distribution with the repository" + + DEFAULT_REPOSITORY = 'http://www.python.org/pypi' + + user_options = [ + ('repository=', 'r', + "url of repository [default: %s]"%DEFAULT_REPOSITORY), + ('verify', None, + 'verify the package metadata for correctness'), + ('list-classifiers', None, + 'list the valid Trove classifiers'), + ('show-response', None, + 'display full response text from server'), + ] + boolean_options = ['verify', 'show-response', 'list-classifiers'] + + def initialize_options(self): + self.repository = None + self.verify = 0 + self.show_response = 0 + self.list_classifiers = 0 + + def finalize_options(self): + if self.repository is None: + self.repository = self.DEFAULT_REPOSITORY + + def run(self): + self.check_metadata() + if self.verify: + self.verify_metadata() + elif self.list_classifiers: + self.classifiers() + else: + self.send_metadata() + + def check_metadata(self): + """Ensure that all required elements of meta-data (name, version, + URL, (author and author_email) or (maintainer and + maintainer_email)) are supplied by the Distribution object; warn if + any are missing. + """ + metadata = self.distribution.metadata + + missing = [] + for attr in ('name', 'version', 'url'): + if not (hasattr(metadata, attr) and getattr(metadata, attr)): + missing.append(attr) + + if missing: + self.warn("missing required meta-data: " + + string.join(missing, ", ")) + + if metadata.author: + if not metadata.author_email: + self.warn("missing meta-data: if 'author' supplied, " + + "'author_email' must be supplied too") + elif metadata.maintainer: + if not metadata.maintainer_email: + self.warn("missing meta-data: if 'maintainer' supplied, " + + "'maintainer_email' must be supplied too") + else: + self.warn("missing meta-data: either (author and author_email) " + + "or (maintainer and maintainer_email) " + + "must be supplied") + + def classifiers(self): + ''' Fetch the list of classifiers from the server. + ''' + response = urllib2.urlopen(self.repository+'?:action=list_classifiers') + print response.read() + + def verify_metadata(self): + ''' Send the metadata to the package index server to be checked. + ''' + # send the info to the server and report the result + (code, result) = self.post_to_server(self.build_post_data('verify')) + print 'Server response (%s): %s'%(code, result) + + def send_metadata(self): + ''' Send the metadata to the package index server. + + Well, do the following: + 1. figure who the user is, and then + 2. send the data as a Basic auth'ed POST. + + First we try to read the username/password from $HOME/.pypirc, + which is a ConfigParser-formatted file with a section + [server-login] containing username and password entries (both + in clear text). Eg: + + [server-login] + username: fred + password: sekrit + + Otherwise, to figure who the user is, we offer the user three + choices: + + 1. use existing login, + 2. register as a new user, or + 3. set the password to a random string and email the user. + + ''' + choice = 'x' + username = password = '' + + # see if we can short-cut and get the username/password from the + # config + config = None + if os.environ.has_key('HOME'): + rc = os.path.join(os.environ['HOME'], '.pypirc') + if os.path.exists(rc): + print 'Using PyPI login from %s'%rc + config = ConfigParser.ConfigParser() + config.read(rc) + username = config.get('server-login', 'username') + password = config.get('server-login', 'password') + choice = '1' + + # get the user's login info + choices = '1 2 3 4'.split() + while choice not in choices: + print '''We need to know who you are, so please choose either: + 1. use your existing login, + 2. register as a new user, + 3. have the server generate a new password for you (and email it to you), or + 4. quit +Your selection [default 1]: ''', + choice = raw_input() + if not choice: + choice = '1' + elif choice not in choices: + print 'Please choose one of the four options!' + + if choice == '1': + # get the username and password + while not username: + username = raw_input('Username: ') + while not password: + password = getpass.getpass('Password: ') + + # set up the authentication + auth = urllib2.HTTPPasswordMgr() + host = urlparse.urlparse(self.repository)[1] + auth.add_password('pypi', host, username, password) + + # send the info to the server and report the result + code, result = self.post_to_server(self.build_post_data('submit'), + auth) + print 'Server response (%s): %s'%(code, result) + + # possibly save the login + if os.environ.has_key('HOME') and config is None and code == 200: + rc = os.path.join(os.environ['HOME'], '.pypirc') + print 'I can store your PyPI login so future submissions will be faster.' + print '(the login will be stored in %s)'%rc + choice = 'X' + while choice.lower() not in 'yn': + choice = raw_input('Save your login (y/N)?') + if not choice: + choice = 'n' + if choice.lower() == 'y': + f = open(rc, 'w') + f.write('[server-login]\nusername:%s\npassword:%s\n'%( + username, password)) + f.close() + try: + os.chmod(rc, 0600) + except: + pass + elif choice == '2': + data = {':action': 'user'} + data['name'] = data['password'] = data['email'] = '' + data['confirm'] = None + while not data['name']: + data['name'] = raw_input('Username: ') + while data['password'] != data['confirm']: + while not data['password']: + data['password'] = getpass.getpass('Password: ') + while not data['confirm']: + data['confirm'] = getpass.getpass(' Confirm: ') + if data['password'] != data['confirm']: + data['password'] = '' + data['confirm'] = None + print "Password and confirm don't match!" + while not data['email']: + data['email'] = raw_input(' EMail: ') + code, result = self.post_to_server(data) + if code != 200: + print 'Server response (%s): %s'%(code, result) + else: + print 'You will receive an email shortly.' + print 'Follow the instructions in it to complete registration.' + elif choice == '3': + data = {':action': 'password_reset'} + data['email'] = '' + while not data['email']: + data['email'] = raw_input('Your email address: ') + code, result = self.post_to_server(data) + print 'Server response (%s): %s'%(code, result) + + def build_post_data(self, action): + # figure the data to send - the metadata plus some additional + # information used by the package server + meta = self.distribution.metadata + data = { + ':action': action, + 'metadata_version' : '1.0', + 'name': meta.get_name(), + 'version': meta.get_version(), + 'summary': meta.get_description(), + 'home_page': meta.get_url(), + 'author': meta.get_contact(), + 'author_email': meta.get_contact_email(), + 'license': meta.get_licence(), + 'description': meta.get_long_description(), + 'keywords': meta.get_keywords(), + 'platform': meta.get_platforms(), + 'classifiers': meta.get_classifiers(), + 'download_url': meta.get_download_url(), + } + return data + + def post_to_server(self, data, auth=None): + ''' Post a query to the server, and return a string response. + ''' + + # Build up the MIME payload for the urllib2 POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = '\n--' + boundary + end_boundary = sep_boundary + '--' + body = StringIO.StringIO() + for key, value in data.items(): + # handle multiple entries for the same name + if type(value) != type([]): + value = [value] + for value in value: + value = str(value) + body.write(sep_boundary) + body.write('\nContent-Disposition: form-data; name="%s"'%key) + body.write("\n\n") + body.write(value) + if value and value[-1] == '\r': + body.write('\n') # write an extra newline (lurve Macs) + body.write(end_boundary) + body.write("\n") + body = body.getvalue() + + # build the Request + headers = { + 'Content-type': 'multipart/form-data; boundary=%s'%boundary, + 'Content-length': str(len(body)) + } + req = urllib2.Request(self.repository, body, headers) + + # handle HTTP and include the Basic Auth handler + opener = urllib2.build_opener( + urllib2.HTTPBasicAuthHandler(password_mgr=auth) + ) + data = '' + try: + result = opener.open(req) + except urllib2.HTTPError, e: + if self.show_response: + data = e.fp.read() + result = e.code, e.msg + except urllib2.URLError, e: + result = 500, str(e) + else: + if self.show_response: + data = result.read() + result = 200, 'OK' + if self.show_response: + print '-'*75, data, '-'*75 + return result + diff --git a/wxPython/distutils/core.py b/wxPython/distutils/core.py index 9ab419ef4c..a463272c2f 100644 --- a/wxPython/distutils/core.py +++ b/wxPython/distutils/core.py @@ -42,6 +42,19 @@ def gen_usage (script_name): _setup_stop_after = None _setup_distribution = None +# Legal keyword arguments for the setup() function +setup_keywords = ('distclass', 'script_name', 'script_args', 'options', + 'name', 'version', 'author', 'author_email', + 'maintainer', 'maintainer_email', 'url', 'license', + 'description', 'long_description', 'keywords', + 'platforms', 'classifiers', 'download_url') + +# Legal keyword arguments for the Extension constructor +extension_keywords = ('name', 'sources', 'include_dirs', + 'define_macros', 'undef_macros', + 'library_dirs', 'libraries', 'runtime_library_dirs', + 'extra_objects', 'extra_compile_args', 'extra_link_args', + 'export_symbols', 'depends', 'language') def setup (**attrs): """The gateway to the Distutils: do everything your setup script needs @@ -226,11 +239,3 @@ def run_setup (script_name, script_args=None, stop_after="run"): # run_setup () -def get_distutil_options (): - """Returns a list of strings recording changes to the Distutils. - - setup.py files can then do: - if 'optional-thing' in get_distutil_options(): - ... - """ - return [] diff --git a/wxPython/distutils/dist.py b/wxPython/distutils/dist.py index faeb7b10b3..08e2a4f7d8 100644 --- a/wxPython/distutils/dist.py +++ b/wxPython/distutils/dist.py @@ -93,6 +93,8 @@ class Distribution: "print the long package description"), ('platforms', None, "print the list of platforms"), + ('classifiers', None, + "print the list of classifiers"), ('keywords', None, "print the list of keywords"), ] @@ -634,6 +636,8 @@ class Distribution: value = getattr(self.metadata, "get_"+opt)() if opt in ['keywords', 'platforms']: print string.join(value, ',') + elif opt == 'classifiers': + print string.join(value, '\n') else: print value any_display_options = 1 @@ -962,7 +966,8 @@ class DistributionMetadata: "maintainer", "maintainer_email", "url", "license", "description", "long_description", "keywords", "platforms", "fullname", "contact", - "contact_email", "licence") + "contact_email", "licence", "classifiers", + "download_url") def __init__ (self): self.name = None @@ -977,6 +982,8 @@ class DistributionMetadata: self.long_description = None self.keywords = None self.platforms = None + self.classifiers = None + self.download_url = None def write_pkg_info (self, base_dir): """Write the PKG-INFO file into the release tree. @@ -992,6 +999,8 @@ class DistributionMetadata: pkg_info.write('Author: %s\n' % self.get_contact() ) pkg_info.write('Author-email: %s\n' % self.get_contact_email() ) pkg_info.write('License: %s\n' % self.get_license() ) + if self.download_url: + pkg_info.write('Download-URL: %s\n' % self.download_url) long_desc = rfc822_escape( self.get_long_description() ) pkg_info.write('Description: %s\n' % long_desc) @@ -1003,6 +1012,9 @@ class DistributionMetadata: for platform in self.get_platforms(): pkg_info.write('Platform: %s\n' % platform ) + for classifier in self.get_classifiers(): + pkg_info.write('Classifier: %s\n' % classifier ) + pkg_info.close() # write_pkg_info () @@ -1059,6 +1071,12 @@ class DistributionMetadata: def get_platforms(self): return self.platforms or ["UNKNOWN"] + def get_classifiers(self): + return self.classifiers or [] + + def get_download_url(self): + return self.download_url or "UNKNOWN" + # class DistributionMetadata diff --git a/wxPython/distutils/extension.py b/wxPython/distutils/extension.py index 94b56e6ce4..e69f3e93e0 100644 --- a/wxPython/distutils/extension.py +++ b/wxPython/distutils/extension.py @@ -82,6 +82,8 @@ class Extension: from the source extensions if not provided. """ + # When adding arguments to this constructor, be sure to update + # setup_keywords in core.py. def __init__ (self, name, sources, include_dirs=None, define_macros=None, diff --git a/wxPython/distutils/msvccompiler.py b/wxPython/distutils/msvccompiler.py index 724fdc9952..e07a6d5a0c 100644 --- a/wxPython/distutils/msvccompiler.py +++ b/wxPython/distutils/msvccompiler.py @@ -310,7 +310,7 @@ class MSVCCompiler (CCompiler) : input_opt = src output_opt = "/fo" + obj try: - self.spawn ([self.rc] + pp_opts + # Robin added pp_opts + self.spawn ([self.rc] + pp_opts + [output_opt] + [input_opt]) except DistutilsExecError, msg: raise CompileError, msg diff --git a/wxPython/distutils/sysconfig.py b/wxPython/distutils/sysconfig.py index aa3636f609..67353a8d64 100644 --- a/wxPython/distutils/sysconfig.py +++ b/wxPython/distutils/sysconfig.py @@ -146,8 +146,8 @@ def customize_compiler(compiler): varies across Unices and is stored in Python's Makefile. """ if compiler.compiler_type == "unix": - (cc, cxx, opt, ccshared, ldshared, so_ext) = \ - get_config_vars('CC', 'CXX', 'OPT', 'CCSHARED', 'LDSHARED', 'SO') + (cc, cxx, opt, basecflags, ccshared, ldshared, so_ext) = \ + get_config_vars('CC', 'CXX', 'OPT', 'BASECFLAGS', 'CCSHARED', 'LDSHARED', 'SO') if os.environ.has_key('CC'): cc = os.environ['CC'] @@ -159,6 +159,8 @@ def customize_compiler(compiler): cpp = cc + " -E" # not always if os.environ.has_key('LDFLAGS'): ldshared = ldshared + ' ' + os.environ['LDFLAGS'] + if basecflags: + opt = basecflags + ' ' + opt if os.environ.has_key('CFLAGS'): opt = opt + ' ' + os.environ['CFLAGS'] ldshared = ldshared + ' ' + os.environ['CFLAGS'] diff --git a/wxPython/distutils/util.py b/wxPython/distutils/util.py index 17fc320aa6..dc3183b691 100644 --- a/wxPython/distutils/util.py +++ b/wxPython/distutils/util.py @@ -40,10 +40,11 @@ def get_platform (): (osname, host, release, version, machine) = os.uname() - # Convert the OS name to lowercase and remove '/' characters - # (to accommodate BSD/OS) + # Convert the OS name to lowercase, remove '/' characters + # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh") osname = string.lower(osname) osname = string.replace(osname, '/', '') + machine = string.replace(machine, ' ', '_') if osname[:5] == "linux": # At least on Linux/Intel, 'machine' is the processor --