Skip to content

Commit c8b1b6b

Browse files
authored
Refactor packaging build helpers into dedicated modules (#388)
Split the old monolithic `setup.py` build logic into focused helper modules so that static builds are coordinated by dedicated utilities rather than one giant script. Additional changes: - Use fixed library versions - Use GNU mirror FTP for downloading related libraries
1 parent a241c56 commit c8b1b6b

File tree

8 files changed

+651
-585
lines changed

8 files changed

+651
-585
lines changed

.github/workflows/wheels.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ jobs:
110110
include: ${{ fromJson(needs.generate-wheels-matrix.outputs.include) }}
111111

112112
env:
113-
PYXMLSEC_LIBXML2_VERSION: 2.14.5
113+
PYXMLSEC_LIBXML2_VERSION: 2.14.6
114114
PYXMLSEC_LIBXSLT_VERSION: 1.1.43
115115

116116
steps:

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ repos:
3131
rev: v1.18.2
3232
hooks:
3333
- id: mypy
34-
exclude: (setup.py|tests/.*.py|doc/.*)
34+
exclude: (setup.py|tests|build_support/.*.py|doc/.*)
3535
types: []
3636
files: ^.*.pyi?$
3737
additional_dependencies: [lxml-stubs, types-docutils]

build_support/__init__.py

Whitespace-only changes.

build_support/build_ext.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import os
2+
import sys
3+
from distutils import log
4+
from distutils.errors import DistutilsError
5+
6+
from setuptools.command.build_ext import build_ext as build_ext_orig
7+
8+
from .static_build import CrossCompileInfo, StaticBuildHelper
9+
10+
11+
class build_ext(build_ext_orig):
12+
def info(self, message):
13+
self.announce(message, level=log.INFO)
14+
15+
def run(self):
16+
ext = self.ext_map['xmlsec']
17+
self.debug = os.environ.get('PYXMLSEC_ENABLE_DEBUG', False)
18+
self.static = os.environ.get('PYXMLSEC_STATIC_DEPS', False)
19+
self.size_opt = os.environ.get('PYXMLSEC_OPTIMIZE_SIZE', True)
20+
21+
if self.static or sys.platform == 'win32':
22+
helper = StaticBuildHelper(self)
23+
helper.prepare(sys.platform)
24+
else:
25+
import pkgconfig
26+
27+
try:
28+
config = pkgconfig.parse('xmlsec1')
29+
except OSError as error:
30+
raise DistutilsError('Unable to invoke pkg-config.') from error
31+
except pkgconfig.PackageNotFoundError as error:
32+
raise DistutilsError('xmlsec1 is not installed or not in path.') from error
33+
34+
if config is None or not config.get('libraries'):
35+
raise DistutilsError('Bad or incomplete result returned from pkg-config.')
36+
37+
ext.define_macros.extend(config['define_macros'])
38+
ext.include_dirs.extend(config['include_dirs'])
39+
ext.library_dirs.extend(config['library_dirs'])
40+
ext.libraries.extend(config['libraries'])
41+
42+
import lxml
43+
44+
ext.include_dirs.extend(lxml.get_include())
45+
46+
ext.define_macros.extend(
47+
[('MODULE_NAME', self.distribution.metadata.name), ('MODULE_VERSION', self.distribution.metadata.version)]
48+
)
49+
for key, value in ext.define_macros:
50+
if key == 'XMLSEC_CRYPTO' and not (value.startswith('"') and value.endswith('"')):
51+
ext.define_macros.remove((key, value))
52+
ext.define_macros.append((key, f'"{value}"'))
53+
break
54+
55+
if sys.platform == 'win32':
56+
ext.extra_compile_args.append('/Zi')
57+
else:
58+
ext.extra_compile_args.extend(
59+
[
60+
'-g',
61+
'-std=c99',
62+
'-fPIC',
63+
'-fno-strict-aliasing',
64+
'-Wno-error=declaration-after-statement',
65+
'-Werror=implicit-function-declaration',
66+
]
67+
)
68+
69+
if self.debug:
70+
ext.define_macros.append(('PYXMLSEC_ENABLE_DEBUG', '1'))
71+
if sys.platform == 'win32':
72+
ext.extra_compile_args.append('/Od')
73+
else:
74+
ext.extra_compile_args.append('-Wall')
75+
ext.extra_compile_args.append('-O0')
76+
else:
77+
if self.size_opt:
78+
if sys.platform == 'win32':
79+
ext.extra_compile_args.append('/Os')
80+
else:
81+
ext.extra_compile_args.append('-Os')
82+
83+
super().run()
84+
85+
86+
__all__ = ('CrossCompileInfo', 'build_ext')

build_support/network.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import contextlib
2+
import json
3+
from urllib.request import Request, urlopen
4+
5+
DEFAULT_USER_AGENT = 'https://github.com/xmlsec/python-xmlsec'
6+
DOWNLOAD_USER_AGENT = 'python-xmlsec build'
7+
8+
9+
def make_request(url, github_token=None, json_response=False):
10+
headers = {'User-Agent': DEFAULT_USER_AGENT}
11+
if github_token:
12+
headers['authorization'] = 'Bearer ' + github_token
13+
request = Request(url, headers=headers)
14+
with contextlib.closing(urlopen(request)) as response:
15+
charset = response.headers.get_content_charset() or 'utf-8'
16+
content = response.read().decode(charset)
17+
if json_response:
18+
return json.loads(content)
19+
return content
20+
21+
22+
def download_lib(url, filename):
23+
request = Request(url, headers={'User-Agent': DOWNLOAD_USER_AGENT})
24+
with urlopen(request) as response, open(filename, 'wb') as target:
25+
while True:
26+
chunk = response.read(8192)
27+
if not chunk:
28+
break
29+
target.write(chunk)

build_support/releases.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import html.parser
2+
import os
3+
import re
4+
from distutils import log
5+
from distutils.version import StrictVersion as Version
6+
7+
from .network import make_request
8+
9+
10+
class HrefCollector(html.parser.HTMLParser):
11+
def __init__(self, *args, **kwargs):
12+
super().__init__(*args, **kwargs)
13+
self.hrefs = []
14+
15+
def handle_starttag(self, tag, attrs):
16+
if tag == 'a':
17+
for name, value in attrs:
18+
if name == 'href':
19+
self.hrefs.append(value)
20+
21+
22+
def latest_release_from_html(url, matcher):
23+
content = make_request(url)
24+
collector = HrefCollector()
25+
collector.feed(content)
26+
hrefs = collector.hrefs
27+
28+
def comp(text):
29+
try:
30+
return Version(matcher.match(text).groupdict()['version'])
31+
except (AttributeError, ValueError):
32+
return Version('0.0')
33+
34+
latest = max(hrefs, key=comp)
35+
return f'{url}/{latest}'
36+
37+
38+
def latest_release_from_gnome_org_cache(url, lib_name):
39+
cache_url = f'{url}/cache.json'
40+
cache = make_request(cache_url, json_response=True)
41+
latest_version = cache[2][lib_name][-1]
42+
latest_source = cache[1][lib_name][latest_version]['tar.xz']
43+
return f'{url}/{latest_source}'
44+
45+
46+
def latest_release_json_from_github_api(repo):
47+
api_url = f'https://api.github.com/repos/{repo}/releases/latest'
48+
token = os.environ.get('GH_TOKEN')
49+
if token:
50+
log.info('Using GitHub token to avoid rate limiting')
51+
return make_request(api_url, token, json_response=True)
52+
53+
54+
def latest_openssl_release():
55+
return latest_release_json_from_github_api('openssl/openssl')['tarball_url']
56+
57+
58+
def latest_zlib_release():
59+
return latest_release_from_html('https://zlib.net/fossils', re.compile('zlib-(?P<version>.*).tar.gz'))
60+
61+
62+
def latest_libiconv_release():
63+
return latest_release_from_html('https://ftpmirror.gnu.org/libiconv', re.compile('libiconv-(?P<version>.*).tar.gz'))
64+
65+
66+
def latest_libxml2_release():
67+
return latest_release_from_gnome_org_cache('https://download.gnome.org/sources/libxml2', 'libxml2')
68+
69+
70+
def latest_libxslt_release():
71+
return latest_release_from_gnome_org_cache('https://download.gnome.org/sources/libxslt', 'libxslt')
72+
73+
74+
def latest_xmlsec_release():
75+
assets = latest_release_json_from_github_api('lsh123/xmlsec')['assets']
76+
(tar_gz,) = [asset for asset in assets if asset['name'].endswith('.tar.gz')]
77+
return tar_gz['browser_download_url']

0 commit comments

Comments
 (0)