| # Copyright 2020 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # ============================================================================== |
| """Setup for Tink package with pip.""" |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| from distutils import spawn |
| import glob |
| import os |
| import posixpath |
| import re |
| import shutil |
| import subprocess |
| import sys |
| |
| import setuptools |
| from setuptools.command import build_ext |
| |
| |
| here = os.path.dirname(os.path.abspath(__file__)) |
| |
| |
| def _get_tink_version(): |
| """Parses the version number from VERSION file.""" |
| with open(os.path.join(here, 'VERSION')) as f: |
| try: |
| version_line = next( |
| line for line in f if line.startswith('TINK_VERSION_LABEL')) |
| except StopIteration: |
| raise ValueError('Version not defined in python/VERSION') |
| else: |
| return version_line.split(' = ')[-1].strip('\n \'"') |
| |
| # Check Bazel enviroment and set executable |
| 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): |
| """Invokes the Protocol Compiler to generate a _pb2.py.""" |
| |
| output = source.replace('.proto', '_pb2.py') |
| |
| if (not os.path.exists(output) or |
| (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'): |
| _generate_proto(proto_file) |
| |
| |
| def _parse_requirements(path): |
| with open(os.path.join(here, path)) as f: |
| return [ |
| line.rstrip() |
| for line in f |
| if not (line.isspace() or line.startswith('#')) |
| ] |
| |
| |
| def _patch_workspace(workspace_content): |
| """Change inclusion of the other WORKSPACEs in Tink to be absolute. |
| |
| Setuptools builds in a temporary folder, therefore the relative paths can not |
| be resolved. Instead we use the http_archives during the build. |
| |
| Args: |
| workspace_content: The original tink/python WORKSPACE. |
| Returns: |
| 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: |
| base_path = os.environ['TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH'] |
| workspace_content = re.sub(r'(?<="tink_base",\n path = ").*(?=\n)', |
| base_path + '", # Modified by setup.py', |
| workspace_content) |
| workspace_content = re.sub(r'(?<="tink_cc",\n path = ").*(?=\n)', |
| base_path + '/cc' + '", # Modified by setup.py', |
| workspace_content) |
| else: |
| # If not base is specified use the latest version from GitHub |
| # Add http_archive load |
| workspace_lines = workspace_content.split('\n') |
| http_archive_load = ('load("@bazel_tools//tools/build_defs/repo:http.bzl", ' |
| '"http_archive")') |
| workspace_content = '\n'.join([workspace_lines[0], http_archive_load] + |
| workspace_lines[1:]) |
| |
| base = ('local_repository(\n' |
| ' name = "tink_base",\n' |
| ' path = "..",\n' |
| ')\n') |
| |
| cc = ('local_repository(\n' |
| ' name = "tink_cc",\n' |
| ' path = "../cc",\n' |
| ')\n') |
| |
| base_patched = ( |
| '# Modified by setup.py\n' |
| 'http_archive(\n' |
| ' name = "tink_base",\n' |
| ' urls = ["https://github.com/google/tink/archive/master.zip"],\n' |
| ' strip_prefix = "tink-master/",\n' |
| ')\n') |
| |
| cc_patched = ( |
| '# Modified by setup.py\n' |
| 'http_archive(\n' |
| ' name = "tink_cc",\n' |
| ' urls = ["https://github.com/google/tink/archive/master.zip"],\n' |
| ' strip_prefix = "tink-master/cc",\n' |
| ')\n') |
| |
| workspace_content = workspace_content.replace(base, base_patched) |
| workspace_content = workspace_content.replace(cc, cc_patched) |
| return workspace_content |
| |
| |
| class BazelExtension(setuptools.Extension): |
| """A C/C++ extension that is defined as a Bazel BUILD target.""" |
| |
| def __init__(self, bazel_target, target_name=''): |
| self.bazel_target = bazel_target |
| self.relpath, self.target_name = ( |
| posixpath.relpath(bazel_target, '//').split(':')) |
| if target_name: |
| self.target_name = target_name |
| ext_name = os.path.join( |
| self.relpath.replace(posixpath.sep, os.path.sep), self.target_name) |
| setuptools.Extension.__init__(self, ext_name, sources=[]) |
| |
| |
| class BuildBazelExtension(build_ext.build_ext): |
| """A command that runs Bazel to build a C/C++ extension.""" |
| |
| def run(self): |
| for ext in self.extensions: |
| self.bazel_build(ext) |
| build_ext.build_ext.run(self) |
| |
| def bazel_build(self, ext): |
| # Change WORKSPACE to include tink_base and tink_cc from an archive |
| with open('WORKSPACE', 'r') as f: |
| workspace_contents = f.read() |
| with open('WORKSPACE', 'w') as f: |
| f.write(_patch_workspace(workspace_contents)) |
| |
| if not os.path.exists(self.build_temp): |
| os.makedirs(self.build_temp) |
| |
| bazel_argv = [ |
| bazel, 'build', ext.bazel_target, |
| '--compilation_mode=' + ('dbg' if self.debug else 'opt'), |
| '--incompatible_linkopts_to_linklibs' |
| # TODO(https://github.com/bazelbuild/bazel/issues/9254): Remove linkopts |
| # flag when issue is fixed. |
| ] |
| self.spawn(bazel_argv) |
| ext_bazel_bin_path = os.path.join('bazel-bin', ext.relpath, |
| ext.target_name + '.so') |
| ext_dest_path = self.get_ext_fullpath(ext.name) |
| ext_dest_dir = os.path.dirname(ext_dest_path) |
| if not os.path.exists(ext_dest_dir): |
| os.makedirs(ext_dest_dir) |
| shutil.copyfile(ext_bazel_bin_path, ext_dest_path) |
| |
| |
| setuptools.setup( |
| name='tink', |
| version=_get_tink_version(), |
| url='https://github.com/google/tink', |
| description='A multi-language, cross-platform library that provides ' |
| 'cryptographic APIs that are secure, easy to use correctly, ' |
| 'and hard(er) to misuse.', |
| author='Tink Developers', |
| author_email='tink-users@googlegroups.com', |
| long_description=open('README.md').read(), |
| long_description_content_type='text/markdown', |
| # Contained modules and scripts. |
| packages=setuptools.find_packages(), |
| install_requires=_parse_requirements('requirements.txt'), |
| cmdclass=dict(build_ext=BuildBazelExtension), |
| ext_modules=[ |
| BazelExtension('//tink/cc/pybind:tink_bindings'), |
| ], |
| zip_safe=False, |
| # PyPI package information. |
| classifiers=[ |
| 'Programming Language :: Python :: 3.7', |
| 'Programming Language :: Python :: 3.8', |
| 'Topic :: Software Development :: Libraries', |
| ], |
| license='Apache 2.0', |
| keywords='tink cryptography', |
| ) |