1#!/usr/bin/env python
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import argparse
19import multiprocessing
20import os
21import shutil
22import subprocess
23import sys
24import time
25
26from subprocess import PIPE, STDOUT
27
28def install_file(file_name, src_dir, dst_dir):
29    src_file = os.path.join(src_dir, file_name)
30    dst_file = os.path.join(dst_dir, file_name)
31
32    print('Copying {} to {}...'.format(src_file, dst_file))
33    if os.path.isdir(src_file):
34        _install_dir(src_file, dst_file)
35    elif os.path.islink(src_file):
36        _install_symlink(src_file, dst_file)
37    else:
38        _install_file(src_file, dst_file)
39
40
41def _install_dir(src_dir, dst_dir):
42    parent_dir = os.path.normpath(os.path.join(dst_dir, '..'))
43    if not os.path.exists(parent_dir):
44        os.makedirs(parent_dir)
45    shutil.copytree(src_dir, dst_dir, symlinks=True)
46
47
48def _install_symlink(src_file, dst_file):
49    dirname = os.path.dirname(dst_file)
50    if not os.path.exists(dirname):
51        os.makedirs(dirname)
52    link_target = os.readlink(src_file)
53    os.symlink(link_target, dst_file)
54
55
56def _install_file(src_file, dst_file):
57    dirname = os.path.dirname(dst_file)
58    if not os.path.exists(dirname):
59        os.makedirs(dirname)
60    # copy2 is just copy followed by copystat (preserves file metadata).
61    shutil.copy2(src_file, dst_file)
62
63THIS_DIR = os.path.realpath(os.path.dirname(__file__))
64
65ALL_ARCHITECTURES = (
66  'arm',
67  'arm64',
68  'x86',
69  'x86_64',
70)
71
72# According to vk_platform.h, armeabi is not supported for Vulkan
73# so remove it from the abis list.
74ALL_ABIS = (
75  'armeabi-v7a',
76  'arm64-v8a',
77  'x86',
78  'x86_64',
79)
80
81def jobs_arg():
82  return '-j{}'.format(multiprocessing.cpu_count() * 2)
83
84def arch_to_abis(arch):
85  return {
86    'arm': ['armeabi-v7a'],
87    'arm64': ['arm64-v8a'],
88    'x86': ['x86'],
89    'x86_64': ['x86_64'],
90  }[arch]
91
92class ArgParser(argparse.ArgumentParser):
93  def __init__(self):
94    super(ArgParser, self).__init__()
95
96    self.add_argument(
97      '--out-dir', help='Directory to place temporary build files.',
98      type=os.path.realpath, default=os.path.join(THIS_DIR, 'out'))
99
100    self.add_argument(
101      '--arch', choices=ALL_ARCHITECTURES,
102      help='Architectures to build. Builds all if not present.')
103
104    self.add_argument('--installdir', dest='installdir', required=True,
105      help='Installation directory. Required.')
106
107    # The default for --dist-dir has to be handled after parsing all
108    # arguments because the default is derived from --out-dir. This is
109    # handled in run().
110    self.add_argument(
111      '--dist-dir', help='Directory to place the packaged artifact.',
112      type=os.path.realpath)
113
114
115def main():
116  print('THIS_DIR: %s' % THIS_DIR)
117  parser = ArgParser()
118  args = parser.parse_args()
119
120  arches = ALL_ARCHITECTURES
121  if args.arch is not None:
122    arches = [args.arch]
123
124  # Make paths absolute, and ensure directories exist.
125  installdir = os.path.abspath(args.installdir)
126
127  abis = []
128  for arch in arches:
129    abis.extend(arch_to_abis(arch))
130
131  shaderc_path = installdir + '/shaderc/android_test'
132  print('shaderc_path = %s' % shaderc_path)
133
134  ndk_dir = os.path.join(THIS_DIR, '../../../prebuilts/toolchain')
135
136  ndk_build = os.path.join(ndk_dir, 'ndk-build')
137  platforms_root = os.path.join(ndk_dir, 'platforms')
138  toolchains_root = os.path.join(ndk_dir, 'toolchains')
139  build_dir = installdir
140
141  print('installdir: %s' % installdir)
142  print('ndk_dir: %s' % ndk_dir)
143  print('ndk_build: %s' % ndk_build)
144  print('platforms_root: %s' % platforms_root)
145
146  compiler = 'clang'
147  stl = 'c++_static'
148  obj_out = os.path.join(THIS_DIR, stl, 'obj')
149  lib_out = os.path.join(THIS_DIR, 'jniLibs')
150
151  print('obj_out: %s' % obj_out)
152  print('lib_out: %s' % lib_out)
153
154  print('Constructing shaderc build tree...')
155  shaderc_root_dir = os.path.join(THIS_DIR, '../../shaderc')
156
157  print('Pulling in vulkan headers and layers...')
158  vulkan_root_dir = os.path.join(THIS_DIR, '../../vulkan-validation-layers')
159  vulkan_headers_root_dir = os.path.join(THIS_DIR, '../../vulkan-headers')
160
161  copies = [
162      {
163          'source_dir': os.path.join(shaderc_root_dir, 'shaderc'),
164          'dest_dir': 'third_party/shaderc',
165          'files': [
166              'Android.mk', 'libshaderc/Android.mk',
167              'libshaderc_util/Android.mk',
168              'third_party/Android.mk',
169              'utils/update_build_version.py',
170              'CHANGES',
171          ],
172          'dirs': [
173              'libshaderc/include', 'libshaderc/src',
174              'libshaderc_util/include', 'libshaderc_util/src',
175              'android_test'
176          ],
177      },
178      {
179          'source_dir': os.path.join(shaderc_root_dir, 'spirv-tools'),
180          'dest_dir': 'third_party/shaderc/third_party/spirv-tools',
181          'files': [
182              'utils/generate_grammar_tables.py',
183              'utils/generate_language_headers.py',
184              'utils/generate_registry_tables.py',
185              'utils/update_build_version.py',
186              'Android.mk',
187              'CHANGES',
188          ],
189          'dirs': ['include', 'source'],
190      },
191      {
192          'source_dir': os.path.join(shaderc_root_dir, 'spirv-headers'),
193          'dest_dir':
194              'third_party/shaderc/third_party/spirv-tools/external/spirv-headers',
195          'dirs': ['include',],
196          'files': [
197              'include/spirv/1.0/spirv.py',
198              'include/spirv/1.1/spirv.py',
199              'include/spirv/1.2/spirv.py',
200          ],
201      },
202      {
203          'source_dir': os.path.join(shaderc_root_dir, 'glslang'),
204          'dest_dir': 'third_party/shaderc/third_party/glslang',
205          'files': ['glslang/OSDependent/osinclude.h',
206                    'Android.mk',
207                    # Build version info is generated frmo the CHANGES.md file.
208                    'CHANGES.md',
209                    'build_info.h.tmpl',
210                    'build_info.py',
211                   ],
212          'dirs': [
213              'SPIRV',
214              'OGLCompilersDLL',
215              'glslang/GenericCodeGen',
216              'hlsl',
217              'glslang/HLSL',
218              'glslang/Include',
219              'glslang/MachineIndependent',
220              'glslang/OSDependent/Unix',
221              'glslang/Public',
222          ],
223      },
224      {
225          'source_dir': vulkan_root_dir,
226          'dest_dir': 'vulkan/src',
227          'files': [
228          ],
229          'dirs': [
230              'layers', 'scripts', 'build-android'
231          ],
232      },
233
234      {
235          'source_dir': vulkan_headers_root_dir,
236          'dest_dir': 'vulkan/src',
237          'files': [
238          ],
239          'dirs': [
240              'include', 'registry'
241          ],
242      },
243  ]
244
245  default_ignore_patterns = shutil.ignore_patterns(
246      "*CMakeLists.txt",
247      "*test.h",
248      "*test.cc")
249
250  for properties in copies:
251      source_dir = properties['source_dir']
252      dest_dir = os.path.join(installdir, properties['dest_dir'])
253      for d in properties['dirs']:
254          src = os.path.join(source_dir, d)
255          dst = os.path.join(dest_dir, d)
256          print(src, " -> ", dst)
257          shutil.copytree(src, dst,
258                          ignore=default_ignore_patterns)
259      for f in properties['files']:
260          print(source_dir, ':', dest_dir, ":", f)
261          # Only copy if the source file exists.  That way
262          # we can update this script in anticipation of
263          # source files yet-to-come.
264          if os.path.exists(os.path.join(source_dir, f)):
265              install_file(f, source_dir, dest_dir)
266          else:
267              print(source_dir, ':', dest_dir, ":", f, "SKIPPED")
268
269  print('Constructing Vulkan validation layer source...')
270
271  build_cmd = [
272    'bash', ndk_build, '-C', build_dir + '/vulkan/src/build-android',
273    jobs_arg(),
274    'APP_ABI=' + ' '.join(abis),
275    # Use the prebuilt platforms and toolchains.
276    'NDK_PLATFORMS_ROOT=' + platforms_root,
277    'NDK_TOOLCHAINS_ROOT=' + toolchains_root,
278    'NDK_MODULE_PATH=' + installdir,
279    'GNUSTL_PREFIX=',
280    'APP_STL=' + stl,
281    'NDK_TOOLCHAIN_VERSION=' + compiler,
282
283    # Tell ndk-build where to put the results
284    'NDK_OUT=' + obj_out,
285    'NDK_LIBS_OUT=' + lib_out,
286  ]
287
288  print('Building Vulkan validation layers for ABIs:' +
289    ' {}'.format(', '.join(abis)) + "...")
290  print(' '.join(build_cmd))
291
292  subprocess.check_call(build_cmd)
293
294  print('Finished building Vulkan validation layers')
295  out_package = os.path.join(installdir, 'vulkan_validation_layers.zip')
296  os.chdir(lib_out)
297  build_cmd = [
298      'zip', '-9qr', out_package, "."
299  ]
300
301  print('Packaging Vulkan validation layers')
302  subprocess.check_call(build_cmd)
303  print('Finished Packaging Vulkan validation layers')
304
305  for properties in copies:
306      dest_dir = os.path.join(installdir, properties['dest_dir'])
307      for d in properties['dirs']:
308          dst = os.path.join(dest_dir, d)
309          print('Remove: %s' % dst)
310          shutil.rmtree(dst)
311
312  return 0
313
314
315if __name__ == '__main__':
316  main()
317