setup.py (tink-1.6.0) | : | setup.py (tink-1.6.1) | ||
---|---|---|---|---|
skipping to change at line 27 | skipping to change at line 27 | |||
from __future__ import division | from __future__ import division | |||
from __future__ import print_function | from __future__ import print_function | |||
from distutils import spawn | from distutils import spawn | |||
import glob | import glob | |||
import os | import os | |||
import posixpath | import posixpath | |||
import re | import re | |||
import shutil | import shutil | |||
import subprocess | import subprocess | |||
import sys | import textwrap | |||
import setuptools | import setuptools | |||
from setuptools.command import build_ext | from setuptools.command import build_ext | |||
here = os.path.dirname(os.path.abspath(__file__)) | _PROJECT_BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |||
def _get_tink_version(): | def _get_tink_version(): | |||
"""Parses the version number from VERSION file.""" | """Parses the version number from VERSION file.""" | |||
with open(os.path.join(here, 'VERSION')) as f: | with open(os.path.join(_PROJECT_BASE_DIR, 'VERSION')) as f: | |||
try: | try: | |||
version_line = next( | version_line = next( | |||
line for line in f if line.startswith('TINK_VERSION_LABEL')) | line for line in f if line.startswith('TINK_VERSION_LABEL')) | |||
except StopIteration: | except StopIteration: | |||
raise ValueError('Version not defined in python/VERSION') | raise ValueError( | |||
'Version not defined in {}/VERSION'.format(_PROJECT_BASE_DIR)) | ||||
else: | else: | |||
return version_line.split(' = ')[-1].strip('\n \'"') | return version_line.split(' = ')[-1].strip('\n \'"') | |||
# Check Bazel enviroment and set executable | _TINK_VERSION = _get_tink_version() | |||
if spawn.find_executable('bazelisk'): | ||||
bazel = 'bazelisk' | ||||
elif spawn.find_executable('bazel'): | ||||
bazel = 'bazel' | ||||
else: | ||||
sys.stderr.write('Could not find bazel executable. Please install bazel to' | ||||
'compile the Tink Python package.') | ||||
sys.exit(-1) | ||||
# Find the Protocol Compiler. | ||||
if 'PROTOC' in os.environ and os.path.exists(os.environ['PROTOC']): | ||||
protoc = os.environ['PROTOC'] | ||||
else: | ||||
protoc = spawn.find_executable('protoc') | ||||
def _generate_proto(source): | def _get_bazel_command(): | |||
"""Finds the bazel command.""" | ||||
if spawn.find_executable('bazelisk'): | ||||
return 'bazelisk' | ||||
elif spawn.find_executable('bazel'): | ||||
return 'bazel' | ||||
raise FileNotFoundError('Could not find bazel executable. Please install ' | ||||
'bazel to compile the Tink Python package.') | ||||
def _get_protoc_command(): | ||||
"""Finds the protoc command.""" | ||||
if 'PROTOC' in os.environ and os.path.exists(os.environ['PROTOC']): | ||||
return os.environ['PROTOC'] | ||||
else: | ||||
return spawn.find_executable('protoc') | ||||
raise FileNotFoundError('Could not find protoc executable. Please install ' | ||||
'protoc to compile the Tink Python package.') | ||||
def _generate_proto(protoc, source): | ||||
"""Invokes the Protocol Compiler to generate a _pb2.py.""" | """Invokes the Protocol Compiler to generate a _pb2.py.""" | |||
output = source.replace('.proto', '_pb2.py') | if not os.path.exists(source): | |||
raise FileNotFoundError('Cannot find required file: {}'.format(source)) | ||||
if (not os.path.exists(output) or | output = source.replace('.proto', '_pb2.py') | |||
(os.path.exists(source) and | ||||
os.path.getmtime(source) > os.path.getmtime(output))): | ||||
print('Generating %s...' % output) | ||||
if not os.path.exists(source): | ||||
sys.stderr.write('Cannot find required file: %s\n' % source) | ||||
sys.exit(-1) | ||||
if protoc is None: | ||||
sys.stderr.write( | ||||
'protoc is not installed nor found in ../src. Please compile it ' | ||||
'or install the binary package.\n') | ||||
sys.exit(-1) | ||||
protoc_command = [protoc, '-I.', '--python_out=.', source] | ||||
if subprocess.call(protoc_command) != 0: | ||||
sys.exit(-1) | ||||
for proto_file in glob.glob('tink/proto/*.proto'): | if (os.path.exists(output) and | |||
_generate_proto(proto_file) | os.path.getmtime(source) < os.path.getmtime(output)): | |||
# No need to regenerate if output is newer than source. | ||||
return | ||||
print('Generating {}...'.format(output)) | ||||
protoc_args = [protoc, '-I.', '--python_out=.', source] | ||||
subprocess.run(args=protoc_args, check=True) | ||||
def _parse_requirements(path): | def _parse_requirements(filename): | |||
with open(os.path.join(here, path)) as f: | with open(os.path.join(_PROJECT_BASE_DIR, filename)) as f: | |||
return [ | return [ | |||
line.rstrip() | line.rstrip() | |||
for line in f | for line in f | |||
if not (line.isspace() or line.startswith('#')) | if not (line.isspace() or line.startswith('#')) | |||
] | ] | |||
def _patch_workspace(workspace_content): | def _patch_workspace(workspace_content): | |||
"""Change inclusion of the other WORKSPACEs in Tink to be absolute. | """Update the Bazel workspace with valid repository references. | |||
Setuptools builds in a temporary folder, therefore the relative paths can not | setuptools builds in a temporary folder which breaks the relative paths | |||
be resolved. Instead we use the http_archives during the build. | defined in the Bazel workspace. By default, the local_repository() rules will | |||
be replaced with http_archive() rules which contain URLs that point to an | ||||
archive of the Tink GitHub repository as of the latest commit as of the master | ||||
branch. | ||||
This behavior can be modified via the following environment variables, in | ||||
order of precedence: | ||||
* TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH | ||||
Instead of using http_archive() rules, update the local_repository() | ||||
rules with the specified alternative local path. This allows for | ||||
building using a local copy of the project (e.g. for testing). | ||||
* TINK_PYTHON_SETUPTOOLS_TAGGED_VERSION | ||||
Instead of providing a URL of an archive of the current master branch, | ||||
instead link to an archive that correspond with the tagged version (e.g. | ||||
for creating release artifacts). | ||||
Args: | Args: | |||
workspace_content: The original tink/python WORKSPACE. | workspace_content: The original tink/python WORKSPACE. | |||
Returns: | Returns: | |||
The workspace_content using http_archive for tink_base and tink_cc. | The workspace_content using http_archive for tink_base and tink_cc. | |||
""" | """ | |||
# This is run by pip from a temporary folder which breaks the WORKSPACE paths. | ||||
# This replaces the paths with the latest http_archive. | ||||
# In order to override this with a local WORKSPACE use the | ||||
# TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH environment variable. | ||||
if 'TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH' in os.environ: | if 'TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH' in os.environ: | |||
base_path = os.environ['TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH'] | base_path = os.environ['TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH'] | |||
workspace_content = re.sub(r'(?<="tink_base",\n path = ").*(?=\n)', | return _patch_with_local_path(workspace_content, base_path) | |||
base_path + '", # Modified by setup.py', | ||||
workspace_content) | if 'TINK_PYTHON_SETUPTOOLS_TAGGED_VERSION' in os.eviron: | |||
workspace_content = re.sub(r'(?<="tink_cc",\n path = ").*(?=\n)', | archive_filename = 'v{}.zip'.format(_TINK_VERSION) | |||
base_path + '/cc' + '", # Modified by setup.py', | return _patch_with_http_archive(workspace_content, archive_filename) | |||
workspace_content) | ||||
else: | return _patch_with_http_archive(workspace_content, 'master.zip') | |||
# If not base is specified use the latest version from GitHub | ||||
# Add http_archive load | def _patch_with_local_path(workspace_content, base_path): | |||
workspace_lines = workspace_content.split('\n') | """Replaces the base paths in the local_repository() rules.""" | |||
http_archive_load = ('load("@bazel_tools//tools/build_defs/repo:http.bzl", ' | ||||
'"http_archive")') | workspace_content = re.sub(r'(?<="tink_base",\n path = ").*(?=\n)', | |||
workspace_content = '\n'.join([workspace_lines[0], http_archive_load] + | base_path + '", # Modified by setup.py', | |||
workspace_lines[1:]) | workspace_content) | |||
workspace_content = re.sub(r'(?<="tink_cc",\n path = ").*(?=\n)', | ||||
base = ('local_repository(\n' | base_path + '/cc", # Modified by setup.py', | |||
' name = "tink_base",\n' | workspace_content) | |||
' path = "..",\n' | return workspace_content | |||
')\n') | ||||
def _patch_with_http_archive(workspace_content, filename): | ||||
cc = ('local_repository(\n' | """Replaces local_repository() rules with http_archive() rules.""" | |||
' name = "tink_cc",\n' | ||||
' path = "../cc",\n' | workspace_lines = workspace_content.split('\n') | |||
')\n') | http_archive_load = ('load("@bazel_tools//tools/build_defs/repo:http.bzl", ' | |||
'"http_archive")') | ||||
base_patched = ( | workspace_content = '\n'.join([workspace_lines[0], http_archive_load] + | |||
'# Modified by setup.py\n' | workspace_lines[1:]) | |||
'http_archive(\n' | ||||
' name = "tink_base",\n' | base = textwrap.dedent( | |||
' urls = ["https://github.com/google/tink/archive/master.zip"],\n' | '''\ | |||
' strip_prefix = "tink-master/",\n' | local_repository( | |||
')\n') | name = "tink_base", | |||
path = "..", | ||||
cc_patched = ( | ) | |||
'# Modified by setup.py\n' | ''') | |||
'http_archive(\n' | ||||
' name = "tink_cc",\n' | cc = textwrap.dedent( | |||
' urls = ["https://github.com/google/tink/archive/master.zip"],\n' | '''\ | |||
' strip_prefix = "tink-master/cc",\n' | local_repository( | |||
')\n') | name = "tink_cc", | |||
path = "../cc", | ||||
)) | ||||
''') | ||||
base_patched = textwrap.dedent( | ||||
'''\ | ||||
# Modified by setup.py | ||||
http_archive( | ||||
name = "tink_base", | ||||
urls = ["https://github.com/google/tink/archive/{}.zip"], | ||||
strip_prefix = "tink-master/", | ||||
)) | ||||
'''.format(filename)) | ||||
cc_patched = textwrap.dedent( | ||||
'''\ | ||||
# Modified by setup.py | ||||
http_archive( | ||||
name = "tink_cc", | ||||
urls = ["https://github.com/google/tink/archive/{}.zip"], | ||||
strip_prefix = "tink-master/cc", | ||||
)) | ||||
'''.format(filename)) | ||||
workspace_content = workspace_content.replace(base, base_patched) | workspace_content = workspace_content.replace(base, base_patched) | |||
workspace_content = workspace_content.replace(cc, cc_patched) | workspace_content = workspace_content.replace(cc, cc_patched) | |||
return workspace_content | return workspace_content | |||
class BazelExtension(setuptools.Extension): | class BazelExtension(setuptools.Extension): | |||
"""A C/C++ extension that is defined as a Bazel BUILD target.""" | """A C/C++ extension that is defined as a Bazel BUILD target.""" | |||
def __init__(self, bazel_target, target_name=''): | def __init__(self, bazel_target, target_name=''): | |||
self.bazel_target = bazel_target | self.bazel_target = bazel_target | |||
self.relpath, self.target_name = ( | self.relpath, self.target_name = ( | |||
posixpath.relpath(bazel_target, '//').split(':')) | posixpath.relpath(bazel_target, '//').split(':')) | |||
if target_name: | if target_name: | |||
self.target_name = target_name | self.target_name = target_name | |||
ext_name = os.path.join( | ext_name = os.path.join( | |||
self.relpath.replace(posixpath.sep, os.path.sep), self.target_name) | self.relpath.replace(posixpath.sep, os.path.sep), self.target_name) | |||
setuptools.Extension.__init__(self, ext_name, sources=[]) | setuptools.Extension.__init__(self, ext_name, sources=[]) | |||
class BuildBazelExtension(build_ext.build_ext): | class BuildBazelExtension(build_ext.build_ext): | |||
"""A command that runs Bazel to build a C/C++ extension.""" | """A command that runs Bazel to build a C/C++ extension.""" | |||
def __init__(self, dist): | ||||
super(BuildBazelExtension, self).__init__(dist) | ||||
self.bazel_command = _get_bazel_command() | ||||
def run(self): | def run(self): | |||
for ext in self.extensions: | for ext in self.extensions: | |||
self.bazel_build(ext) | self.bazel_build(ext) | |||
build_ext.build_ext.run(self) | build_ext.build_ext.run(self) | |||
def bazel_build(self, ext): | def bazel_build(self, ext): | |||
# Change WORKSPACE to include tink_base and tink_cc from an archive | # Change WORKSPACE to include tink_base and tink_cc from an archive | |||
with open('WORKSPACE', 'r') as f: | with open('WORKSPACE', 'r') as f: | |||
workspace_contents = f.read() | workspace_contents = f.read() | |||
with open('WORKSPACE', 'w') as f: | with open('WORKSPACE', 'w') as f: | |||
f.write(_patch_workspace(workspace_contents)) | f.write(_patch_workspace(workspace_contents)) | |||
if not os.path.exists(self.build_temp): | if not os.path.exists(self.build_temp): | |||
os.makedirs(self.build_temp) | os.makedirs(self.build_temp) | |||
# Ensure no artifacts from previous builds are reused (i.e. from builds | ||||
# using a different Python version). | ||||
bazel_clean_argv = [self.bazel_command, 'clean', '--expunge'] | ||||
self.spawn(bazel_clean_argv) | ||||
bazel_argv = [ | bazel_argv = [ | |||
bazel, 'build', ext.bazel_target, | self.bazel_command, 'build', ext.bazel_target, | |||
'--compilation_mode=' + ('dbg' if self.debug else 'opt'), | '--compilation_mode=' + ('dbg' if self.debug else 'opt'), | |||
'--incompatible_linkopts_to_linklibs' | '--incompatible_linkopts_to_linklibs' | |||
# TODO(https://github.com/bazelbuild/bazel/issues/9254): Remove linkopts | # TODO(https://github.com/bazelbuild/bazel/issues/9254): Remove linkopts | |||
# flag when issue is fixed. | # flag when issue is fixed. | |||
] | ] | |||
self.spawn(bazel_argv) | self.spawn(bazel_argv) | |||
ext_bazel_bin_path = os.path.join('bazel-bin', ext.relpath, | ext_bazel_bin_path = os.path.join('bazel-bin', ext.relpath, | |||
ext.target_name + '.so') | ext.target_name + '.so') | |||
ext_dest_path = self.get_ext_fullpath(ext.name) | ext_dest_path = self.get_ext_fullpath(ext.name) | |||
ext_dest_dir = os.path.dirname(ext_dest_path) | ext_dest_dir = os.path.dirname(ext_dest_path) | |||
if not os.path.exists(ext_dest_dir): | if not os.path.exists(ext_dest_dir): | |||
os.makedirs(ext_dest_dir) | os.makedirs(ext_dest_dir) | |||
shutil.copyfile(ext_bazel_bin_path, ext_dest_path) | shutil.copyfile(ext_bazel_bin_path, ext_dest_path) | |||
setuptools.setup( | def main(): | |||
name='tink', | # Generate compiled protocol buffers. | |||
version=_get_tink_version(), | protoc_command = _get_protoc_command() | |||
url='https://github.com/google/tink', | for proto_file in glob.glob('tink/proto/*.proto'): | |||
description='A multi-language, cross-platform library that provides ' | _generate_proto(protoc_command, proto_file) | |||
'cryptographic APIs that are secure, easy to use correctly, ' | ||||
'and hard(er) to misuse.', | setuptools.setup( | |||
author='Tink Developers', | name='tink', | |||
author_email='tink-users@googlegroups.com', | version=_TINK_VERSION, | |||
long_description=open('README.md').read(), | url='https://github.com/google/tink', | |||
long_description_content_type='text/markdown', | description='A multi-language, cross-platform library that provides ' | |||
# Contained modules and scripts. | 'cryptographic APIs that are secure, easy to use correctly, ' | |||
packages=setuptools.find_packages(), | 'and hard(er) to misuse.', | |||
install_requires=_parse_requirements('requirements.txt'), | author='Tink Developers', | |||
cmdclass=dict(build_ext=BuildBazelExtension), | author_email='tink-users@googlegroups.com', | |||
ext_modules=[ | long_description=open('README.md').read(), | |||
BazelExtension('//tink/cc/pybind:tink_bindings'), | long_description_content_type='text/markdown', | |||
], | # Contained modules and scripts. | |||
zip_safe=False, | packages=setuptools.find_packages(), | |||
# PyPI package information. | install_requires=_parse_requirements('requirements.txt'), | |||
classifiers=[ | cmdclass=dict(build_ext=BuildBazelExtension), | |||
'Programming Language :: Python :: 3.7', | ext_modules=[ | |||
'Programming Language :: Python :: 3.8', | BazelExtension('//tink/cc/pybind:tink_bindings'), | |||
'Topic :: Software Development :: Libraries', | ], | |||
], | zip_safe=False, | |||
license='Apache 2.0', | # PyPI package information. | |||
keywords='tink cryptography', | classifiers=[ | |||
) | 'Programming Language :: Python :: 3.7', | |||
'Programming Language :: Python :: 3.8', | ||||
'Programming Language :: Python :: 3.9', | ||||
'Topic :: Software Development :: Libraries', | ||||
], | ||||
license='Apache 2.0', | ||||
keywords='tink cryptography', | ||||
) | ||||
if __name__ == '__main__': | ||||
main() | ||||
End of changes. 19 change blocks. | ||||
93 lines changed or deleted | 132 lines changed or added |