diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index a18a014a..b9145f5b 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -1,125 +1,100 @@
# Makefile.am for the Python bindings.
# Copyright (C) 2016 g10 Code GmbH
#
# This file is part of GPGME.
#
# GPGME is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# GPGME 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 Lesser General
# Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see .
EXTRA_DIST = \
README \
MANIFEST.in \
gpgme.i \
helpers.c helpers.h private.h \
gpgme-h-clean.py \
examples \
gpg
SUBDIRS = . tests
-COPY_FILES = \
- $(srcdir)/gpgme.i \
- $(srcdir)/README \
- $(srcdir)/MANIFEST.in \
- $(srcdir)/gpgme-h-clean.py \
- $(srcdir)/examples \
- $(srcdir)/helpers.c $(srcdir)/helpers.h $(srcdir)/private.h
-
-COPY_FILES_GPG = \
- $(srcdir)/gpg/callbacks.py \
- $(srcdir)/gpg/constants \
- $(srcdir)/gpg/core.py \
- $(srcdir)/gpg/errors.py \
- $(srcdir)/gpg/__init__.py \
- $(srcdir)/gpg/results.py \
- $(srcdir)/gpg/util.py
-
.PHONY: prepare
-prepare:
- test -n "$(PREPAREDIR)"
- $(MKDIR_P) "$(PREPAREDIR)/gpg"
- cp -R $(COPY_FILES) "$(PREPAREDIR)"
- cp setup.py "$(PREPAREDIR)"
- cp gpg/version.py "$(PREPAREDIR)/gpg"
- ln -sf "$(abs_top_srcdir)/src/data.h" "$(PREPAREDIR)"
- ln -sf "$(abs_top_builddir)/config.h" "$(PREPAREDIR)"
- cp -R $(COPY_FILES_GPG) "$(PREPAREDIR)/gpg"
+prepare: copystamp
# For VPATH builds we need to copy some files because Python's
# distutils are not VPATH-aware.
-copystamp: $(COPY_FILES) $(COPY_FILES_GPG)
- set -e ; for VERSION in $(PYTHON_VERSIONS); do \
- $(MAKE) PREPAREDIR=python$${VERSION}-gpg prepare; \
- done
+copystamp:
+ ln -sf "$(abs_top_srcdir)/src/data.h" .
+ ln -sf "$(abs_top_builddir)/config.h" .
touch $@
all-local: copystamp
set -e ; set $(PYTHONS); for VERSION in $(PYTHON_VERSIONS); do \
PYTHON="$$1" ; shift ; \
- cd python$${VERSION}-gpg && \
CFLAGS="$(CFLAGS)" \
abs_top_builddir="$(abs_top_builddir)" \
- $$PYTHON setup.py build --verbose ; \
- cd .. ; \
+ $$PYTHON setup.py build --verbose --build-base=python$${VERSION}-gpg ; \
done
-python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \
python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc: copystamp
- cd python$(PYTHON_VERSION)-gpg && \
+ $(MKDIR_P) python$(PYTHON_VERSION)-gpg-dist
CFLAGS="$(CFLAGS)" \
abs_top_builddir="$(abs_top_builddir)" \
- $(PYTHON) setup.py sdist --verbose
- gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz
+ $(PYTHON) setup.py sdist --verbose --dist-dir=python$(PYTHON_VERSION)-gpg-dist \
+ --manifest=python$(PYTHON_VERSION)-gpg-dist/MANIFEST
+ gpg2 --detach-sign --armor python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz
.PHONY: sdist
-sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \
- python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
+sdist: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
.PHONY: upload
-upload: python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz \
- python$(PYTHON_VERSION)-gpg/dist/gpg-$(VERSION).tar.gz.asc
+upload: python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz \
+ python$(PYTHON_VERSION)-gpg-dist/gpg-$(VERSION).tar.gz.asc
twine upload $^
-CLEANFILES = copystamp
+CLEANFILES = copystamp \
+ config.h \
+ data.h \
+ files.txt \
+ install_files.txt
# Remove the rest.
#
# 'make distclean' clears the write bit, breaking rm -rf. Fix the
# permissions.
clean-local:
rm -rf -- build
for VERSION in $(PYTHON_VERSIONS); do \
find python$${VERSION}-gpg -type d ! -perm -200 -exec chmod u+w {} ';' ; \
rm -rf -- python$${VERSION}-gpg ; \
done
install-exec-local:
- rm -f install_files.txt
set -e ; set $(PYTHONS); for VERSION in $(PYTHON_VERSIONS); do \
PYTHON="$$1" ; shift ; \
- cd python$${VERSION}-gpg ; \
abs_top_builddir="$(abs_top_builddir)" \
- $$PYTHON setup.py install \
- --prefix $(DESTDIR)$(prefix) \
+ $$PYTHON setup.py \
+ build \
+ --build-base=python$${VERSION}-gpg \
+ install \
+ --prefix "$(DESTDIR)$(prefix)" \
--record files.txt \
--verbose ; \
- cat files.txt >> ../install_files.txt ; \
+ cat files.txt >> install_files.txt ; \
rm files.txt ; \
- cd .. ; \
done
- $(MKDIR_P) $(DESTDIR)$(pythondir)/gpg
- mv install_files.txt $(DESTDIR)$(pythondir)/gpg
+ $(MKDIR_P) "$(DESTDIR)$(pythondir)/gpg"
+ mv install_files.txt "$(DESTDIR)$(pythondir)/gpg"
uninstall-local:
- xargs <$(DESTDIR)$(pythondir)/gpg/install_files.txt -- rm -rf --
- rm -rf -- $(DESTDIR)$(pythondir)/gpg
+ xargs < "$(DESTDIR)$(pythondir)/gpg/install_files.txt" -- rm -rf --
+ rm -rf -- "$(DESTDIR)$(pythondir)/gpg"
diff --git a/lang/python/setup.py.in b/lang/python/setup.py.in
index 8ddbf276..6692de6f 100755
--- a/lang/python/setup.py.in
+++ b/lang/python/setup.py.in
@@ -1,194 +1,220 @@
#!/usr/bin/env python
# Copyright (C) 2016 g10 Code GmbH
# Copyright (C) 2004 Igor Belyi
# Copyright (C) 2002 John Goerzen
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from distutils.core import setup, Extension
import os, os.path, sys
import glob
+import shutil
import subprocess
# Out-of-tree build of the gpg bindings.
gpg_error_config = ["gpg-error-config"]
gpgme_config_flags = ["--thread=pthread"]
gpgme_config = ["gpgme-config"] + gpgme_config_flags
gpgme_h = ""
include_dirs = [os.getcwd()]
library_dirs = []
in_tree = False
extra_swig_opts = []
extra_macros = dict()
abs_top_builddir = os.environ.get("abs_top_builddir")
if abs_top_builddir:
# In-tree build.
in_tree = True
gpgme_config = [os.path.join(abs_top_builddir, "src/gpgme-config")] + gpgme_config_flags
gpgme_h = os.path.join(abs_top_builddir, "src/gpgme.h")
library_dirs = [os.path.join(abs_top_builddir, "src/.libs")] # XXX uses libtool internals
extra_macros.update(
HAVE_CONFIG_H=1,
HAVE_DATA_H=1,
IN_TREE_BUILD=1,
)
if hasattr(subprocess, "DEVNULL"):
devnull = subprocess.DEVNULL
else:
devnull = open(os.devnull, "w")
try:
subprocess.check_call(gpg_error_config + ['--version'],
stdout=devnull)
except:
sys.exit("Could not find gpg-error-config. " +
"Please install the libgpg-error development package.")
try:
subprocess.check_call(gpgme_config + ['--version'],
stdout=devnull)
except:
sys.exit("Could not find gpgme-config. " +
"Please install the libgpgme development package.")
def getconfig(what, config=gpgme_config):
confdata = subprocess.Popen(config + ["--%s" % what],
stdout=subprocess.PIPE).communicate()[0]
return [x for x in confdata.decode('utf-8').split() if x != '']
version = version_raw = getconfig("version")[0]
if '-' in version:
version = version.split('-')[0]
major, minor, patch = map(int, version.split('.'))
if not (major > 1 or (major == 1 and minor >= 7)):
sys.exit('Need at least GPGME version 1.7, found {}.'.format(version_raw))
if not gpgme_h:
gpgme_h = os.path.join(getconfig("prefix")[0], "include", "gpgme.h")
gpg_error_prefix = getconfig("prefix", config=gpg_error_config)[0]
gpg_error_h = os.path.join(gpg_error_prefix, "include", "gpg-error.h")
if not os.path.exists(gpg_error_h):
gpg_error_h = \
glob.glob(os.path.join(gpg_error_prefix, "include",
"*", "gpg-error.h"))[0]
-print("Building python gpg module using {} and {}.".format(gpgme_h, gpg_error_h))
-
-# Cleanup gpgme.h from deprecated functions and typedefs.
-subprocess.check_call([sys.executable, "gpgme-h-clean.py", gpgme_h],
- stdout=open("gpgme.h", "w"))
-subprocess.check_call([sys.executable, "gpgme-h-clean.py", gpg_error_h],
- stdout=open("errors.i", "w"))
-
define_macros = []
libs = getconfig('libs')
# Define extra_macros for both the SWIG and C code
for k, v in extra_macros.items():
extra_swig_opts.append("-D{0}={1}".format(k, v))
define_macros.append((k, str(v)))
for item in getconfig('cflags'):
if item.startswith("-I"):
include_dirs.append(item[2:])
elif item.startswith("-D"):
defitem = item[2:].split("=", 1)
if len(defitem)==2:
define_macros.append((defitem[0], defitem[1]))
else:
define_macros.append((defitem[0], None))
# Adjust include and library locations in case of win32
uname_s = os.popen("uname -s").read()
if uname_s.startswith("MINGW32"):
mnts = [x.split()[0:3:2] for x in os.popen("mount").read().split("\n") if x]
tmplist = sorted([(len(x[1]), x[1], x[0]) for x in mnts])
tmplist.reverse()
extra_dirs = []
for item in include_dirs:
for ln, mnt, tgt in tmplist:
if item.startswith(mnt):
item = os.path.normpath(item[ln:])
while item[0] == os.path.sep:
item = item[1:]
extra_dirs.append(os.path.join(tgt, item))
break
include_dirs += extra_dirs
for item in [x[2:] for x in libs if x.startswith("-L")]:
for ln, mnt, tgt in tmplist:
if item.startswith(mnt):
item = os.path.normpath(item[ln:])
while item[0] == os.path.sep:
item = item[1:]
library_dirs.append(os.path.join(tgt, item))
break
# We build an Extension using SWIG, which generates a Python module.
# By default, the 'build_py' step is run before 'build_ext', and
# therefore the generated Python module is not copied into the build
# directory.
# Bug: http://bugs.python.org/issue1016626
# Workaround:
# http://stackoverflow.com/questions/12491328/python-distutils-not-include-the-swig-generated-module
from distutils.command.build import build
class BuildExtFirstHack(build):
+
+ def _generate(self):
+ print("Building python gpg module using {} and {}.".format(gpgme_h, gpg_error_h))
+
+ # Cleanup gpgme.h from deprecated functions and typedefs.
+ # Keep timestamp to avoid rebuild
+ if not os.path.exists(self.build_base):
+ os.makedirs(self.build_base)
+
+ for src, dst in (
+ (gpgme_h, os.path.join(self.build_base, "gpgme.h")),
+ (gpg_error_h, os.path.join(self.build_base, "errors.i"))
+ ):
+ subprocess.check_call([sys.executable, "gpgme-h-clean.py", src],
+ stdout=open(dst, "w"))
+ shutil.copystat(src, dst)
+
+ # Copy due to http://bugs.python.org/issue2624
+ # Avoid creating in srcdir
+ shutil.copy2("gpgme.i", self.build_base)
+
def run(self):
+ self._generate()
+
+ # Append generated files via build_base
+ if not os.path.exists(os.path.join(self.build_lib, "gpg")):
+ os.makedirs(os.path.join(self.build_lib, "gpg"))
+
+ swig_sources.append(os.path.join(self.build_base, 'gpgme.i'))
+ swig_opts.extend(['-I' + self.build_base, '-outdir', os.path.join(self.build_lib, 'gpg')])
+ include_dirs.append(self.build_base)
+
self.run_command('build_ext')
build.run(self)
py3 = [] if sys.version_info.major < 3 else ['-py3']
-swige = Extension("gpg._gpgme", ["gpgme.i", "helpers.c"],
- swig_opts = ['-threads',
- '-outdir', 'gpg'] + py3 + extra_swig_opts,
+swig_sources = ['helpers.c']
+swig_opts = ['-threads'] + py3 + extra_swig_opts
+swige = Extension("gpg._gpgme",
+ sources = swig_sources,
+ swig_opts = swig_opts,
include_dirs = include_dirs,
define_macros = define_macros,
library_dirs = library_dirs,
extra_link_args = libs)
setup(name="gpg",
cmdclass={'build': BuildExtFirstHack},
version="@VERSION@",
description='Python bindings for GPGME GnuPG cryptography library',
# XXX add a long description
#long_description=long_description,
author='The GnuPG hackers',
author_email='gnupg-devel@gnupg.org',
url='https://www.gnupg.org',
ext_modules=[swige],
packages = ['gpg', 'gpg.constants', 'gpg.constants.data',
'gpg.constants.keylist', 'gpg.constants.sig',
'gpg.constants.tofu'],
license="LGPL2.1+ (the library), GPL2+ (tests and examples)",
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Operating System :: POSIX',
'Operating System :: Microsoft :: Windows',
'Topic :: Communications :: Email',
'Topic :: Security :: Cryptography',
],
)
diff --git a/lang/python/tests/run-tests.py b/lang/python/tests/run-tests.py
index 9e061d83..9e2fb78c 100644
--- a/lang/python/tests/run-tests.py
+++ b/lang/python/tests/run-tests.py
@@ -1,106 +1,105 @@
#!/usr/bin/env python
# Copyright (C) 2016 g10 Code GmbH
#
# This file is part of GPGME.
#
# GPGME 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.
#
# GPGME 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 Lesser General
# Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see .
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import glob
import os
import subprocess
import sys
class SplitAndAccumulate(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
current = getattr(namespace, self.dest, list())
current.extend(values.split())
setattr(namespace, self.dest, current)
parser = argparse.ArgumentParser(description='Run tests.')
parser.add_argument('tests', metavar='TEST', type=str, nargs='+',
help='A test to run')
parser.add_argument('-v', '--verbose', action="store_true", default=False,
help='Be verbose.')
parser.add_argument('-q', '--quiet', action="store_true", default=False,
help='Be quiet.')
parser.add_argument('--interpreters', metavar='PYTHON', type=str,
default=[], action=SplitAndAccumulate,
help='Use these interpreters to run the tests, ' +
'separated by spaces.')
parser.add_argument('--srcdir', type=str,
default=os.environ.get("srcdir", ""),
help='Location of the tests.')
parser.add_argument('--builddir', type=str,
default=os.environ.get("abs_builddir", ""),
help='Location of the tests.')
parser.add_argument('--parallel', action="store_true", default=False,
help='Ignored. For compatibility with run-tests.scm.')
args = parser.parse_args()
if not args.interpreters:
args.interpreters = [sys.executable]
out = sys.stdout if args.verbose else None
err = sys.stderr if args.verbose else None
def status_to_str(code):
return {0: "PASS", 77: "SKIP", 99: "ERROR"}.get(code, "FAIL")
results = list()
for interpreter in args.interpreters:
version = subprocess.check_output(
[interpreter, "-c", "import sys; print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))"]).strip().decode()
pattern = os.path.join(args.builddir, "..",
"python{0}-gpg".format(version),
- "build",
"lib*"+version)
builddirs = glob.glob(pattern)
if len(builddirs) == 0:
sys.exit("Build directory matching {0!r} not found.".format(pattern))
elif len(builddirs) > 1:
sys.exit("Multiple build directories matching {0!r} found: {1}".format(
pattern, builddirs))
env = dict(os.environ)
env["PYTHONPATH"] = builddirs[0]
if not args.quiet:
print("Running tests using {0} ({1})...".format(interpreter, version))
for test in args.tests:
status = subprocess.call(
[interpreter, os.path.join(args.srcdir, test)],
env=env, stdout=out, stderr=err)
if not args.quiet:
print("{0}: {1}".format(status_to_str(status), test))
results.append(status)
def count(status):
return len(list(filter(lambda x: x == status, results)))
def failed():
return len(list(filter(lambda x: x not in (0, 77, 99), results)))
if not args.quiet:
print("{0} tests run, {1} succeeded, {2} failed, {3} skipped.".format(
len(results), count(0), failed(), count(77)))
sys.exit(len(results) - count(0) - count(77))
sys.exit(results[0])