From c8105c8dc379967d5816392929a261035b308e35 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 5 Oct 2015 22:17:42 +0100 Subject: [PATCH] Fix #44 Implement native Debian packaging (control files are part of the source repo) with Makefile to simplify the release procedure. --- Makefile | 176 ++++++++++++++++++++++++++++++++++++++++++ README.rst | 21 ++--- RELEASE.rst | 51 ++++++++++++ debian/changelog | 8 ++ debian/compat | 1 + debian/control | 32 ++++++++ debian/copyright | 33 ++++++++ debian/docs | 1 + debian/rules | 20 +++++ debian/source/format | 1 + debian/source/options | 3 + setup.py | 113 +++++++++++++++++++-------- 12 files changed, 419 insertions(+), 41 deletions(-) create mode 100644 Makefile create mode 100644 RELEASE.rst create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 debian/source/options diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e5a2326 --- /dev/null +++ b/Makefile @@ -0,0 +1,176 @@ +# vim: set noet sw=4 ts=4 fileencoding=utf-8: + +# External utilities +PYTHON=python +PIP=pip +PYTEST=py.test +COVERAGE=coverage +PYFLAGS= +DEST_DIR=/ + +# Horrid hack to ensure setuptools is installed in our python environment. This +# is necessary with Python 3.3's venvs which don't install it by default. +ifeq ($(shell python -c "import setuptools" 2>&1),) +SETUPTOOLS:= +else +SETUPTOOLS:=$(shell wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $(PYTHON)) +endif + +# Calculate the base names of the distribution, the location of all source, +# documentation, packaging, icon, and executable script files +NAME:=$(shell $(PYTHON) $(PYFLAGS) setup.py --name) +VER:=$(shell $(PYTHON) $(PYFLAGS) setup.py --version) +ifeq ($(shell lsb_release -si),Ubuntu) +DEB_SUFFIX:=ubuntu1 +else +DEB_SUFFIX:= +endif +DEB_ARCH:=$(shell dpkg --print-architecture) +PYVER:=$(shell $(PYTHON) $(PYFLAGS) -c "import sys; print('py%d.%d' % sys.version_info[:2])") +PY_SOURCES:=$(shell \ + $(PYTHON) $(PYFLAGS) setup.py egg_info >/dev/null 2>&1 && \ + grep -v "\.egg-info" $(NAME).egg-info/SOURCES.txt) +DEB_SOURCES:=debian/changelog \ + debian/control \ + debian/copyright \ + debian/rules \ + debian/docs \ + $(wildcard debian/*.init) \ + $(wildcard debian/*.default) \ + $(wildcard debian/*.manpages) \ + $(wildcard debian/*.docs) \ + $(wildcard debian/*.doc-base) \ + $(wildcard debian/*.desktop) +DOC_SOURCES:= +SUBDIRS:= + +# Calculate the name of all outputs +DIST_EGG=dist/$(NAME)-$(VER)-$(PYVER).egg +DIST_TAR=dist/$(NAME)-$(VER).tar.gz +DIST_ZIP=dist/$(NAME)-$(VER).zip +DIST_DEB=dist/python-$(NAME)_$(VER)-1$(DEB_SUFFIX)_all.deb \ + dist/python3-$(NAME)_$(VER)-1$(DEB_SUFFIX)_all.deb \ + dist/python-$(NAME)-docs_$(VER)-1$(DEB_SUFFIX)_all.deb \ + dist/$(NAME)_$(VER)-1$(DEB_SUFFIX)_$(DEB_ARCH).changes +DIST_DSC=dist/$(NAME)_$(VER)-1$(DEB_SUFFIX).tar.gz \ + dist/$(NAME)_$(VER)-1$(DEB_SUFFIX).dsc \ + dist/$(NAME)_$(VER)-1$(DEB_SUFFIX)_source.changes +MAN_PAGES= + + +# Default target +all: + @echo "make install - Install on local system" + @echo "make develop - Install symlinks for development" + @echo "make test - Run tests" + @echo "make doc - Generate HTML and PDF documentation" + @echo "make source - Create source package" + @echo "make egg - Generate a PyPI egg package" + @echo "make zip - Generate a source zip package" + @echo "make tar - Generate a source tar package" + @echo "make deb - Generate Debian packages" + @echo "make dist - Generate all packages" + @echo "make clean - Get rid of all generated files" + @echo "make release - Create and tag a new release" + @echo "make upload - Upload the new release to repositories" + +install: $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py install --root $(DEST_DIR) + +doc: $(DOC_SOURCES) + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(MAKE) -C docs latexpdf + +source: $(DIST_TAR) $(DIST_ZIP) + +egg: $(DIST_EGG) + +zip: $(DIST_ZIP) + +tar: $(DIST_TAR) + +deb: $(DIST_DEB) $(DIST_DSC) + +dist: $(DIST_EGG) $(DIST_DEB) $(DIST_DSC) $(DIST_TAR) $(DIST_ZIP) + +develop: tags + @# These have to be done separately to avoid a cockup... + $(PIP) install -U setuptools + $(PIP) install -U pip + $(PIP) install -e . + +test: + $(COVERAGE) run -m $(PYTEST) tests -v + $(COVERAGE) report --rcfile coverage.cfg + +clean: + $(PYTHON) $(PYFLAGS) setup.py clean + $(MAKE) -f $(CURDIR)/debian/rules clean + $(MAKE) -C docs clean + rm -fr build/ dist/ $(NAME).egg-info/ tags + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir clean; \ + done + find $(CURDIR) -name "*.pyc" -delete + +tags: $(PY_SOURCES) + ctags -R --exclude="build/*" --exclude="debian/*" --exclude="docs/*" --languages="Python" + +$(SUBDIRS): + $(MAKE) -C $@ + +$(MAN_PAGES): $(DOC_SOURCES) + $(PYTHON) $(PYFLAGS) setup.py build_sphinx -b man + mkdir -p man/ + cp build/sphinx/man/*.[0-9] man/ + +$(DIST_TAR): $(PY_SOURCES) $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py sdist --formats gztar + +$(DIST_ZIP): $(PY_SOURCES) $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py sdist --formats zip + +$(DIST_EGG): $(PY_SOURCES) $(SUBDIRS) + $(PYTHON) $(PYFLAGS) setup.py bdist_egg + +$(DIST_DEB): $(PY_SOURCES) $(SUBDIRS) $(DEB_SOURCES) $(MAN_PAGES) + # build the binary package in the parent directory then rename it to + # project_version.orig.tar.gz + $(PYTHON) $(PYFLAGS) setup.py sdist --dist-dir=../ + rename -f 's/$(NAME)-(.*)\.tar\.gz/$(NAME)_$$1\.orig\.tar\.gz/' ../* + debuild -b -i -I -Idist -Ibuild -Idocs/_build -Icoverage -I__pycache__ -I.coverage -Itags -I*.pyc -I*.vim -I*.xcf -rfakeroot + mkdir -p dist/ + for f in $(DIST_DEB); do cp ../$${f##*/} dist/; done + +$(DIST_DSC): $(PY_SOURCES) $(SUBDIRS) $(DEB_SOURCES) $(MAN_PAGES) + # build the source package in the parent directory then rename it to + # project_version.orig.tar.gz + $(PYTHON) $(PYFLAGS) setup.py sdist --dist-dir=../ + rename -f 's/$(NAME)-(.*)\.tar\.gz/$(NAME)_$$1\.orig\.tar\.gz/' ../* + debuild -S -i -I -Idist -Ibuild -Idocs/_build -Icoverage -I__pycache__ -I.coverage -Itags -I*.pyc -I*.vim -I*.xcf -rfakeroot + mkdir -p dist/ + for f in $(DIST_DSC); do cp ../$${f##*/} dist/; done + +release: $(PY_SOURCES) $(DOC_SOURCES) $(DEB_SOURCES) + $(MAKE) clean + # ensure there are no current uncommitted changes + test -z "$(shell git status --porcelain)" + # update the debian changelog with new release information + dch --newversion $(VER)-1$(DEB_SUFFIX) --controlmaint + # commit the changes and add a new tag + git commit debian/changelog -m "Updated changelog for release $(VER)" + git tag -s release-$(VER) -m "Release $(VER)" + # update the package's registration on PyPI (in case any metadata's changed) + $(PYTHON) $(PYFLAGS) setup.py register + +upload: $(PY_SOURCES) $(DOC_SOURCES) $(DIST_DEB) $(DIST_DSC) + # build a source archive and upload to PyPI + $(PYTHON) $(PYFLAGS) setup.py sdist upload + # build the deb source archive and upload to Raspbian + dput raspberrypi dist/$(NAME)_$(VER)-1$(DEB_SUFFIX)_source.changes + dput raspberrypi dist/$(NAME)_$(VER)-1$(DEB_SUFFIX)_$(DEB_ARCH).changes + git push --tags + +.PHONY: all install develop test doc source egg zip tar deb dist clean tags release upload $(SUBDIRS) + diff --git a/README.rst b/README.rst index ae5ab06..ae34077 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,6 @@ Example usage for lighting up an LED:: from gpiozero import LED led = LED(2) - led.on() Documentation @@ -55,7 +54,8 @@ This project is being developed on `GitHub`_. Join in: * Help design the `API`_ * Contribute to the code -Alternatively, email suggestions and feedback to ben@raspberrypi.org or add to the `Google Doc`_. +Alternatively, email suggestions and feedback to ben@raspberrypi.org or add to +the `Google Doc`_. Contributors ============ @@ -65,11 +65,12 @@ Contributors - `Martin O'Hanlon`_ -.. _`pythonhosted.org/gpiozero`: http://pythonhosted.org/gpiozero -.. _`GitHub`: https://github.com/RPi-Distro/python-gpiozero -.. _`Issues`: https://github.com/RPi-Distro/python-gpiozero/issues -.. _`API`: https://github.com/RPi-Distro/python-gpiozero/issues/7 -.. _`Google Doc`: https://docs.google.com/document/d/1EbbVjdgXbKVPFlgH_pEEtPZ0zOZVSPHT4sQNW88Am7w/edit?usp=sharing -.. _`Ben Nuttall`: https://github.com/bennuttall -.. _`Dave Jones`: https://github.com/waveform80 -.. _`Martin O'Hanlon`: https://github.com/martinohanlon +.. _pythonhosted.org/gpiozero: http://pythonhosted.org/gpiozero +.. _GitHub: https://github.com/RPi-Distro/python-gpiozero +.. _Issues: https://github.com/RPi-Distro/python-gpiozero/issues +.. _API: https://github.com/RPi-Distro/python-gpiozero/issues/7 +.. _Google Doc: https://docs.google.com/document/d/1EbbVjdgXbKVPFlgH_pEEtPZ0zOZVSPHT4sQNW88Am7w/edit?usp=sharing +.. _Ben Nuttall: https://github.com/bennuttall +.. _Dave Jones: https://github.com/waveform80 +.. _Martin O'Hanlon: https://github.com/martinohanlon + diff --git a/RELEASE.rst b/RELEASE.rst new file mode 100644 index 0000000..1a27244 --- /dev/null +++ b/RELEASE.rst @@ -0,0 +1,51 @@ +================= +Release Procedure +================= + +On your build Pi, perform the following steps: + +1. Ensure you have a reliable Internet connection (preferably Ethernet). + +2. Ensure you have the following Debian packages installed: ``devscripts, dput, + python-all, python3-all, gnupg, build-essential, git, python-setuptools, + python3-setuptools, python-rpi.gpio, python3-rpi.gpio`` + +3. Ensure you have a valid ``~/.pypirc`` configuration. For example:: + + [distutils] + index-servers = + pypi + + [pypi] + username:my_username + password:my_long_password + +4. Ensure you have a valid ``~/.dput.cf`` setup which includes the + ``[raspberrypi]`` target. For example:: + + [raspberrypi] + fqdn = build-master.raspberrypi.org + incoming = incoming + login = incoming + method = scp + +5. Ensure you have a valid public/private key-pair defined for GNUPG. + +6. In the ``python-gpiozero`` directory, run ``make release``. This will launch + ``dch`` to update the Debian changelog. Fill this out properly (ticket + references!) and the release will be generated, tagged, signed, and + registered with GitHub and PyPI. + + .. note:: + + Although the release has been registered at this point, no packages + have been generated or uploaded to any service. + +7. Still in the ``python-gpiozero`` directory, run ``make upload``. This will + generate the actual debian packages, upload them to Raspbian, and upload + the source package to PyPI. + +8. On GitHub, close any milestone associated with the release. + +9. On ReadTheDocs, update the project configuration to build the new release, + then set it to the default version for the project. diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..3f4758b --- /dev/null +++ b/debian/changelog @@ -0,0 +1,8 @@ +gpiozero (0.6.0-1) stable; urgency=medium + + * Raspbian packaging (#44) + * PWM functionality including variable level RGB LEDs (#40) + * Ability to recreate GPIO device objects (#38) + + -- Ben Nuttall Mon, 05 Oct 2015 22:21:48 +0100 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..601312a --- /dev/null +++ b/debian/control @@ -0,0 +1,32 @@ +Source: gpiozero +Maintainer: Ben Nuttall +Homepage: http://github.com/RPi-Distro/python-gpiozero +Section: python +Priority: extra +Build-Depends: debhelper (>= 8), python-all (>= 2.7), python-setuptools, python3-all, python3-setuptools +Standards-Version: 3.9.3 +X-Python-Version: >= 2.7 +X-Python3-Version: >= 3.2 + +Package: python-gpiozero +Architecture: all +Depends: ${misc:Depends}, ${python:Depends}, python-rpi.gpio, python-w1thermsensor, python-spidev +Description: Simple API for controlling devices attached to the GPIO pins. + gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify + interaction with devices connected to the GPIO pins, from simple buttons and + LEDs, up to various add-on boards. The API tries to adhere closely to Python's + idioms and naming conventions. + . + This is the Python 2 version of the package. + +Package: python3-gpiozero +Architecture: all +Depends: ${misc:Depends}, ${python3:Depends}, python3-rpi.gpio, python3-w1thermsensor, python3-spidev +Description: Simple API for controlling devices attached to the GPIO pins. + gpiozero builds on RPi.GPIO to provide a set of classes designed to simplify + interaction with devices connected to the GPIO pins, from simple buttons and + LEDs, up to various add-on boards. The API tries to adhere closely to Python's + idioms and naming conventions. + . + This is the Python 3 version of the package. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..5890dc3 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,33 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: gpiozero +Source: https://github.com/RPi-Distro/python-gpiozero + +Files: * +Copyright: 2015 Ben Nuttall +License: BSD-3-Clause + +License: BSD-3-Clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..a1320b1 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README.rst diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..4922cc2 --- /dev/null +++ b/debian/rules @@ -0,0 +1,20 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +#export DH_VERBOSE=1 +export DH_OPTIONS + +%: + dh $@ --with python2,python3 --buildsystem=python_distutils + +override_dh_auto_install: + python setup.py install --root debian/python-picraft --install-layout=deb + python3 setup.py install --root debian/python3-picraft --install-layout=deb + +#override_dh_auto_test: +# # Don't run the tests! + +#override_dh_installdocs: +# python setup.py build_sphinx -b html +# dh_installdocs + diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/source/options b/debian/source/options new file mode 100644 index 0000000..d3d540d --- /dev/null +++ b/debian/source/options @@ -0,0 +1,3 @@ +extend-diff-ignore = "(^|/)(Makefile|setup\.cfg)$" +extend-diff-ignore = "(^|/)(docs/.*)$" +extend-diff-ignore = "(^|/)(build/sphinx/doctrees/.*)$" diff --git a/setup.py b/setup.py index 18834b9..8e1f65d 100644 --- a/setup.py +++ b/setup.py @@ -1,37 +1,88 @@ import os +import sys from setuptools import setup, find_packages +"A simple interface to everyday GPIO components used with Raspberry Pi" -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() +if sys.version_info[0] == 2: + if not sys.version_info >= (2, 7): + raise ValueError('This package requires Python 2.7 or above') +elif sys.version_info[0] == 3: + if not sys.version_info >= (3, 2): + raise ValueError('This package requires Python 3.2 or above') +else: + raise ValueError('Unrecognized major version of Python') + +HERE = os.path.abspath(os.path.dirname(__file__)) + +# Workaround +try: + import multiprocessing +except ImportError: + pass + +__project__ = 'gpiozero' +__version__ = '0.7.0' +__author__ = 'Ben Nuttall' +__author_email__ = 'ben@raspberrypi.org' +__url__ = 'https://github.com/RPi-Distro/python-gpiozero' +__platforms__ = 'ALL' + +__classifiers__ = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Education", + "Intended Audience :: Developers", + "Topic :: Education", + "Topic :: System :: Hardware", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", +] + +__keywords__ = [ + 'raspberrypi', + 'gpio', +] + +__requires__ = [ + 'RPi.GPIO', + 'w1thermsensor', + 'spidev', +] + +__extra_requires__ = { +} + +__entry_points__ = { +} -setup( - name="gpiozero", - version="0.7.0", - author="Ben Nuttall", - description="A simple interface to everyday GPIO components used with Raspberry Pi", - license="BSD", - keywords=[ - "raspberrypi", - "gpio", - ], - url="https://github.com/RPi-Distro/gpio-zero", - packages=find_packages(), - install_requires=[ - "RPi.GPIO", - "w1thermsensor", - "spidev", - ], - long_description=read('README.rst'), - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Education", - "Intended Audience :: Developers", - "Topic :: Education", - "Topic :: System :: Hardware", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 3", - ], -) +def main(): + import io + with io.open(os.path.join(HERE, 'README.rst'), 'r') as readme: + setup( + name = __project__, + version = __version__, + description = __doc__, + long_description = readme.read(), + classifiers = __classifiers__, + author = __author__, + author_email = __author_email__, + url = __url__, + license = [ + c.rsplit('::', 1)[1].strip() + for c in __classifiers__ + if c.startswith('License ::') + ][0], + keywords = __keywords__, + packages = find_packages(), + include_package_data = True, + platforms = __platforms__, + install_requires = __requires__, + extras_require = __extra_requires__, + entry_points = __entry_points__, + ) + + +if __name__ == '__main__': + main()