From e44b8cd30d880e9af99d07ae2b697ca586c2c9f3 Mon Sep 17 00:00:00 2001 From: Bastian Kleineidam Date: Mon, 7 Dec 2015 20:16:28 +0100 Subject: [PATCH] Build distribution package as wheel (.whl) file for Python --- .gitignore | 1 + Makefile | 35 ++------ setup.cfg | 11 +-- setup.py | 231 +---------------------------------------------------- 4 files changed, 13 insertions(+), 265 deletions(-) diff --git a/.gitignore b/.gitignore index 7b9da97..a35bade 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Changelog.patool* /testresults.txt /tests/__pycache__ /_patool_configdata.py +/patool.egg-info/ diff --git a/Makefile b/Makefile index c083017..c5de0d3 100644 --- a/Makefile +++ b/Makefile @@ -5,14 +5,11 @@ MAINTAINER:=$(shell $(PYTHON) setup.py --maintainer) AUTHOR:=$(shell $(PYTHON) setup.py --author) APPNAME:=$(shell $(PYTHON) setup.py --name) ARCHIVE_SOURCE:=$(APPNAME)-$(VERSION).tar.gz -ARCHIVE_WIN32:=$(APPNAME)-$(VERSION).exe +ARCHIVE_WHEEL:=$(APPNAME)-$(VERSION)-py2.py3-none-any.whl GITUSER:=wummel GITREPO:=$(APPNAME) HOMEPAGE:=$(HOME)/public_html/patool-webpage.git WEBMETA:=doc/web/app.yaml -DEBUILDDIR:=$(HOME)/projects/debian/unofficial -DEBORIGFILE:=$(DEBUILDDIR)/$(APPNAME)_$(VERSION).orig.tar.gz -DEBPACKAGEDIR:=$(DEBUILDDIR)/$(APPNAME)-$(VERSION) # Pytest options: # --resultlog: write test results in file # -s: do not capture stdout/stderr (some tests fail otherwise) @@ -27,21 +24,20 @@ all: dist: [ -d dist ] || mkdir dist - $(PYTHON) setup.py sdist --formats=tar + $(PYTHON) setup.py sdist --formats=tar bdist_wheel gzip --best dist/$(APPNAME)-$(VERSION).tar -# [ ! -f ../$(ARCHIVE_WIN32) ] || cp ../$(ARCHIVE_WIN32) dist sign: [ -f dist/$(ARCHIVE_SOURCE).asc ] || gpg --detach-sign --armor dist/$(ARCHIVE_SOURCE) -# [ -f dist/$(ARCHIVE_WIN32).asc ] || gpg --detach-sign --armor dist/$(ARCHIVE_WIN32) + [ -f dist/$(ARCHIVE_WHEEL).asc ] || gpg --detach-sign --armor dist/$(ARCHIVE_WHEEL) -upload: upload_source #upload_binary +upload: upload_source upload_binary upload_source: twine upload dist/$(ARCHIVE_SOURCE) dist/$(ARCHIVE_SOURCE).asc upload_binary: - cp dist/$(ARCHIVE_WIN32) dist/$(ARCHIVE_WIN32).asc \ + cp dist/$(ARCHIVE_WHEEL) dist/$(ARCHIVE_WHEEL).asc \ $(HOMEPAGE)/dist update_webmeta: @@ -81,10 +77,6 @@ releasecheck: test check echo "Version in doc/changelog.txt:"; head -n1 doc/changelog.txt; \ echo "Version in setup.py: $(VERSION)"; false; \ fi -# @if [ ! -f ../$(ARCHIVE_WIN32) ]; then \ -# echo "Missing WIN32 distribution archive at ../$(ARCHIVE_WIN32)"; \ -# false; \ -# fi $(PYTHON) setup.py check --restructuredtext check: @@ -128,22 +120,9 @@ test: localbuild $(PYTHON) -m pytest $(PYTESTOPTS) $(TESTOPTS) $(TESTS) doc/$(APPNAME).txt: doc/$(APPNAME).1 -# make text file from man page for Windows builds +# make text file from man page for wheel builds cols=`stty size | cut -d" " -f2`; stty cols 72; man -l $< | sed -e 's/.\cH//g' > $@; stty cols $$cols -deb: -# build a debian package - [ -f $(DEBORIGFILE) ] || cp dist/$(ARCHIVE_SOURCE) $(DEBORIGFILE) - sed -i -e 's/VERSION_$(APPNAME):=.*/VERSION_$(APPNAME):=$(VERSION)/' $(DEBUILDDIR)/$(APPNAME).mak - [ -d $(DEBPACKAGEDIR) ] || (cd $(DEBUILDDIR); \ - patool extract $(DEBORIGFILE); \ - cd $(CURDIR); \ - git checkout debian; \ - cp -r debian $(DEBPACKAGEDIR); \ - rm -f $(DEBPACKAGEDIR)/debian/.gitignore; \ - git checkout master) - $(MAKE) -C $(DEBUILDDIR) $(APPNAME)_clean $(APPNAME) - update-copyright: # update-copyright is a local tool which updates the copyright year for each # modified file. @@ -154,6 +133,6 @@ changelog: # closes issues mentioned in the changelog entries. github-changelog $(DRYRUN) $(GITUSER) $(GITREPO) doc/changelog.txt -.PHONY: changelog update-copyright deb test clean count pyflakes check +.PHONY: changelog update-copyright test clean count pyflakes check .PHONY: releasecheck release upload sign dist all tag register homepage .PHONY: localbuild doccheck diff --git a/setup.cfg b/setup.cfg index 9fc7a70..45d99c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,4 @@ [global] -;command_packages = distcmds -[bdist_rpm] -release = 1 -packager = Bastian Kleineidam -doc_files = doc/*.txt -provides = patool -group = Applications/Archiving -install_script = install-rpm.sh -python = python +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index 97d2ef9..37b0343 100644 --- a/setup.py +++ b/setup.py @@ -26,24 +26,8 @@ import re import shutil import glob import subprocess -try: - from cx_Freeze import setup, Executable -except ImportError: - from distutils.core import setup -try: - # py2exe monkey-patches the distutils.core.Distribution class - # So we need to import it before importing the Distribution class - import py2exe - has_py2exe = True -except ImportError: - # py2exe is not installed - has_py2exe = False -try: - from cx_Freeze.dist import Distribution - executables = [Executable("patool")] -except ImportError: - from distutils.core import Distribution - executables = None +from setuptools import setup +from distutils.core import Distribution from distutils.command.install_lib import install_lib from distutils import util from distutils.file_util import write_file @@ -53,28 +37,6 @@ AppVersion = "1.9" MyName = "Bastian Kleineidam" MyEmail = "bastian.kleineidam@web.de" -py_excludes = ['doctest', 'unittest', 'Tkinter', '_ssl', 'pdb', - 'email', 'calendar', 'ftplib', 'httplib', 'pickle', 'optparse','rfc822' -] -# py2exe options for Windows packaging -py2exe_options = dict( - packages=["encodings", 'patoolib.programs'], - excludes=py_excludes, - # silence py2exe error about not finding msvcp90.dll - dll_excludes=['MSVCP90.dll'], - compressed=1, - optimize=2, -) -# cx_Freeze for Linux RPM packaging -cxfreeze_options = dict( - packages=["encodings", 'patoolib.programs'], - excludes=py_excludes, -) - -# Microsoft Visual C++ runtime version (tested with Python 2.7.2) -MSVCP90Version = '9.0.21022.8' -MSVCP90Token = '1fc8b3b9a1e18e3b' - def normpath (path): """Norm a path name to platform specific notation.""" @@ -128,31 +90,6 @@ def get_nt_platform_vars (): return os.path.expandvars(progvar), architecture -def add_msvc_files (files): - """Add needed MSVC++ runtime files. Only Version 9.0.21022.8 is tested - and can be downloaded here: - http://www.microsoft.com/en-us/download/details.aspx?id=29 - """ - prog_dir, architecture = get_nt_platform_vars() - dirname = "Microsoft.VC90.CRT" - version = "%s_%s_x-ww_d08d0375" % (MSVCP90Token, MSVCP90Version) - args = (architecture, dirname, version) - path = r'C:\Windows\WinSxS\%s_%s_%s\*.*' % args - files.append((dirname, glob.glob(path))) - # Copy the manifest file into the build directory and rename it - # because it must have the same name as the directory. - path = r'C:\Windows\WinSxS\Manifests\%s_%s_%s.manifest' % args - target = os.path.join(os.getcwd(), 'build', '%s.manifest' % dirname) - shutil.copy(path, target) - files.append((dirname, [target])) - - -if 'py2exe' in sys.argv[1:]: - if not has_py2exe: - raise SystemExit("py2exe module could not be imported") - add_msvc_files(data_files) - - class MyInstallLib (install_lib, object): """Custom library installation.""" @@ -185,7 +122,7 @@ class MyInstallLib (install_lib, object): else: val = getattr(cmd_obj, attr) if attr == 'install_data': - cdir = os.path.join(val, "share", "dosage") + cdir = os.path.join(val, "share", AppName) data.append('config_dir = %r' % cnormpath(cdir)) elif attr == 'install_lib': if cmd_obj.root: @@ -259,161 +196,6 @@ class MyDistribution (Distribution, object): "creating %s" % filename, self.verbose >= 1, self.dry_run) -class InnoScript: - """Class to generate INNO script.""" - - def __init__(self, lib_dir, dist_dir, windows_exe_files=[], - console_exe_files=[], service_exe_files=[], - comserver_files=[], lib_files=[]): - """Store INNO script infos.""" - 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] - self.icon = os.path.abspath(r'doc\icon\favicon.ico') - - def chop(self, pathname): - """Remove distribution directory from path name.""" - assert pathname.startswith(self.dist_dir) - return pathname[len(self.dist_dir):] - - def create(self, pathname=r"dist\omt.iss"): - """Create Inno script.""" - self.pathname = pathname - self.distfilebase = "%s-%s" % (self.name, self.version) - self.distfile = self.distfilebase + ".exe" - with open(self.pathname, "w") as fd: - self.write_inno_script(fd) - - def write_inno_script (self, fd): - """Write Inno script contents.""" - print("; WARNING: This script has been created by py2exe. Changes to this script", file=fd) - print("; will be overwritten the next time py2exe is run!", file=fd) - print("[Setup]", file=fd) - print("AppName=%s" % self.name, file=fd) - print("AppVerName=%s %s" % (self.name, self.version), file=fd) - print("ChangesEnvironment=true", file=fd) - print(r"DefaultDirName={pf}\%s" % self.name, file=fd) - print("DefaultGroupName=%s" % self.name, file=fd) - print("OutputBaseFilename=%s" % self.distfilebase, file=fd) - print("OutputDir=..", file=fd) - print("SetupIconFile=%s" % self.icon, file=fd) - print(file=fd) - print("[Tasks]", file=fd) - print("Name: modifypath; Description: Add application directory to %PATH%;", file=fd) - print(file=fd) - # List of source files - files = self.windows_exe_files + \ - self.console_exe_files + \ - self.service_exe_files + \ - self.comserver_files + \ - self.lib_files - print('[Files]', file=fd) - for path in files: - print(r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion' % (path, os.path.dirname(path)), file=fd) - # Set icon filename - print('[Icons]', file=fd) - for path in self.windows_exe_files: - print(r'Name: "{group}\%s"; Filename: "{app}\%s"' % - (self.name, path), file=fd) - for path in self.console_exe_files: - name = os.path.basename(path).capitalize() - print(r'Name: "{group}\%s help"; Filename: "cmd.exe"; Parameters: "/K %s --help";' % (name, path), file=fd) - print(r'Name: "{group}\Uninstall %s"; Filename: "{uninstallexe}"' % self.name, file=fd) - print(file=fd) - # Uninstall optional log files - print('[UninstallDelete]', file=fd) - for path in (self.windows_exe_files + self.console_exe_files): - exename = os.path.basename(path) - print(r'Type: files; Name: "{pf}\%s\%s.log"' % (self.name, exename), file=fd) - print(file=fd) - # Add app dir to PATH - print("[Code]", file=fd) - print("""\ -const - ModPathName = 'modifypath'; - ModPathType = 'user'; - -function ModPathDir(): TArrayOfString; -begin - setArrayLength(Result, 1) - Result[0] := ExpandConstant('{app}'); -end; -#include "modpath.iss" -""", file=fd) - shutil.copy(r"scripts\modpath.iss", "dist") - - - def compile (self): - """Compile Inno script with iscc.exe.""" - progpath = get_nt_platform_vars()[0] - cmd = r'%s\Inno Setup 5\iscc.exe' % progpath - subprocess.check_call([cmd, self.pathname]) - - def sign (self): - """Sign InnoSetup installer with local self-signed certificate.""" - print("*** signing the inno setup installer ***") - pfxfile = r'scripts\%s.pfx' % self.name - if os.path.isfile(pfxfile): - path = get_windows_sdk_path() - signtool = os.path.join(path, "bin", "signtool.exe") - if os.path.isfile(signtool): - cmd = [signtool, 'sign', '/f', pfxfile, self.distfile] - subprocess.check_call(cmd) - else: - print("No signed installer: signtool.exe not found.") - else: - print("No signed installer: certificate %s not found." % pfxfile) - -def get_windows_sdk_path(): - """Return path of Microsoft Windows SDK installation, or None if - not found.""" - try: - import _winreg as winreg - except ImportError: - import winreg - sub_key = r"Software\Microsoft\Microsoft SDKs\Windows" - with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, sub_key) as key: - name = "CurrentInstallFolder" - return winreg.QueryValueEx(key, name)[0] - return None - -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. - Needs InnoSetup to be installed.""" - - def run (self): - """Generate py2exe installer.""" - # First, let py2exe do it's work. - py2exe_build.run(self) - print("*** preparing the inno setup script ***") - lib_dir = self.lib_dir - dist_dir = self.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() - script.sign() -except ImportError: - class MyPy2exe: - """Dummy py2exe class.""" - pass - - args = dict( name = AppName, version = AppVersion, @@ -462,13 +244,6 @@ installed. distclass = MyDistribution, cmdclass = { 'install_lib': MyInstallLib, - 'py2exe': MyPy2exe, - }, - options = { - "py2exe": py2exe_options, - "build_exe": cxfreeze_options, }, ) -if executables: - args["executables"] = executables setup(**args)