https://github.com/google/tink
Raw File
Tip revision: 50150573bd1d6f05e818cc3706cfba3fe9ed490a authored by thaidn on 30 April 2020, 23:04:57 UTC
Fixing a typo in KMS Envelope AEAD.
Tip revision: 5015057
setup.py
# 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

import glob
import os
import posixpath
import shutil
import subprocess
import sys

from distutils import spawn
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.
  """
  # 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:])

  # Replace local with http archives
  base = ('local_repository(\n'
          '    name = "tink_base",\n'
          '    path = "..",\n'
          ')\n')

  cc = ('local_repository(\n'
        '    name = "tink_cc",\n'
        '    path = "../cc",\n'
        ')\n')

  base_http = (
      'http_archive(\n'
      '    name = "tink_base",\n'
      '    urls = ["https://github.com/google/tink/archive/master.zip"],\n'
      '    strip_prefix = "tink-master/",\n'
      ')\n')

  cc_http = (
      '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_http)
  workspace_content = workspace_content.replace(cc, cc_http)
  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'),
    ]
    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)

# TODO(kste): Parse these targets from the pybind folder.
bazel_modules = [
    BazelExtension('//tink/cc/pybind:import_helper', 'libimport_helper'),
    BazelExtension('//tink/cc/pybind:cc_gcp_kms_client'),
    BazelExtension('//tink/cc/pybind:cc_key_manager'),
    BazelExtension('//tink/cc/pybind:cc_tink_config'),
    BazelExtension('//tink/cc/pybind:python_file_object_adapter'),
    BazelExtension('//tink/cc/pybind:output_stream_adapter'),
    BazelExtension('//tink/cc/pybind:cc_streaming_aead_wrappers'),
    BazelExtension('//tink/cc/pybind:status_utils', 'libstatus_utils'),
    BazelExtension('//tink/cc/pybind:status'),
    BazelExtension('//tink/cc/pybind:status_example'),
    BazelExtension('//tink/cc/pybind:aead'),
    BazelExtension('//tink/cc/pybind:deterministic_aead'),
    BazelExtension('//tink/cc/pybind:streaming_aead'),
    BazelExtension('//tink/cc/pybind:hybrid_decrypt'),
    BazelExtension('//tink/cc/pybind:hybrid_encrypt'),
    BazelExtension('//tink/cc/pybind:mac'),
    BazelExtension('//tink/cc/pybind:public_key_sign'),
    BazelExtension('//tink/cc/pybind:public_key_verify'),
]

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=bazel_modules,
    zip_safe=False,
    # PyPI package information.
    classifiers=[
        'Programming Language :: Python :: 3.7',
        'Topic :: Software Development :: Libraries',
    ],
    license='Apache 2.0',
    keywords='tink cryptography',
)
back to top