#!/usr/bin/python # -*- coding: iso-8859-1 -*- # Copyright (C) 2000-2010 Bastian Kleineidam # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ Setup file for the distuils module. """ import sys if not (hasattr(sys, 'version_info') or sys.version_info < (2, 6, 0, 'final', 0)): raise SystemExit("This program requires Python 2.6 or later.") import os import subprocess import platform import stat import glob # Use setuptools to support eggs. Note that this conflicts with py2exe. # The setuptools are not officially supported. #try: # from setuptools import setup # from setuptools.command.install_lib import install_lib # from setuptools.command.build_ext import build_ext # from setuptools.command.sdist import sdist # from distutils.command.sdist import sdist as _sdist # sdist.user_options = _sdist.user_options + sdist.user_options # from setuptools.extension import Extension # from setuptools.dist import Distribution #except ImportError: # use distutils from distutils.core import setup, Extension from distutils.command.install_lib import install_lib from distutils.command.build_ext import build_ext from distutils.command.sdist import sdist from distutils.command.clean import clean from distutils.command.build import build from distutils.command.install_data import install_data from distutils.command.register import register from distutils.dir_util import remove_tree, copy_tree from distutils.file_util import write_file, copy_file from distutils import util, log try: # Note that py2exe monkey-patches the distutils.core.Distribution class import py2exe except ImportError: # ignore when py2exe is not installed pass from distutils.core import Distribution AppVersion = "6.0" AppName = "LinkChecker" # py2exe options for windows .exe packaging py2exe_options = dict( packages=["encodings"], excludes=['doctest', 'unittest', 'optcomplete', 'Tkinter', 'win32com.gen_py'], dll_excludes=['MSVCP90.dll'], # add sip so that PyQt4 works # add PyQt4.QtSql so that sqlite neede by QHelpCollection works includes=["sip", "PyQt4.QtSql"], compressed=1, optimize=2, ) def normpath (path): """Norm a path name to platform specific notation.""" return os.path.normpath(path) def cnormpath (path): """Norm a path name to platform specific notation.""" path = normpath(path) if os.name == 'nt': # replace slashes with backslashes path = path.replace("/", "\\") if not os.path.isabs(path): path = normpath(os.path.join(sys.prefix, path)) return path class MyInstallLib (install_lib, object): def install (self): """Install the generated config file.""" outs = super(MyInstallLib, self).install() infile = self.create_conf_file() outfile = os.path.join(self.install_dir, os.path.basename(infile)) self.copy_file(infile, outfile) outs.append(outfile) return outs def create_conf_file (self): cmd_obj = self.distribution.get_command_obj("install") cmd_obj.ensure_finalized() # we have to write a configuration file because we need the # directory (and other stuff like author, url, ...) # all paths are made absolute by cnormpath() data = [] for d in ['purelib', 'platlib', 'lib', 'headers', 'scripts', 'data']: attr = 'install_%s' % d if cmd_obj.root: # cut off root path prefix cutoff = len(cmd_obj.root) # don't strip the path separator if cmd_obj.root.endswith(os.sep): cutoff -= 1 val = getattr(cmd_obj, attr)[cutoff:] else: val = getattr(cmd_obj, attr) if attr == 'install_data': cdir = os.path.join(val, "share", "linkchecker") data.append('config_dir = %r' % cnormpath(cdir)) elif attr == 'install_lib': if cmd_obj.root: _drive, tail = os.path.splitdrive(val) if tail.startswith(os.sep): tail = tail[1:] self.install_lib = os.path.join(cmd_obj.root, tail) else: self.install_lib = val data.append("%s = %r" % (attr, cnormpath(val))) self.distribution.create_conf_file(data, directory=self.install_lib) return self.distribution.get_conf_filename(self.install_lib) def get_outputs (self): """Add the generated config file to the list of outputs.""" outs = super(MyInstallLib, self).get_outputs() outs.append(self.distribution.get_conf_filename(self.install_lib)) return outs class MyInstallData (install_data, object): """Handle locale files and permissions.""" def run (self): """Adjust permissions on POSIX systems.""" self.add_message_files() super(MyInstallData, self).run() self.fix_permissions() def add_message_files (self): """Add locale message files to data_files list.""" for (src, dst) in list_message_files(self.distribution.get_name()): dstdir = os.path.dirname(dst) self.data_files.append((dstdir, [os.path.join("build", dst)])) def fix_permissions (self): """Set correct read permissions on POSIX systems. Might also be possible by setting umask?""" if os.name == 'posix' and not self.dry_run: # Make the data files we just installed world-readable, # and the directories world-executable as well. for path in self.get_outputs(): mode = os.stat(path)[stat.ST_MODE] if stat.S_ISDIR(mode): mode |= 011 mode |= 044 os.chmod(path, mode) class MyDistribution (Distribution, object): """Custom distribution class generating config file.""" def __init__ (self, attrs): super(MyDistribution, self).__init__(attrs) self.console = ['linkchecker'] self.windows = [{ "script": "linkchecker-gui", "icon_resources": [(1, "doc/html/favicon.ico")], }] def run_commands (self): """Generate config file and run commands.""" cwd = os.getcwd() data = [] data.append('config_dir = %r' % os.path.join(cwd, "config")) data.append("install_data = %r" % cwd) data.append("install_scripts = %r" % cwd) self.create_conf_file(data) super(MyDistribution, self).run_commands() def get_conf_filename (self, directory): """Get name for config file.""" return os.path.join(directory, "_%s_configdata.py" % self.get_name()) def create_conf_file (self, data, directory=None): """Create local config file from given data (list of lines) in the directory (or current directory if not given).""" data.insert(0, "# this file is automatically created by setup.py") data.insert(0, "# -*- coding: iso-8859-1 -*-") if directory is None: directory = os.getcwd() filename = self.get_conf_filename(directory) # add metadata metanames = ("name", "version", "author", "author_email", "maintainer", "maintainer_email", "url", "license", "description", "long_description", "keywords", "platforms", "fullname", "contact", "contact_email", "fullname") for name in metanames: method = "get_" + name val = getattr(self.metadata, method)() if isinstance(val, str): val = unicode(val) cmd = "%s = %r" % (name, val) data.append(cmd) # write the config file data.append('appname = "LinkChecker"') util.execute(write_file, (filename, data), "creating %s" % filename, self.verbose >= 1, self.dry_run) def cc_run (args): """Run the C compiler with a simple main program. @return: successful exit flag @rtype: bool """ prog = "int main(){}\n" pipe = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) pipe.communicate(input=prog) if os.WIFEXITED(pipe.returncode): return os.WEXITSTATUS(pipe.returncode) == 0 return False def cc_supports_option (cc, option): """Check if the given C compiler supports the given option. @return: True if the compiler supports the option, else False @rtype: bool """ return cc_run([cc[0], "-E", option, "-"]) def cc_remove_option (compiler, option): for optlist in (compiler.compiler, compiler.compiler_so): if option in optlist: optlist.remove(option) class MyBuildExt (build_ext, object): """Custom build extension command.""" def build_extensions (self): """Add -std=gnu99 to build options if supported.""" # For gcc >= 3 we can add -std=gnu99 to get rid of warnings. extra = [] if self.compiler.compiler_type == 'unix': option = "-std=gnu99" if cc_supports_option(self.compiler.compiler, option): extra.append(option) if platform.machine() == 'm68k': # work around ICE on m68k machines in gcc 4.0.1 cc_remove_option(self.compiler, "-O3") # First, sanity-check the 'extensions' list self.check_extensions_list(self.extensions) for ext in self.extensions: for opt in extra: if opt not in ext.extra_compile_args: ext.extra_compile_args.append(opt) self.build_extension(ext) def list_message_files (package, suffix=".po"): """Return list of all found message files and their installation paths.""" for fname in glob.glob("po/*" + suffix): # basename (without extension) is a locale name localename = os.path.splitext(os.path.basename(fname))[0] yield (fname, os.path.join( "share", "locale", localename, "LC_MESSAGES", "%s.mo" % package)) def check_manifest (): """Snatched from roundup.sf.net. Check that the files listed in the MANIFEST are present when the source is unpacked.""" try: f = open('MANIFEST') except Exception: print '\n*** SOURCE WARNING: The MANIFEST file is missing!' return try: manifest = [l.strip() for l in f.readlines()] finally: f.close() err = [line for line in manifest if not os.path.exists(line)] if err: n = len(manifest) print '\n*** SOURCE WARNING: There are files missing (%d/%d found)!'%( n - len(err), n) print 'Missing:', '\nMissing: '.join(err) class MyBuild (build, object): """Custom build command.""" def build_message_files (self): """For each po/*.po, build .mo file in target locale directory.""" # msgfmt.py is in the po/ subdirectory sys.path.append('po') import msgfmt for (src, dst) in list_message_files(self.distribution.get_name()): build_dst = os.path.join("build", dst) self.mkpath(os.path.dirname(build_dst)) self.announce("Compiling %s -> %s" % (src, build_dst)) msgfmt.make(src, build_dst) def run (self): check_manifest() self.build_message_files() build.run(self) class MyClean (clean, object): """Custom clean command.""" def run (self): if self.all: # remove share directory directory = os.path.join("build", "share") if os.path.exists(directory): remove_tree(directory, dry_run=self.dry_run) else: log.warn("'%s' does not exist -- can't clean it", directory) clean.run(self) class MySdist (sdist, object): """Custom sdist command.""" def get_file_list (self): """Add MANIFEST to the file list.""" super(MySdist, self).get_file_list() self.filelist.append("MANIFEST") # global include dirs include_dirs = [] # global macros define_macros = [] # compiler args extra_compile_args = [] # library directories library_dirs = [] # libraries libraries = [] # scripts scripts = ['linkchecker', 'linkchecker-gui'] if os.name == 'nt': # windows does not have unistd.h define_macros.append(('YY_NO_UNISTD_H', None)) else: extra_compile_args.append("-pedantic") myname = "Bastian Kleineidam" myemail = "calvin@users.sourceforge.net" data_files = [ ('share/linkchecker', ['config/linkcheckerrc', 'config/logging.conf', 'doc/html/lccollection.qhc', 'doc/html/lcdoc.qch']), ('share/linkchecker/examples', ['cgi-bin/lconline/leer.html.en', 'cgi-bin/lconline/leer.html.de', 'cgi-bin/lconline/index.html', 'cgi-bin/lconline/lc_cgi.html.en', 'cgi-bin/lconline/lc_cgi.html.de', 'cgi-bin/lconline/check.js', 'cgi-bin/lc.cgi', 'cgi-bin/lc.fcgi', 'config/linkchecker.apache2.conf', ]), ] if os.name == 'posix': data_files.append(('share/man/man1', ['doc/en/linkchecker.1', 'doc/en/linkchecker-gui.1'])) data_files.append(('share/man/man5', ['doc/en/linkcheckerrc.5'])) data_files.append(('share/man/de/man1', ['doc/de/linkchecker.1', 'doc/de/linkchecker-gui.1'])) data_files.append(('share/man/de/man5', ['doc/de/linkcheckerrc.5'])) data_files.append(('share/linkchecker/examples', ['config/linkchecker-completion', 'doc/examples/check_blacklist.sh', 'doc/examples/check_for_x_errors.sh', 'doc/examples/check_urls.sh'])) class InnoScript: def __init__(self, lib_dir, dist_dir, windows_exe_files=[], console_exe_files=[], service_exe_files=[], comserver_files=[], lib_files=[]): self.lib_dir = lib_dir self.dist_dir = dist_dir if not self.dist_dir[-1] in "\\/": self.dist_dir += "\\" self.name = AppName self.version = AppVersion self.windows_exe_files = [self.chop(p) for p in windows_exe_files] self.console_exe_files = [self.chop(p) for p in console_exe_files] self.service_exe_files = [self.chop(p) for p in service_exe_files] self.comserver_files = [self.chop(p) for p in comserver_files] self.lib_files = [self.chop(p) for p in lib_files] def chop(self, pathname): assert pathname.startswith(self.dist_dir) return pathname[len(self.dist_dir):] def create(self, pathname="dist\\omt.iss"): self.pathname = pathname ofi = self.file = open(pathname, "w") print >> ofi, "; WARNING: This script has been created by py2exe. Changes to this script" print >> ofi, "; will be overwritten the next time py2exe is run!" print >> ofi, r"[Setup]" print >> ofi, r"AppName=%s" % self.name print >> ofi, r"AppVerName=%s %s" % (self.name, self.version) print >> ofi, r"DefaultDirName={pf}\%s" % self.name print >> ofi, r"DefaultGroupName=%s" % self.name print >> ofi, r"OutputBaseFilename=%s-%s" % (self.name, self.version) print >> ofi, r"OutputDir=." print >> ofi files = self.windows_exe_files + \ self.console_exe_files + \ self.service_exe_files + \ self.comserver_files + \ self.lib_files print >> ofi, r"[Files]" for path in files: print >> ofi, r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion' % (path, os.path.dirname(path)) # Install Microsoft Visual C runtime DLL installer vcredist = os.path.join(self.dist_dir, 'vcredist_x86.exe') print >> ofi, r'Source: "%s"; DestDir: "{app}"; Flags: ignoreversion' % vcredist print >> ofi # Set icon filename print >> ofi, r"[Icons]" for path in self.windows_exe_files: print >> ofi, r'Name: "{group}\%s"; Filename: "{app}\%s"' % \ (self.name, path) print >> ofi, 'Name: "{group}\Uninstall %s"; Filename: "{uninstallexe}"' % self.name print >> ofi # Uninstall registry keys print >> ofi, r"[Registry]" print >> ofi, r'Root: HKCU; Subkey: "Software\Bastian\LinkChecker"; Flags: uninsdeletekey' print >> ofi # Run Microsoft Visual C runtime DLL installer print >> ofi, r'[Run]' print >> ofi, r'Filename: "{app}\vcredist_x86.exe"; StatusMsg: "Installing Microsoft dependencies"; Parameters: "/q:a"; Flags: waituntilterminated shellexec' def compile(self): import ctypes res = ctypes.windll.shell32.ShellExecuteA(0, "compile", self.pathname, None, None, 0) if res < 32: raise RuntimeError("ShellExecute failed, error %d" % res) try: from py2exe.build_exe import py2exe as py2exe_build class MyPy2exe (py2exe_build): """First builds the exe file(s), then creates a Windows installer. You need InnoSetup for it.""" def run (self): # First, let py2exe do it's work. py2exe_build.run(self) lib_dir = self.lib_dir dist_dir = self.dist_dir # Copy needed sqlite plugin files to distribution directory. import PyQt4 src = os.path.join(os.path.dirname(PyQt4.__file__), "plugins", "sqldrivers") dst = os.path.join(dist_dir, "sqldrivers") copy_tree(src, dst) for path in os.listdir(dst): self.lib_files.append(os.path.join(dst, path)) # Copy Microsoft Visual C runtime DLL installer copy_file(r'c:\software\vcredist_x86.exe', dist_dir) # create the Installer, using the files py2exe has created. script = InnoScript(lib_dir, dist_dir, self.windows_exe_files, self.console_exe_files, self.service_exe_files, self.comserver_files, self.lib_files) print "*** creating the inno setup script***" script.create() print "*** compiling the inno setup script***" script.compile() except ImportError: class MyPy2exe: pass class MyRegister (register, object): def build_post_data(self, action): """Force application name to lower case.""" data = super(MyRegister, self).build_post_data(action) data['name'] = data['name'].lower() return data setup ( name = AppName, version = AppVersion, description = "check websites and HTML documents for broken links", keywords = "link,url,checking,verification", author = myname, author_email = myemail, maintainer = myname, maintainer_email = myemail, url = "http://linkchecker.sourceforge.net/", download_url="http://sourceforge.net/project/showfiles.php?group_id=1913", license = "GPL", long_description = """Linkchecker features: o recursive and multithreaded checking o output in colored or normal text, HTML, SQL, CSV, XML or a sitemap graph in different formats o HTTP/1.1, HTTPS, FTP, mailto:, news:, nntp:, Telnet and local file links support o restrict link checking with regular expression filters for URLs o proxy support o username/password authorization for HTTP, FTP and Telnet o honors robots.txt exclusion protocol o Cookie support o HTML and CSS syntax check o Antivirus check o a command line interface o a (Fast)CGI web interface (requires HTTP server) """, distclass = MyDistribution, cmdclass = { 'install_lib': MyInstallLib, 'install_data': MyInstallData, 'build_ext': MyBuildExt, 'build': MyBuild, 'clean': MyClean, 'sdist': MySdist, 'py2exe': MyPy2exe, 'register': MyRegister, }, packages = [ 'linkcheck', 'linkcheck.logger', 'linkcheck.checker', 'linkcheck.director', 'linkcheck.configuration', 'linkcheck.cache', 'linkcheck.htmlutil', 'linkcheck.dns', 'linkcheck.dns.rdtypes', 'linkcheck.dns.rdtypes.ANY', 'linkcheck.dns.rdtypes.IN', 'linkcheck.HtmlParser', 'linkcheck.network', 'linkcheck.gui', ], ext_modules = [ Extension('linkcheck.HtmlParser.htmlsax', sources = [ 'linkcheck/HtmlParser/htmllex.c', 'linkcheck/HtmlParser/htmlparse.c', 'linkcheck/HtmlParser/s_util.c', ], extra_compile_args = extra_compile_args, library_dirs = library_dirs, libraries = libraries, define_macros = define_macros + [('YY_NO_INPUT', None)], include_dirs = include_dirs + [normpath("linkcheck/HtmlParser")], ), Extension("linkcheck.network._network", sources = ["linkcheck/network/_network.c"], extra_compile_args = extra_compile_args, library_dirs = library_dirs, libraries = libraries, define_macros = define_macros, include_dirs = include_dirs, ), ], scripts = scripts, data_files = data_files, classifiers = [ 'Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking', 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Programming Language :: Python', 'Programming Language :: C', ], options = {"py2exe": py2exe_options}, )