summaryrefslogtreecommitdiff
path: root/winbuild
diff options
context:
space:
mode:
authorwinbuild <winbuild@example.com>2020-01-28 12:08:51 -0500
committerOleg Pudeyev <oleg@bsdpower.com>2020-01-28 13:31:03 -0500
commit1c7b2a3c4dd2326840e67220f34bbe9098a00ea8 (patch)
tree627b8527a633917350de982dacaf1ad6a8ffffda /winbuild
parente1e4f6638bdb75fffd6afab8e9777a0c5ccdf84f (diff)
downloadpycurl-1c7b2a3c4dd2326840e67220f34bbe9098a00ea8.tar.gz
Reorganize winbuild
Diffstat (limited to 'winbuild')
-rw-r--r--winbuild/builder.py165
-rw-r--r--winbuild/cares.py27
-rw-r--r--winbuild/config.py108
-rw-r--r--winbuild/curl.py105
-rw-r--r--winbuild/iconv.py11
-rw-r--r--winbuild/idn.py10
-rw-r--r--winbuild/nghttp.py121
-rw-r--r--winbuild/openssl.py76
-rw-r--r--winbuild/pycurl.py119
-rw-r--r--winbuild/ssh.py40
-rw-r--r--winbuild/utils.py14
-rw-r--r--winbuild/zlib.py25
12 files changed, 821 insertions, 0 deletions
diff --git a/winbuild/builder.py b/winbuild/builder.py
new file mode 100644
index 0000000..1653a25
--- /dev/null
+++ b/winbuild/builder.py
@@ -0,0 +1,165 @@
+import os, os.path, shutil, sys, subprocess
+from .utils import *
+from .config import *
+
+class Batch(object):
+ def __init__(self, bconf):
+ self.bconf = bconf
+ self.commands = []
+
+ self.add(self.vcvars_cmd)
+ self.add('echo on')
+ if self.bconf.vc_version == 'vc14':
+ # I don't know why vcvars doesn't configure this under vc14
+ self.add('set include=%s\\include;%%include%%' % self.bconf.windows_sdk_path)
+ if self.bconf.bitness == 32:
+ self.add('set lib=%s\\lib;%%lib%%' % self.bconf.windows_sdk_path)
+ self.add('set path=%s\\bin;%%path%%' % self.bconf.windows_sdk_path)
+ else:
+ self.add('set lib=%s\\lib\\x64;%%lib%%' % self.bconf.windows_sdk_path)
+ self.add('set path=%s\\bin\\x64;%%path%%' % self.bconf.windows_sdk_path)
+ self.add(self.nasm_cmd)
+
+ self.add('set path=%s;%%path%%' % PATHS[self.bconf.bitness]['rc_bin'])
+
+ def add(self, cmd):
+ self.commands.append(cmd)
+
+ # if patch fails to apply hunks, it exits with nonzero code.
+ # if patch doesn't find the patch file to apply, it exits with a zero code!
+ ERROR_CHECK = 'IF %ERRORLEVEL% NEQ 0 exit %errorlevel%'
+
+ def batch_text(self):
+ return ("\n" + self.ERROR_CHECK + "\n").join(self.commands)
+
+ @property
+ def vcvars_bitness_parameter(self):
+ params = {
+ 32: 'x86',
+ 64: 'amd64',
+ }
+ return params[self.bconf.bitness]
+
+ @property
+ def vcvars_relative_path(self):
+ return 'vc/vcvarsall.bat'
+
+ @property
+ def vc_path(self):
+ if self.bconf.vc_version in config.vc_paths and config.vc_paths[self.bconf.vc_version]:
+ path = config.vc_paths[self.bconf.vc_version]
+ if not os.path.join(path, self.vcvars_relative_path):
+ raise Exception('vcvars not found in specified path')
+ return path
+ else:
+ for path in config.default_vc_paths[self.bconf.vc_version]:
+ if os.path.exists(os.path.join(path, self.vcvars_relative_path)):
+ return path
+ raise Exception('No usable vc path found')
+
+ @property
+ def vcvars_path(self):
+ return os.path.join(self.vc_path, self.vcvars_relative_path)
+
+ @property
+ def vcvars_cmd(self):
+ # https://msdn.microsoft.com/en-us/library/x4d2c09s.aspx
+ return "call \"%s\" %s" % (
+ self.vcvars_path,
+ self.vcvars_bitness_parameter,
+ )
+
+ @property
+ def nasm_cmd(self):
+ return "set path=%s;%%path%%\n" % config.nasm_path
+
+class BuildConfig(ExtendedConfig):
+ '''Parameters for a particular build configuration.
+
+ Unlike ExtendedConfig, this class fixes bitness and Python version.
+ '''
+
+ def __init__(self, **kwargs):
+ ExtendedConfig.__init__(self, **kwargs)
+ for k in kwargs:
+ setattr(self, k, kwargs[k])
+
+ assert self.bitness
+ assert self.bitness in (32, 64)
+ assert self.vc_version
+
+ @property
+ def vc_tag(self):
+ return '%s-%s' % (self.vc_version, self.bitness)
+
+class Builder(object):
+ def __init__(self, **kwargs):
+ self.bconf = kwargs.pop('bconf')
+ self.use_dlls = False
+
+ @contextlib.contextmanager
+ def execute_batch(self):
+ batch = Batch(self.bconf)
+ yield batch
+ with open('doit.bat', 'w') as f:
+ f.write(batch.batch_text())
+ if False:
+ print("Executing:")
+ with open('doit.bat', 'r') as f:
+ print(f.read())
+ sys.stdout.flush()
+ rv = subprocess.call(['doit.bat'])
+ if rv != 0:
+ print("\nFailed to execute the following commands:\n")
+ with open('doit.bat', 'r') as f:
+ print(f.read())
+ sys.stdout.flush()
+ exit(3)
+
+class StandardBuilder(Builder):
+ @property
+ def state_tag(self):
+ return self.output_dir_path
+
+ @property
+ def bin_path(self):
+ return os.path.join(config.archives_path, self.output_dir_path, 'dist', 'bin')
+
+ @property
+ def include_path(self):
+ return os.path.join(config.archives_path, self.output_dir_path, 'dist', 'include')
+
+ @property
+ def lib_path(self):
+ return os.path.join(config.archives_path, self.output_dir_path, 'dist', 'lib')
+
+ @property
+ def dll_paths(self):
+ raise NotImplementedError
+
+ @property
+ def builder_name(self):
+ return self.__class__.__name__.replace('Builder', '').lower()
+
+ @property
+ def my_version(self):
+ return getattr(self.bconf, '%s_version' % self.builder_name)
+
+ @property
+ def output_dir_path(self):
+ return '%s-%s-%s' % (self.builder_name, self.my_version, self.bconf.vc_tag)
+
+ def standard_fetch_extract(self, url_template):
+ url = url_template % dict(
+ my_version=self.my_version,
+ )
+ fetch(url)
+ archive_basename = os.path.basename(url)
+ archive_name = archive_basename.replace('.tar.gz', '')
+ untar(archive_name)
+
+ suffixed_dir = self.output_dir_path
+ if os.path.exists(suffixed_dir):
+ shutil.rmtree(suffixed_dir)
+ os.rename(archive_name, suffixed_dir)
+ return suffixed_dir
diff --git a/winbuild/cares.py b/winbuild/cares.py
new file mode 100644
index 0000000..1c9929b
--- /dev/null
+++ b/winbuild/cares.py
@@ -0,0 +1,27 @@
+from .utils import *
+from .builder import *
+
+class CaresBuilder(StandardBuilder):
+ def build(self):
+ cares_dir = self.standard_fetch_extract(
+ 'http://c-ares.haxx.se/download/c-ares-%(my_version)s.tar.gz')
+ if self.bconf.cares_version == '1.12.0':
+ # msvc_ver.inc is missing in c-ares-1.12.0.tar.gz
+ # https://github.com/c-ares/c-ares/issues/69
+ fetch('https://raw.githubusercontent.com/c-ares/c-ares/cares-1_12_0/msvc_ver.inc',
+ archive='cares-1.12.0/msvc_ver.inc')
+ with in_dir(cares_dir):
+ with self.execute_batch() as b:
+ if self.bconf.cares_version == '1.10.0':
+ b.add("patch -p1 < %s" %
+ require_file_exists(os.path.join(config.winbuild_patch_root, 'c-ares-vs2015.patch')))
+ b.add("nmake -f Makefile.msvc")
+
+ # assemble dist
+ b.add('mkdir dist dist\\include dist\\lib')
+ if self.bconf.cares_version_tuple < (1, 14, 0):
+ subdir = 'ms%s0' % self.bconf.vc_version
+ else:
+ subdir = 'msvc'
+ b.add('cp %s/cares/lib-release/*.lib dist/lib' % subdir)
+ b.add('cp *.h dist/include')
diff --git a/winbuild/config.py b/winbuild/config.py
new file mode 100644
index 0000000..76ab857
--- /dev/null
+++ b/winbuild/config.py
@@ -0,0 +1,108 @@
+class ExtendedConfig(Config):
+ '''Global configuration that specifies what the entire process will do.
+
+ Unlike Config, this class contains also various derived properties
+ for convenience.
+ '''
+
+ def __init__(self, **kwargs):
+ for k in kwargs:
+ setattr(self, k, kwargs[k])
+
+ # These are defaults, overrides can be specified as vc_paths in Config above
+ default_vc_paths = {
+ # where msvc 9 is installed, for python 2.6-3.2
+ 'vc9': [
+ 'c:/program files (x86)/microsoft visual studio 9.0',
+ 'c:/program files/microsoft visual studio 9.0',
+ ],
+ # where msvc 10 is installed, for python 3.3-3.4
+ 'vc10': [
+ 'c:/program files (x86)/microsoft visual studio 10.0',
+ 'c:/program files/microsoft visual studio 10.0',
+ ],
+ # where msvc 14 is installed, for python 3.5-3.8
+ 'vc14': [
+ 'c:/program files (x86)/microsoft visual studio 14.0',
+ 'c:/program files/microsoft visual studio 14.0',
+ ],
+ }
+
+ @property
+ def nasm_path(self):
+ return select_existing_path(Config.nasm_path)
+
+ @property
+ def activestate_perl_path(self):
+ return select_existing_path(Config.activestate_perl_path)
+
+ @property
+ def archives_path(self):
+ return os.path.join(self.root, 'archives')
+
+ @property
+ def state_path(self):
+ return os.path.join(self.root, 'state')
+
+ @property
+ def git_bin_path(self):
+ #git_bin_path = os.path.join(git_root, 'bin')
+ return ''
+
+ @property
+ def git_path(self):
+ return os.path.join(self.git_bin_path, 'git')
+
+ @property
+ def rm_path(self):
+ return find_in_paths('rm', self.msysgit_bin_paths)
+
+ @property
+ def cp_path(self):
+ return find_in_paths('cp', self.msysgit_bin_paths)
+
+ @property
+ def sed_path(self):
+ return find_in_paths('sed', self.msysgit_bin_paths)
+
+ @property
+ def tar_path(self):
+ return find_in_paths('tar', self.msysgit_bin_paths)
+
+ @property
+ def activestate_perl_bin_path(self):
+ return os.path.join(self.activestate_perl_path, 'bin')
+
+ @property
+ def winbuild_patch_root(self):
+ return os.path.join(DIR_HERE, 'winbuild')
+
+ @property
+ def openssl_version_tuple(self):
+ return tuple(
+ int(part) if part < 'a' else part
+ for part in re.sub(r'([a-z])', r'.\1', self.openssl_version).split('.')
+ )
+
+ @property
+ def libssh2_version_tuple(self):
+ return tuple(int(part) for part in self.libssh2_version.split('.'))
+
+ @property
+ def cares_version_tuple(self):
+ return tuple(int(part) for part in self.cares_version.split('.'))
+
+ @property
+ def libcurl_version_tuple(self):
+ return tuple(int(part) for part in self.libcurl_version.split('.'))
+
+ @property
+ def python_releases(self):
+ return [PythonRelease('.'.join(version.split('.')[:2]))
+ for version in self.python_versions]
+
+ def buildconfigs(self):
+ return [BuildConfig(bitness=bitness, vc_version=vc_version)
+ for bitness in self.bitnesses
+ for vc_version in needed_vc_versions(self.python_versions)
+ ]
diff --git a/winbuild/curl.py b/winbuild/curl.py
new file mode 100644
index 0000000..f412076
--- /dev/null
+++ b/winbuild/curl.py
@@ -0,0 +1,105 @@
+import os.path, shutil, os
+from .utils import *
+from .builder import *
+
+class LibcurlBuilder(StandardBuilder):
+ def build(self):
+ curl_dir = self.standard_fetch_extract(
+ 'https://curl.haxx.se/download/curl-%(my_version)s.tar.gz')
+
+ with in_dir(os.path.join(curl_dir, 'winbuild')):
+ if self.bconf.vc_version == 'vc9':
+ # normaliz.lib in vc9 does not have the symbols libcurl
+ # needs for winidn.
+ # Handily we have a working normaliz.lib in vc14.
+ # Let's take the working one and copy it locally.
+ os.mkdir('support')
+ if self.bconf.bitness == 32:
+ shutil.copy(os.path.join(self.bconf.windows_sdk_path, 'lib', 'normaliz.lib'),
+ os.path.join('support', 'normaliz.lib'))
+ else:
+ shutil.copy(os.path.join(self.bconf.windows_sdk_path, 'lib', 'x64', 'normaliz.lib'),
+ os.path.join('support', 'normaliz.lib'))
+
+ with self.execute_batch() as b:
+ b.add("patch -p1 < %s" %
+ require_file_exists(os.path.join(config.winbuild_patch_root, 'libcurl-fix-zlib-references.patch')))
+ if self.use_dlls:
+ dll_or_static = 'dll'
+ else:
+ dll_or_static = 'static'
+ extra_options = ' mode=%s' % dll_or_static
+ if self.bconf.vc_version == 'vc9':
+ # use normaliz.lib from msvc14/more recent windows sdk
+ b.add("set lib=%s;%%lib%%" % os.path.abspath('support'))
+ if self.bconf.use_zlib:
+ zlib_builder = ZlibBuilder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % zlib_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % zlib_builder.lib_path)
+ extra_options += ' WITH_ZLIB=%s' % dll_or_static
+ if self.bconf.use_openssl:
+ openssl_builder = OpensslBuilder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % openssl_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % openssl_builder.lib_path)
+ extra_options += ' WITH_SSL=%s' % dll_or_static
+ if self.bconf.use_cares:
+ cares_builder = CaresBuilder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % cares_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % cares_builder.lib_path)
+ extra_options += ' WITH_CARES=%s' % dll_or_static
+ if self.bconf.use_libssh2:
+ libssh2_builder = Libssh2Builder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % libssh2_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % libssh2_builder.lib_path)
+ extra_options += ' WITH_SSH2=%s' % dll_or_static
+ if self.bconf.use_nghttp2:
+ nghttp2_builder = Nghttp2Builder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % nghttp2_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % nghttp2_builder.lib_path)
+ extra_options += ' WITH_NGHTTP2=%s NGHTTP2_STATICLIB=1' % dll_or_static
+ if self.bconf.use_libidn:
+ libidn_builder = LibidnBuilder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % libidn_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % libidn_builder.lib_path)
+ extra_options += ' WITH_LIBIDN=%s' % dll_or_static
+ if config.openssl_version_tuple >= (1, 1):
+ # openssl 1.1.0
+ # https://curl.haxx.se/mail/lib-2016-08/0104.html
+ # https://github.com/curl/curl/issues/984
+ # crypt32.lib: http://stackoverflow.com/questions/37522654/linking-with-openssl-lib-statically
+ extra_options += ' MAKE="NMAKE /e" SSL_LIBS="libssl.lib libcrypto.lib crypt32.lib"'
+ # https://github.com/curl/curl/issues/1863
+ extra_options += ' VC=%s' % self.bconf.vc_version[2:]
+
+ # curl uses winidn APIs that do not exist in msvc9:
+ # https://github.com/curl/curl/issues/1863
+ # We work around the msvc9 deficiency by using
+ # msvc14 normaliz.lib on vc9.
+ extra_options += ' ENABLE_IDN=yes'
+
+ b.add("nmake /f Makefile.vc %s" % extra_options)
+
+ # assemble dist - figure out where libcurl put its files
+ # and move them to a more reasonable location
+ with in_dir(curl_dir):
+ subdirs = sorted(os.listdir('builds'))
+ if len(subdirs) != 3:
+ raise Exception('Should be 3 directories here')
+ expected_dir = subdirs.pop(0)
+ for dir in subdirs:
+ if not dir.startswith(expected_dir):
+ raise Exception('%s does not start with %s' % (dir, expected_dir))
+
+ os.rename(os.path.join('builds', expected_dir), 'dist')
+ if self.bconf.vc_version == 'vc9':
+ # need this normaliz.lib to build pycurl later on
+ shutil.copy('winbuild/support/normaliz.lib', 'dist/lib/normaliz.lib')
+
+ # need libcurl.lib to build pycurl with --curl-dir argument
+ shutil.copy('dist/lib/libcurl_a.lib', 'dist/lib/libcurl.lib')
+
+ @property
+ def dll_paths(self):
+ return [
+ os.path.join(self.bin_path, 'libcurl.dll'),
+ ]
diff --git a/winbuild/iconv.py b/winbuild/iconv.py
new file mode 100644
index 0000000..8a25fd9
--- /dev/null
+++ b/winbuild/iconv.py
@@ -0,0 +1,11 @@
+from .utils import *
+from .builder import *
+
+class LibiconvBuilder(StandardBuilder):
+ def build(self):
+ libiconv_dir = self.standard_fetch_extract(
+ 'https://ftp.gnu.org/pub/gnu/libiconv/libiconv-%(my_version)s.tar.gz')
+ with in_dir(libiconv_dir):
+ with self.execute_batch() as b:
+ b.add("env LD=link bash ./configure")
+ b.add(config.gmake_path)
diff --git a/winbuild/idn.py b/winbuild/idn.py
new file mode 100644
index 0000000..e7820b8
--- /dev/null
+++ b/winbuild/idn.py
@@ -0,0 +1,10 @@
+from .utils import *
+from .builder import *
+
+class LibidnBuilder(StandardBuilder):
+ def build(self):
+ libidn_dir = self.standard_fetch_extract(
+ 'https://ftp.gnu.org/gnu/libidn/libidn-%(my_version)s.tar.gz')
+ with in_dir(libidn_dir):
+ with self.execute_batch() as b:
+ b.add("env LD=link bash ./configure")
diff --git a/winbuild/nghttp.py b/winbuild/nghttp.py
new file mode 100644
index 0000000..8b65a98
--- /dev/null
+++ b/winbuild/nghttp.py
@@ -0,0 +1,121 @@
+import shutil
+from .builder import *
+
+class Nghttp2Builder(StandardBuilder):
+ CMAKE_GENERATORS = {
+ # Thanks cmake for requiring both version number and year,
+ # necessitating this additional map
+ 'vc9': 'Visual Studio 9 2008',
+ 'vc14': 'Visual Studio 14 2015',
+ }
+
+ def build(self):
+ nghttp2_dir = self.standard_fetch_extract(
+ 'https://github.com/nghttp2/nghttp2/releases/download/v%(my_version)s/nghttp2-%(my_version)s.tar.gz')
+
+ # nghttp2 uses stdint.h which msvc9 does not ship.
+ # Amazingly, nghttp2 can seemingly build successfully without this
+ # file existing, but libcurl build subsequently fails
+ # when it tries to include stdint.h.
+ # Well, the reason why nghttp2 builds correctly is because it is built
+ # with the wrong compiler - msvc14 when 9 and 14 are both installed.
+ # nghttp2 build with msvc9 does fail without stdint.h existing.
+ if self.bconf.vc_version == 'vc9':
+ # https://stackoverflow.com/questions/126279/c99-stdint-h-header-and-ms-visual-studio
+ fetch('https://raw.githubusercontent.com/mattn/gntp-send/master/include/msinttypes/stdint.h')
+ with in_dir(nghttp2_dir):
+ shutil.copy('../stdint.h', 'lib/includes/stdint.h')
+
+ with in_dir(nghttp2_dir):
+ generator = self.CMAKE_GENERATORS[self.bconf.vc_version]
+ with self.execute_batch() as b:
+ # Workaround for VCTargetsPath issue that looks like this:
+ # C:\dev\build-pycurl\archives\nghttp2-1.40.0-vc14-32\CMakeFiles\3.16.3\VCTargetsPath.vcxproj(14,2): error MSB4019: The imported project "C:\Microsoft.Cpp.Default.props" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.
+ #
+ # Many solutions proposed on SO, including:
+ # https://stackoverflow.com/questions/41695251/c-microsoft-cpp-default-props-was-not-found
+ # https://stackoverflow.com/questions/16092169/why-does-msbuild-look-in-c-for-microsoft-cpp-default-props-instead-of-c-progr
+ if not os.path.exists(self.bconf.vc_targets_path):
+ raise ValueError("VCTargetsPath does not exist: %s" % self.bconf.vc_targets_path)
+ b.add('SET VCTargetsPath=%s' % self.bconf.vc_targets_path)
+
+ # The msbuild.exe in path could be v4.0 from .net sdk, whereas the
+ # vctargetspath ends up referencing the msbuild from visual studio...
+ # Put the visual studio msbuild into the path first.
+ if self.bconf.bitness == 64:
+ msbuild_bin_path = os.path.join(self.bconf.msbuild_bin_path, 'amd64')
+ else:
+ msbuild_bin_path = self.bconf.msbuild_bin_path
+ b.add("set path=%s;%%path%%" % msbuild_bin_path)
+
+ # When performing 64-bit build, ucrtd.lib is not in library path for whatever reason. Sigh.
+ # Superseded by https://stackoverflow.com/questions/56145118/cmake-cannot-open-ucrtd-lib instructions below.
+ if self.bconf.bitness == 64 and False:
+ windows_sdk_lib_path = glob_first("c:\\Program Files (x86)\\Windows Kits\\10\\Lib\\*\\ucrt\\x64")
+ b.add('set lib=%s;%%lib%%' % windows_sdk_lib_path)
+
+ parts = [
+ '"%s"' % config.cmake_path,
+ # I don't know if this does anything, build type/config
+ # must be specified with --build option below.
+ '-DCMAKE_BUILD_TYPE=Release',
+ # This configures libnghttp2 only which is what we want.
+ # However, configure step still complains about all of the
+ # missing dependencies for nghttp2 server.
+ # And there is no indication whatsoever from configure step
+ # that this option is enabled, or that the missing
+ # dependency complaints can be ignored.
+ '-DENABLE_LIB_ONLY=1',
+ # This is required to get a static library built.
+ # However, even with this turned on there is still a DLL
+ # built - without an import library for it.
+ '-DENABLE_STATIC_LIB=1',
+ # And cmake ignores all visual studio environment variables
+ # and uses the newest compiler by default, which is great
+ # if one doesn't care what compiler their code is compiled with.
+ # https://stackoverflow.com/questions/6430251/what-is-the-default-generator-for-cmake-in-windows
+ '-G', '"%s"' % generator,
+ ]
+
+ # Cmake also couldn't care less about the bitness I have configured in the
+ # environment since it ignores the environment entirely.
+ # Educate it on the required bitness by hand.
+ # https://stackoverflow.com/questions/28350214/how-to-build-x86-and-or-x64-on-windows-from-command-line-with-cmake#28370892
+ #
+ # New strategy:
+ # https://cmake.org/cmake/help/v3.14/generator/Visual%20Studio%2014%202015.html
+ if self.bconf.bitness == 64 and False:
+ parts += ['-A', 'x64']
+
+ # And it does its own windows sdk selection, apparently, and botches it.
+ # https://stackoverflow.com/questions/56145118/cmake-cannot-open-ucrtd-lib
+ # TODO figure out which version is needed here, 8.1 or 10.0 or 10.0.10240.0
+ parts.append('-DCMAKE_SYSTEM_VERSION=8.1')
+
+ b.add('%s .' % ' '.join(parts))
+ b.add(' '.join([
+ '"%s"' % config.cmake_path,
+ '--build', '.',
+ # this is what produces a release build
+ '--config', 'Release',
+ # this builds the static library.
+ # without this option cmake configures itself to be capable
+ # of building a static library but sometimes builds a DLL
+ # and sometimes builds a static library
+ # depending on compiler in use (vc9/vc14) or, possibly,
+ # phase of the moon.
+ '--target', 'nghttp2_static',
+ ]))
+
+ # assemble dist
+ b.add('mkdir dist dist\\include dist\\include\\nghttp2 dist\\lib')
+ b.add('cp lib/Release/*.lib dist/lib')
+ b.add('cp lib/includes/nghttp2/*.h dist/include/nghttp2')
+ if self.bconf.vc_version == 'vc9':
+ # stdint.h
+ b.add('cp lib/includes/*.h dist/include')
+
+ # libcurl expects nghttp2_static.lib apparently, and depending on nghttp2 version/configuration(?)
+ # the library name is sometimes nghttp2.lib
+ if not os.path.exists('lib/Release/nghttp2_static.lib'):
+ shutil.copy('lib/Release/nghttp2.lib', 'lib/Release/nghttp2_static.lib')
diff --git a/winbuild/openssl.py b/winbuild/openssl.py
new file mode 100644
index 0000000..cc8bba4
--- /dev/null
+++ b/winbuild/openssl.py
@@ -0,0 +1,76 @@
+import os.path
+from .utils import *
+from .builder import *
+
+class OpensslBuilder(StandardBuilder):
+ def build(self):
+ # another openssl gem:
+ # nasm output is redirected to NUL which ends up creating a file named NUL.
+ # however being a reserved file name this file is not deletable by
+ # ordinary tools.
+ nul_file = "openssl-%s-%s\\NUL" % (self.bconf.openssl_version, self.bconf.vc_tag)
+ check_call(['rm', '-f', nul_file])
+ openssl_dir = self.standard_fetch_extract(
+ 'https://www.openssl.org/source/openssl-%(my_version)s.tar.gz')
+ with in_dir(openssl_dir):
+ with self.execute_batch() as b:
+ if self.bconf.openssl_version_tuple < (1, 1):
+ # openssl 1.0.2
+ b.add("patch -p0 < %s" %
+ require_file_exists(os.path.join(config.winbuild_patch_root, 'openssl-fix-crt-1.0.2.patch')))
+ elif self.bconf.openssl_version_tuple < (1, 1, 1):
+ # openssl 1.1.0
+ b.add("patch -p0 < %s" %
+ require_file_exists(os.path.join(config.winbuild_patch_root, 'openssl-fix-crt-1.1.0.patch')))
+ else:
+ # openssl 1.1.1
+ b.add("patch -p0 < %s" %
+ require_file_exists(os.path.join(config.winbuild_patch_root, 'openssl-fix-crt-1.1.1.patch')))
+ if self.bconf.bitness == 64:
+ target = 'VC-WIN64A'
+ batch_file = 'do_win64a'
+ else:
+ target = 'VC-WIN32'
+ batch_file = 'do_nasm'
+
+ # msysgit perl has trouble with backslashes used in
+ # win64 assembly things in openssl 1.0.2
+ # and in x86 assembly as well in openssl 1.1.0;
+ # use ActiveState Perl
+ if not os.path.exists(config.activestate_perl_bin_path):
+ raise ValueError('activestate_perl_bin_path refers to a nonexisting path')
+ if not os.path.exists(os.path.join(config.activestate_perl_bin_path, 'perl.exe')):
+ raise ValueError('No perl binary in activestate_perl_bin_path')
+ b.add("set path=%s;%%path%%" % config.activestate_perl_bin_path)
+ b.add("perl -v")
+
+ openssl_prefix = os.path.join(os.path.realpath('.'), 'build')
+ # Do not want compression:
+ # https://en.wikipedia.org/wiki/CRIME
+ extras = ['no-comp', 'no-unit-test', 'no-tests', 'no-external-tests']
+ if config.openssl_version_tuple >= (1, 1):
+ # openssl 1.1.0
+ # in 1.1.0 the static/shared selection is handled by
+ # invoking the right makefile
+ extras += ['no-shared']
+
+ # looks like openssl 1.1.0c does not derive
+ # --openssldir from --prefix, like its Configure claims,
+ # and like 1.0.2 does; provide a relative openssl dir
+ # manually
+ extras += ['--openssldir=ssl']
+ b.add("perl Configure %s %s --prefix=%s" % (target, ' '.join(extras), openssl_prefix))
+
+ if config.openssl_version_tuple < (1, 1):
+ # openssl 1.0.2
+ b.add("call ms\\%s" % batch_file)
+ b.add("nmake -f ms\\nt.mak")
+ b.add("nmake -f ms\\nt.mak install")
+ else:
+ # openssl 1.1.0
+ b.add("nmake")
+ b.add("nmake install")
+
+ # assemble dist
+ b.add('mkdir dist')
+ b.add('cp -r build/include build/lib dist')
diff --git a/winbuild/pycurl.py b/winbuild/pycurl.py
new file mode 100644
index 0000000..b87aeb1
--- /dev/null
+++ b/winbuild/pycurl.py
@@ -0,0 +1,119 @@
+import os.path, shutil, zipfile
+from .builder import *
+from .utils import *
+
+class PycurlBuilder(Builder):
+ def __init__(self, **kwargs):
+ self.python_release = kwargs.pop('python_release')
+ super(PycurlBuilder, self).__init__(**kwargs)
+ # vc_version is specified externally for bconf/BuildConfig
+ assert self.bconf.vc_version == PYTHON_VC_VERSIONS[self.python_release]
+
+ @property
+ def python_path(self):
+ if config.build_wheels:
+ python_path = os.path.join(config.archives_path, 'venv-%s-%s' % (self.python_release, self.bconf.bitness), 'scripts', 'python')
+ else:
+ python_path = PythonBinary(self.python_release, self.bconf.bitness).executable_path
+ return python_path
+
+ @property
+ def platform_indicator(self):
+ platform_indicators = {32: 'win32', 64: 'win-amd64'}
+ return platform_indicators[self.bconf.bitness]
+
+ def build(self, targets):
+ libcurl_builder = LibcurlBuilder(bconf=self.bconf)
+ libcurl_dir = os.path.join(os.path.abspath(libcurl_builder.output_dir_path), 'dist')
+ dll_paths = libcurl_builder.dll_paths
+ if self.bconf.use_zlib:
+ zlib_builder = ZlibBuilder(bconf=self.bconf)
+ dll_paths += zlib_builder.dll_paths
+ dll_paths = [os.path.abspath(dll_path) for dll_path in dll_paths]
+ with in_dir(os.path.join('pycurl-%s' % self.bconf.pycurl_version)):
+ dest_lib_path = 'build/lib.%s-%s' % (self.platform_indicator,
+ self.python_release)
+ # exists for building additional targets for the same python version
+ mkdir_p(dest_lib_path)
+ if self.use_dlls:
+ for dll_path in dll_paths:
+ shutil.copy(dll_path, dest_lib_path)
+ with self.execute_batch() as b:
+ b.add("%s setup.py docstrings" % (self.python_path,))
+ if self.use_dlls:
+ libcurl_arg = '--use-libcurl-dll'
+ else:
+ libcurl_arg = '--libcurl-lib-name=libcurl_a.lib'
+ if self.bconf.use_openssl:
+ libcurl_arg += ' --with-openssl'
+ if config.openssl_version_tuple >= (1, 1):
+ libcurl_arg += ' --openssl-lib-name=""'
+ openssl_builder = OpensslBuilder(bconf=self.bconf)
+ b.add("set include=%%include%%;%s" % openssl_builder.include_path)
+ b.add("set lib=%%lib%%;%s" % openssl_builder.lib_path)
+ #if build_wheels:
+ #b.add("call %s" % os.path.join('..', 'venv-%s-%s' % (self.python_release, self.bconf.bitness), 'Scripts', 'activate'))
+ if config.build_wheels:
+ targets = targets + ['bdist_wheel']
+ if config.libcurl_version_tuple >= (7, 60, 0):
+ # As of 7.60.0 libcurl does not include its dependencies into
+ # its static libraries.
+ # libcurl_a.lib in 7.59.0 is 30 mb.
+ # libcurl_a.lib in 7.60.0 is 2 mb.
+ # https://github.com/curl/curl/pull/2474 is most likely culprit.
+ # As a result we need to specify all of the libraries that
+ # libcurl depends on here, plus the library paths,
+ # plus even windows standard libraries for good measure.
+ if self.bconf.use_zlib:
+ zlib_builder = ZlibBuilder(bconf=self.bconf)
+ libcurl_arg += ' --link-arg=/LIBPATH:%s' % zlib_builder.lib_path
+ libcurl_arg += ' --link-arg=zlib.lib'
+ if self.bconf.use_openssl:
+ openssl_builder = OpensslBuilder(bconf=self.bconf)
+ libcurl_arg += ' --link-arg=/LIBPATH:%s' % openssl_builder.lib_path
+ # openssl 1.1
+ libcurl_arg += ' --link-arg=libcrypto.lib'
+ libcurl_arg += ' --link-arg=libssl.lib'
+ libcurl_arg += ' --link-arg=crypt32.lib'
+ libcurl_arg += ' --link-arg=advapi32.lib'
+ if self.bconf.use_cares:
+ cares_builder = CaresBuilder(bconf=self.bconf)
+ libcurl_arg += ' --link-arg=/LIBPATH:%s' % cares_builder.lib_path
+ libcurl_arg += ' --link-arg=libcares.lib'
+ if self.bconf.use_libssh2:
+ libssh2_builder = Libssh2Builder(bconf=self.bconf)
+ libcurl_arg += ' --link-arg=/LIBPATH:%s' % libssh2_builder.lib_path
+ libcurl_arg += ' --link-arg=libssh2.lib'
+ if self.bconf.use_nghttp2:
+ nghttp2_builder = Nghttp2Builder(bconf=self.bconf)
+ libcurl_arg += ' --link-arg=/LIBPATH:%s' % nghttp2_builder.lib_path
+ libcurl_arg += ' --link-arg=nghttp2.lib'
+ if self.bconf.vc_version == 'vc9':
+ # this is for normaliz.lib
+ libcurl_builder = LibcurlBuilder(bconf=self.bconf)
+ libcurl_arg += ' --link-arg=/LIBPATH:%s' % libcurl_builder.lib_path
+ # We always use normaliz.lib, but it may come from
+ # "standard" msvc location or from libcurl's lib dir for msvc9
+ libcurl_arg += ' --link-arg=normaliz.lib'
+ libcurl_arg += ' --link-arg=user32.lib'
+ b.add("%s setup.py %s --curl-dir=%s %s" % (
+ self.python_path, ' '.join(targets), libcurl_dir, libcurl_arg))
+ if 'bdist' in targets:
+ zip_basename_orig = 'pycurl-%s.%s.zip' % (
+ self.bconf.pycurl_version, self.platform_indicator)
+ zip_basename_new = 'pycurl-%s.%s-py%s.zip' % (
+ self.bconf.pycurl_version, self.platform_indicator, self.python_release)
+ with zipfile.ZipFile('dist/%s' % zip_basename_orig, 'r') as src_zip:
+ with zipfile.ZipFile('dist/%s' % zip_basename_new, 'w') as dest_zip:
+ for name in src_zip.namelist():
+ parts = name.split('/')
+ while True:
+ popped = parts.pop(0)
+ if popped == 'python%s' % self.python_release.dotless or popped.startswith('venv-'):
+ break
+ assert len(parts) > 0
+ new_name = '/'.join(parts)
+ print('Recompressing %s -> %s' % (name, new_name))
+
+ member = src_zip.open(name)
+ dest_zip.writestr(new_name, member.read(), zipfile.ZIP_DEFLATED)
diff --git a/winbuild/ssh.py b/winbuild/ssh.py
new file mode 100644
index 0000000..64d46e6
--- /dev/null
+++ b/winbuild/ssh.py
@@ -0,0 +1,40 @@
+from .utils import *
+from .builder import *
+
+class Libssh2Builder(StandardBuilder):
+ def build(self):
+ libssh2_dir = self.standard_fetch_extract(
+ 'http://www.libssh2.org/download/libssh2-%(my_version)s.tar.gz')
+ with in_dir(libssh2_dir):
+ with self.execute_batch() as b:
+ if self.bconf.libssh2_version_tuple < (1, 8, 0) and self.bconf.vc_version == 'vc14':
+ b.add("patch -p0 < %s" %
+ require_file_exists(os.path.join(config.winbuild_patch_root, 'libssh2-vs2015.patch')))
+ zlib_builder = ZlibBuilder(bconf=self.bconf)
+ openssl_builder = OpensslBuilder(bconf=self.bconf)
+ vars = '''
+OPENSSLINC=%(openssl_include_path)s
+OPENSSLLIB=%(openssl_lib_path)s
+ZLIBINC=%(zlib_include_path)s
+ZLIBLIB=%(zlib_lib_path)s
+WITH_ZLIB=1
+BUILD_STATIC_LIB=1
+ ''' % dict(
+ openssl_include_path=openssl_builder.include_path,
+ openssl_lib_path=openssl_builder.lib_path,
+ zlib_include_path=zlib_builder.include_path,
+ zlib_lib_path=zlib_builder.lib_path,
+ )
+ with open('win32/config.mk', 'r+') as cf:
+ contents = cf.read()
+ cf.seek(0)
+ cf.write(vars)
+ cf.write(contents)
+ b.add("nmake -f NMakefile")
+ # libcurl loves its _a suffixes on static library names
+ b.add("cp Release\\src\\libssh2.lib Release\\src\\libssh2_a.lib")
+
+ # assemble dist
+ b.add('mkdir dist dist\\include dist\\lib')
+ b.add('cp Release/src/*.lib dist/lib')
+ b.add('cp -r include dist')
diff --git a/winbuild/utils.py b/winbuild/utils.py
index 8f1dfde..ec9d31e 100644
--- a/winbuild/utils.py
+++ b/winbuild/utils.py
@@ -98,3 +98,17 @@ def glob_first(pattern, selector=None):
else:
return paths[0]
raise Exception("Not found: %s" % pattern)
+
+@contextlib.contextmanager
+def in_dir(dir):
+ old_cwd = os.getcwd()
+ try:
+ os.chdir(dir)
+ yield
+ finally:
+ os.chdir(old_cwd)
+
+def untar(basename):
+ if os.path.exists(basename):
+ shutil.rmtree(basename)
+ check_call([config.tar_path, 'xf', '%s.tar.gz' % basename])
diff --git a/winbuild/zlib.py b/winbuild/zlib.py
new file mode 100644
index 0000000..299a4c3
--- /dev/null
+++ b/winbuild/zlib.py
@@ -0,0 +1,25 @@
+import os.path
+from .utils import *
+from .builder import *
+
+class ZlibBuilder(StandardBuilder):
+ def build(self):
+ zlib_dir = self.standard_fetch_extract(
+ 'http://downloads.sourceforge.net/project/libpng/zlib/%(my_version)s/zlib-%(my_version)s.tar.gz')
+ with in_dir(zlib_dir):
+ with self.execute_batch() as b:
+ b.add("nmake /f win32/Makefile.msc")
+ # libcurl loves its _a suffixes on static library names
+ b.add("cp zlib.lib zlib_a.lib")
+
+ # assemble dist
+ b.add('mkdir dist dist\\include dist\\lib dist\\bin')
+ b.add('cp *.lib *.exp dist/lib')
+ b.add('cp *.dll dist/bin')
+ b.add('cp *.h dist/include')
+
+ @property
+ def dll_paths(self):
+ return [
+ os.path.join(self.bin_path, 'zlib1.dll'),
+ ]