From 1c7b2a3c4dd2326840e67220f34bbe9098a00ea8 Mon Sep 17 00:00:00 2001 From: winbuild Date: Tue, 28 Jan 2020 12:08:51 -0500 Subject: Reorganize winbuild --- winbuild/builder.py | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++ winbuild/cares.py | 27 +++++++++ winbuild/config.py | 108 ++++++++++++++++++++++++++++++++++ winbuild/curl.py | 105 +++++++++++++++++++++++++++++++++ winbuild/iconv.py | 11 ++++ winbuild/idn.py | 10 ++++ winbuild/nghttp.py | 121 ++++++++++++++++++++++++++++++++++++++ winbuild/openssl.py | 76 ++++++++++++++++++++++++ winbuild/pycurl.py | 119 +++++++++++++++++++++++++++++++++++++ winbuild/ssh.py | 40 +++++++++++++ winbuild/utils.py | 14 +++++ winbuild/zlib.py | 25 ++++++++ 12 files changed, 821 insertions(+) create mode 100644 winbuild/builder.py create mode 100644 winbuild/cares.py create mode 100644 winbuild/config.py create mode 100644 winbuild/curl.py create mode 100644 winbuild/iconv.py create mode 100644 winbuild/idn.py create mode 100644 winbuild/nghttp.py create mode 100644 winbuild/openssl.py create mode 100644 winbuild/pycurl.py create mode 100644 winbuild/ssh.py create mode 100644 winbuild/zlib.py (limited to 'winbuild') 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 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'), + ] -- cgit v1.2.1