xref: /aosp_15_r20/external/tink/python/setup.py (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1*e7b1675dSTing-Kang Chang# Copyright 2020 Google LLC
2*e7b1675dSTing-Kang Chang#
3*e7b1675dSTing-Kang Chang# Licensed under the Apache License, Version 2.0 (the "License");
4*e7b1675dSTing-Kang Chang# you may not use this file except in compliance with the License.
5*e7b1675dSTing-Kang Chang# You may obtain a copy of the License at
6*e7b1675dSTing-Kang Chang#
7*e7b1675dSTing-Kang Chang#     http://www.apache.org/licenses/LICENSE-2.0
8*e7b1675dSTing-Kang Chang#
9*e7b1675dSTing-Kang Chang# Unless required by applicable law or agreed to in writing, software
10*e7b1675dSTing-Kang Chang# distributed under the License is distributed on an "AS IS" BASIS,
11*e7b1675dSTing-Kang Chang# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*e7b1675dSTing-Kang Chang# See the License for the specific language governing permissions and
13*e7b1675dSTing-Kang Chang# limitations under the License.
14*e7b1675dSTing-Kang Chang# ==============================================================================
15*e7b1675dSTing-Kang Chang"""Setup for Tink package with pip."""
16*e7b1675dSTing-Kang Changfrom __future__ import absolute_import
17*e7b1675dSTing-Kang Changfrom __future__ import division
18*e7b1675dSTing-Kang Changfrom __future__ import print_function
19*e7b1675dSTing-Kang Chang
20*e7b1675dSTing-Kang Changimport glob
21*e7b1675dSTing-Kang Changimport os
22*e7b1675dSTing-Kang Changimport posixpath
23*e7b1675dSTing-Kang Changimport re
24*e7b1675dSTing-Kang Changimport shutil
25*e7b1675dSTing-Kang Changimport subprocess
26*e7b1675dSTing-Kang Changimport textwrap
27*e7b1675dSTing-Kang Chang
28*e7b1675dSTing-Kang Changimport setuptools
29*e7b1675dSTing-Kang Changfrom setuptools.command import build_ext
30*e7b1675dSTing-Kang Changfrom setuptools.command import sdist
31*e7b1675dSTing-Kang Chang
32*e7b1675dSTing-Kang Chang
33*e7b1675dSTing-Kang Chang_PROJECT_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
34*e7b1675dSTing-Kang Chang
35*e7b1675dSTing-Kang Chang
36*e7b1675dSTing-Kang Changdef _get_tink_version():
37*e7b1675dSTing-Kang Chang  """Parses the version number from VERSION file."""
38*e7b1675dSTing-Kang Chang  with open(os.path.join(_PROJECT_BASE_DIR, 'VERSION')) as f:
39*e7b1675dSTing-Kang Chang    try:
40*e7b1675dSTing-Kang Chang      version_line = next(
41*e7b1675dSTing-Kang Chang          line for line in f if line.startswith('TINK_VERSION_LABEL'))
42*e7b1675dSTing-Kang Chang    except StopIteration:
43*e7b1675dSTing-Kang Chang      raise ValueError(
44*e7b1675dSTing-Kang Chang          'Version not defined in {}/VERSION'.format(_PROJECT_BASE_DIR))
45*e7b1675dSTing-Kang Chang    else:
46*e7b1675dSTing-Kang Chang      return version_line.split(' = ')[-1].strip('\n \'"')
47*e7b1675dSTing-Kang Chang
48*e7b1675dSTing-Kang Chang
49*e7b1675dSTing-Kang Chang_TINK_VERSION = _get_tink_version()
50*e7b1675dSTing-Kang Chang
51*e7b1675dSTing-Kang Chang
52*e7b1675dSTing-Kang Changdef _get_bazel_command():
53*e7b1675dSTing-Kang Chang  """Finds the bazel command."""
54*e7b1675dSTing-Kang Chang  if shutil.which('bazelisk'):
55*e7b1675dSTing-Kang Chang    return 'bazelisk'
56*e7b1675dSTing-Kang Chang  elif shutil.which('bazel'):
57*e7b1675dSTing-Kang Chang    return 'bazel'
58*e7b1675dSTing-Kang Chang  raise FileNotFoundError('Could not find bazel executable. Please install '
59*e7b1675dSTing-Kang Chang                          'bazel to compile the Tink Python package.')
60*e7b1675dSTing-Kang Chang
61*e7b1675dSTing-Kang Chang
62*e7b1675dSTing-Kang Changdef _get_protoc_command():
63*e7b1675dSTing-Kang Chang  """Finds the protoc command."""
64*e7b1675dSTing-Kang Chang  if 'PROTOC' in os.environ and os.path.exists(os.environ['PROTOC']):
65*e7b1675dSTing-Kang Chang    return os.environ['PROTOC']
66*e7b1675dSTing-Kang Chang  protoc_path = shutil.which('protoc')
67*e7b1675dSTing-Kang Chang  if protoc_path is None:
68*e7b1675dSTing-Kang Chang    raise FileNotFoundError('Could not find protoc executable. Please install '
69*e7b1675dSTing-Kang Chang                            'protoc to compile the Tink Python package.')
70*e7b1675dSTing-Kang Chang  return protoc_path
71*e7b1675dSTing-Kang Chang
72*e7b1675dSTing-Kang Chang
73*e7b1675dSTing-Kang Changdef _generate_proto(protoc, source):
74*e7b1675dSTing-Kang Chang  """Invokes the Protocol Compiler to generate a _pb2.py."""
75*e7b1675dSTing-Kang Chang
76*e7b1675dSTing-Kang Chang  if not os.path.exists(source):
77*e7b1675dSTing-Kang Chang    raise FileNotFoundError('Cannot find required file: {}'.format(source))
78*e7b1675dSTing-Kang Chang
79*e7b1675dSTing-Kang Chang  output = source.replace('.proto', '_pb2.py')
80*e7b1675dSTing-Kang Chang
81*e7b1675dSTing-Kang Chang  if (os.path.exists(output) and
82*e7b1675dSTing-Kang Chang      os.path.getmtime(source) < os.path.getmtime(output)):
83*e7b1675dSTing-Kang Chang    # No need to regenerate if output is newer than source.
84*e7b1675dSTing-Kang Chang    return
85*e7b1675dSTing-Kang Chang
86*e7b1675dSTing-Kang Chang  print('Generating {}...'.format(output))
87*e7b1675dSTing-Kang Chang  protoc_args = [protoc, '-I.', '--python_out=.', source]
88*e7b1675dSTing-Kang Chang  subprocess.run(args=protoc_args, check=True)
89*e7b1675dSTing-Kang Chang
90*e7b1675dSTing-Kang Chang
91*e7b1675dSTing-Kang Changdef _parse_requirements(filename):
92*e7b1675dSTing-Kang Chang  with open(os.path.join(_PROJECT_BASE_DIR, filename)) as f:
93*e7b1675dSTing-Kang Chang    return [
94*e7b1675dSTing-Kang Chang        line.rstrip()
95*e7b1675dSTing-Kang Chang        for line in f
96*e7b1675dSTing-Kang Chang        if not (line.isspace() or line.startswith('#'))
97*e7b1675dSTing-Kang Chang    ]
98*e7b1675dSTing-Kang Chang
99*e7b1675dSTing-Kang Chang
100*e7b1675dSTing-Kang Changdef _patch_workspace(workspace_file):
101*e7b1675dSTing-Kang Chang  """Update the Bazel workspace with valid repository references.
102*e7b1675dSTing-Kang Chang
103*e7b1675dSTing-Kang Chang  When installing the sdist, e.g., with `pip install tink --no-binary` or
104*e7b1675dSTing-Kang Chang  `python3 -m pip install -v path/to/sdist.tar.gz`, setuptools unpacks the
105*e7b1675dSTing-Kang Chang  sdist in a temporary folder that contains only the python/ folder, and then
106*e7b1675dSTing-Kang Chang  builds it. As a consequence, relative local_repository paths that are set by
107*e7b1675dSTing-Kang Chang  default in python/WORKSPACE don't exist (or worst, they may exist by
108*e7b1675dSTing-Kang Chang  chance!).
109*e7b1675dSTing-Kang Chang
110*e7b1675dSTing-Kang Chang  By default, the local_repository() rules will be replaced with http_archive()
111*e7b1675dSTing-Kang Chang  rules which contain URLs that point to an archive of the Tink GitHub
112*e7b1675dSTing-Kang Chang  repository as of the latest commit as of the master branch.
113*e7b1675dSTing-Kang Chang
114*e7b1675dSTing-Kang Chang  This behavior can be modified via the following environment variables, in
115*e7b1675dSTing-Kang Chang  order of precedence:
116*e7b1675dSTing-Kang Chang
117*e7b1675dSTing-Kang Chang    * TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH
118*e7b1675dSTing-Kang Chang        Instead of using http_archive() rules, update the local_repository()
119*e7b1675dSTing-Kang Chang        rules with the specified alternative local path. This allows for
120*e7b1675dSTing-Kang Chang        building using a local copy of the project (e.g. for testing).
121*e7b1675dSTing-Kang Chang
122*e7b1675dSTing-Kang Chang    * TINK_PYTHON_SETUPTOOLS_TAGGED_VERSION
123*e7b1675dSTing-Kang Chang        Instead of providing a URL of an archive of the current master branch,
124*e7b1675dSTing-Kang Chang        instead link to an archive that correspond with the tagged version (e.g.
125*e7b1675dSTing-Kang Chang        for creating release artifacts).
126*e7b1675dSTing-Kang Chang
127*e7b1675dSTing-Kang Chang  Args:
128*e7b1675dSTing-Kang Chang    workspace_file: The tink/python WORKSPACE.
129*e7b1675dSTing-Kang Chang  """
130*e7b1675dSTing-Kang Chang
131*e7b1675dSTing-Kang Chang  with open(workspace_file, 'r') as f:
132*e7b1675dSTing-Kang Chang    workspace_content = f.read()
133*e7b1675dSTing-Kang Chang
134*e7b1675dSTing-Kang Chang  if 'TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH' in os.environ:
135*e7b1675dSTing-Kang Chang    base_path = os.environ['TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH']
136*e7b1675dSTing-Kang Chang    workspace_content = _patch_with_local_path(workspace_content, base_path)
137*e7b1675dSTing-Kang Chang
138*e7b1675dSTing-Kang Chang  elif 'TINK_PYTHON_SETUPTOOLS_TAGGED_VERSION' in os.environ:
139*e7b1675dSTing-Kang Chang    tagged_version = os.environ['TINK_PYTHON_SETUPTOOLS_TAGGED_VERSION']
140*e7b1675dSTing-Kang Chang    archive_filename = 'v{}.zip'.format(tagged_version)
141*e7b1675dSTing-Kang Chang    archive_prefix = 'tink-{}'.format(tagged_version)
142*e7b1675dSTing-Kang Chang    workspace_content = _patch_with_http_archive(workspace_content,
143*e7b1675dSTing-Kang Chang                                                 archive_filename,
144*e7b1675dSTing-Kang Chang                                                 archive_prefix)
145*e7b1675dSTing-Kang Chang  else:
146*e7b1675dSTing-Kang Chang    workspace_content = _patch_with_http_archive(workspace_content,
147*e7b1675dSTing-Kang Chang                                                 'master.zip', 'tink-master')
148*e7b1675dSTing-Kang Chang
149*e7b1675dSTing-Kang Chang  with open(workspace_file, 'w') as f:
150*e7b1675dSTing-Kang Chang    f.write(workspace_content)
151*e7b1675dSTing-Kang Chang
152*e7b1675dSTing-Kang Chang
153*e7b1675dSTing-Kang Changdef _patch_with_local_path(workspace_content, base_path):
154*e7b1675dSTing-Kang Chang  """Replaces the base paths in the local_repository() rules."""
155*e7b1675dSTing-Kang Chang  return re.sub(
156*e7b1675dSTing-Kang Chang      r'(?<="tink_cc",\n    path = ").*(?=\n)',
157*e7b1675dSTing-Kang Chang      base_path + '/cc",  # Modified by setup.py',
158*e7b1675dSTing-Kang Chang      workspace_content,
159*e7b1675dSTing-Kang Chang  )
160*e7b1675dSTing-Kang Chang
161*e7b1675dSTing-Kang Chang
162*e7b1675dSTing-Kang Changdef _patch_with_http_archive(workspace_content, filename, prefix):
163*e7b1675dSTing-Kang Chang  """Replaces local_repository() rules with http_archive() rules."""
164*e7b1675dSTing-Kang Chang
165*e7b1675dSTing-Kang Chang  workspace_lines = workspace_content.split('\n')
166*e7b1675dSTing-Kang Chang  http_archive_load = (
167*e7b1675dSTing-Kang Chang      'load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")')
168*e7b1675dSTing-Kang Chang
169*e7b1675dSTing-Kang Chang  if http_archive_load not in workspace_content:
170*e7b1675dSTing-Kang Chang    workspace_content = '\n'.join([workspace_lines[0], http_archive_load] +
171*e7b1675dSTing-Kang Chang                                  workspace_lines[1:])
172*e7b1675dSTing-Kang Chang
173*e7b1675dSTing-Kang Chang  cc = textwrap.dedent(
174*e7b1675dSTing-Kang Chang      '''\
175*e7b1675dSTing-Kang Chang      local_repository(
176*e7b1675dSTing-Kang Chang          name = "tink_cc",
177*e7b1675dSTing-Kang Chang          path = "../cc",
178*e7b1675dSTing-Kang Chang      )
179*e7b1675dSTing-Kang Chang      ''')
180*e7b1675dSTing-Kang Chang
181*e7b1675dSTing-Kang Chang  cc_patched = textwrap.dedent(
182*e7b1675dSTing-Kang Chang      '''\
183*e7b1675dSTing-Kang Chang      # Modified by setup.py
184*e7b1675dSTing-Kang Chang      http_archive(
185*e7b1675dSTing-Kang Chang          name = "tink_cc",
186*e7b1675dSTing-Kang Chang          urls = ["https://github.com/google/tink/archive/{}"],
187*e7b1675dSTing-Kang Chang          strip_prefix = "{}/cc",
188*e7b1675dSTing-Kang Chang      )
189*e7b1675dSTing-Kang Chang      '''.format(filename, prefix))
190*e7b1675dSTing-Kang Chang
191*e7b1675dSTing-Kang Chang  return workspace_content.replace(cc, cc_patched)
192*e7b1675dSTing-Kang Chang
193*e7b1675dSTing-Kang Chang
194*e7b1675dSTing-Kang Changclass BazelExtension(setuptools.Extension):
195*e7b1675dSTing-Kang Chang  """A C/C++ extension that is defined as a Bazel BUILD target."""
196*e7b1675dSTing-Kang Chang
197*e7b1675dSTing-Kang Chang  def __init__(self, bazel_target, target_name=''):
198*e7b1675dSTing-Kang Chang    self.bazel_target = bazel_target
199*e7b1675dSTing-Kang Chang    self.relpath, self.target_name = (
200*e7b1675dSTing-Kang Chang        posixpath.relpath(bazel_target, '//').split(':'))
201*e7b1675dSTing-Kang Chang    if target_name:
202*e7b1675dSTing-Kang Chang      self.target_name = target_name
203*e7b1675dSTing-Kang Chang    ext_name = os.path.join(
204*e7b1675dSTing-Kang Chang        self.relpath.replace(posixpath.sep, os.path.sep), self.target_name)
205*e7b1675dSTing-Kang Chang    setuptools.Extension.__init__(self, ext_name, sources=[])
206*e7b1675dSTing-Kang Chang
207*e7b1675dSTing-Kang Chang
208*e7b1675dSTing-Kang Changclass BuildBazelExtension(build_ext.build_ext):
209*e7b1675dSTing-Kang Chang  """A command that runs Bazel to build a C/C++ extension."""
210*e7b1675dSTing-Kang Chang
211*e7b1675dSTing-Kang Chang  def __init__(self, dist):
212*e7b1675dSTing-Kang Chang    super(BuildBazelExtension, self).__init__(dist)
213*e7b1675dSTing-Kang Chang    self.bazel_command = _get_bazel_command()
214*e7b1675dSTing-Kang Chang
215*e7b1675dSTing-Kang Chang  def run(self):
216*e7b1675dSTing-Kang Chang    for ext in self.extensions:
217*e7b1675dSTing-Kang Chang      self.bazel_build(ext)
218*e7b1675dSTing-Kang Chang    build_ext.build_ext.run(self)
219*e7b1675dSTing-Kang Chang
220*e7b1675dSTing-Kang Chang  def bazel_build(self, ext):
221*e7b1675dSTing-Kang Chang    # Change WORKSPACE to include tink_cc from an archive
222*e7b1675dSTing-Kang Chang    _patch_workspace('WORKSPACE')
223*e7b1675dSTing-Kang Chang
224*e7b1675dSTing-Kang Chang    if not os.path.exists(self.build_temp):
225*e7b1675dSTing-Kang Chang      os.makedirs(self.build_temp)
226*e7b1675dSTing-Kang Chang
227*e7b1675dSTing-Kang Chang    # Ensure no artifacts from previous builds are reused (i.e. from builds
228*e7b1675dSTing-Kang Chang    # using a different Python version).
229*e7b1675dSTing-Kang Chang    bazel_clean_argv = [self.bazel_command, 'clean', '--expunge']
230*e7b1675dSTing-Kang Chang    self.spawn(bazel_clean_argv)
231*e7b1675dSTing-Kang Chang
232*e7b1675dSTing-Kang Chang    bazel_argv = [
233*e7b1675dSTing-Kang Chang        self.bazel_command, 'build', ext.bazel_target,
234*e7b1675dSTing-Kang Chang        '--compilation_mode=' + ('dbg' if self.debug else 'opt')
235*e7b1675dSTing-Kang Chang    ]
236*e7b1675dSTing-Kang Chang    self.spawn(bazel_argv)
237*e7b1675dSTing-Kang Chang    ext_bazel_bin_path = os.path.join('bazel-bin', ext.relpath,
238*e7b1675dSTing-Kang Chang                                      ext.target_name + '.so')
239*e7b1675dSTing-Kang Chang    ext_dest_path = self.get_ext_fullpath(ext.name)
240*e7b1675dSTing-Kang Chang    ext_dest_dir = os.path.dirname(ext_dest_path)
241*e7b1675dSTing-Kang Chang    if not os.path.exists(ext_dest_dir):
242*e7b1675dSTing-Kang Chang      os.makedirs(ext_dest_dir)
243*e7b1675dSTing-Kang Chang    shutil.copyfile(ext_bazel_bin_path, ext_dest_path)
244*e7b1675dSTing-Kang Chang
245*e7b1675dSTing-Kang Chang
246*e7b1675dSTing-Kang Changclass SdistCmd(sdist.sdist):
247*e7b1675dSTing-Kang Chang  """A command that patches the workspace before creating an sdist."""
248*e7b1675dSTing-Kang Chang
249*e7b1675dSTing-Kang Chang  def run(self):
250*e7b1675dSTing-Kang Chang    _patch_workspace('WORKSPACE')
251*e7b1675dSTing-Kang Chang    sdist.sdist.run(self)
252*e7b1675dSTing-Kang Chang
253*e7b1675dSTing-Kang Chang
254*e7b1675dSTing-Kang Changdef main():
255*e7b1675dSTing-Kang Chang  # Generate compiled protocol buffers.
256*e7b1675dSTing-Kang Chang  protoc_command = _get_protoc_command()
257*e7b1675dSTing-Kang Chang  for proto_file in glob.glob('tink/proto/*.proto'):
258*e7b1675dSTing-Kang Chang    _generate_proto(protoc_command, proto_file)
259*e7b1675dSTing-Kang Chang
260*e7b1675dSTing-Kang Chang  setuptools.setup(
261*e7b1675dSTing-Kang Chang      name='tink',
262*e7b1675dSTing-Kang Chang      version=_TINK_VERSION,
263*e7b1675dSTing-Kang Chang      url='https://github.com/google/tink',
264*e7b1675dSTing-Kang Chang      description='A multi-language, cross-platform library that provides '
265*e7b1675dSTing-Kang Chang      'cryptographic APIs that are secure, easy to use correctly, '
266*e7b1675dSTing-Kang Chang      'and hard(er) to misuse.',
267*e7b1675dSTing-Kang Chang      author='Tink Developers',
268*e7b1675dSTing-Kang Chang      author_email='[email protected]',
269*e7b1675dSTing-Kang Chang      long_description=open('README.md').read(),
270*e7b1675dSTing-Kang Chang      long_description_content_type='text/markdown',
271*e7b1675dSTing-Kang Chang      # Contained modules and scripts.
272*e7b1675dSTing-Kang Chang      packages=setuptools.find_packages(),
273*e7b1675dSTing-Kang Chang      install_requires=_parse_requirements('requirements.in'),
274*e7b1675dSTing-Kang Chang      cmdclass={
275*e7b1675dSTing-Kang Chang          'build_ext': BuildBazelExtension,
276*e7b1675dSTing-Kang Chang          'sdist': SdistCmd,
277*e7b1675dSTing-Kang Chang      },
278*e7b1675dSTing-Kang Chang      ext_modules=[
279*e7b1675dSTing-Kang Chang          BazelExtension('//tink/cc/pybind:tink_bindings'),
280*e7b1675dSTing-Kang Chang      ],
281*e7b1675dSTing-Kang Chang      zip_safe=False,
282*e7b1675dSTing-Kang Chang      # PyPI package information.
283*e7b1675dSTing-Kang Chang      classifiers=[
284*e7b1675dSTing-Kang Chang          'Programming Language :: Python :: 3.7',
285*e7b1675dSTing-Kang Chang          'Programming Language :: Python :: 3.8',
286*e7b1675dSTing-Kang Chang          'Programming Language :: Python :: 3.9',
287*e7b1675dSTing-Kang Chang          'Programming Language :: Python :: 3.10',
288*e7b1675dSTing-Kang Chang          'Topic :: Software Development :: Libraries',
289*e7b1675dSTing-Kang Chang      ],
290*e7b1675dSTing-Kang Chang      license='Apache 2.0',
291*e7b1675dSTing-Kang Chang      keywords='tink cryptography',
292*e7b1675dSTing-Kang Chang  )
293*e7b1675dSTing-Kang Chang
294*e7b1675dSTing-Kang Chang
295*e7b1675dSTing-Kang Changif __name__ == '__main__':
296*e7b1675dSTing-Kang Chang  main()
297