artifact_targets.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. #!/usr/bin/env python
  2. # Copyright 2016 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Definition of targets to build artifacts."""
  16. import os.path
  17. import random
  18. import string
  19. import sys
  20. sys.path.insert(0, os.path.abspath('..'))
  21. import python_utils.jobset as jobset
  22. def create_docker_jobspec(name,
  23. dockerfile_dir,
  24. shell_command,
  25. environ={},
  26. flake_retries=0,
  27. timeout_retries=0,
  28. timeout_seconds=30 * 60,
  29. extra_docker_args=None,
  30. verbose_success=False):
  31. """Creates jobspec for a task running under docker."""
  32. environ = environ.copy()
  33. environ['ARTIFACTS_OUT'] = 'artifacts/%s' % name
  34. docker_args = []
  35. for k, v in list(environ.items()):
  36. docker_args += ['-e', '%s=%s' % (k, v)]
  37. docker_env = {
  38. 'DOCKERFILE_DIR': dockerfile_dir,
  39. 'DOCKER_RUN_SCRIPT': 'tools/run_tests/dockerize/docker_run.sh',
  40. 'DOCKER_RUN_SCRIPT_COMMAND': shell_command,
  41. 'OUTPUT_DIR': 'artifacts'
  42. }
  43. if extra_docker_args is not None:
  44. docker_env['EXTRA_DOCKER_ARGS'] = extra_docker_args
  45. jobspec = jobset.JobSpec(
  46. cmdline=['tools/run_tests/dockerize/build_and_run_docker.sh'] +
  47. docker_args,
  48. environ=docker_env,
  49. shortname='build_artifact.%s' % (name),
  50. timeout_seconds=timeout_seconds,
  51. flake_retries=flake_retries,
  52. timeout_retries=timeout_retries,
  53. verbose_success=verbose_success)
  54. return jobspec
  55. def create_jobspec(name,
  56. cmdline,
  57. environ={},
  58. shell=False,
  59. flake_retries=0,
  60. timeout_retries=0,
  61. timeout_seconds=30 * 60,
  62. use_workspace=False,
  63. cpu_cost=1.0,
  64. verbose_success=False):
  65. """Creates jobspec."""
  66. environ = environ.copy()
  67. if use_workspace:
  68. environ['WORKSPACE_NAME'] = 'workspace_%s' % name
  69. environ['ARTIFACTS_OUT'] = os.path.join('..', 'artifacts', name)
  70. cmdline = ['bash', 'tools/run_tests/artifacts/run_in_workspace.sh'
  71. ] + cmdline
  72. else:
  73. environ['ARTIFACTS_OUT'] = os.path.join('artifacts', name)
  74. jobspec = jobset.JobSpec(cmdline=cmdline,
  75. environ=environ,
  76. shortname='build_artifact.%s' % (name),
  77. timeout_seconds=timeout_seconds,
  78. flake_retries=flake_retries,
  79. timeout_retries=timeout_retries,
  80. shell=shell,
  81. cpu_cost=cpu_cost,
  82. verbose_success=verbose_success)
  83. return jobspec
  84. _MACOS_COMPAT_FLAG = '-mmacosx-version-min=10.10'
  85. _ARCH_FLAG_MAP = {'x86': '-m32', 'x64': '-m64'}
  86. class PythonArtifact:
  87. """Builds Python artifacts."""
  88. def __init__(self, platform, arch, py_version, presubmit=False):
  89. self.name = 'python_%s_%s_%s' % (platform, arch, py_version)
  90. self.platform = platform
  91. self.arch = arch
  92. self.labels = ['artifact', 'python', platform, arch, py_version]
  93. if presubmit:
  94. self.labels.append('presubmit')
  95. self.py_version = py_version
  96. if 'manylinux' in platform:
  97. self.labels.append('linux')
  98. if 'linux_extra' in platform:
  99. # linux_extra wheels used to be built by a separate kokoro job.
  100. # Their build is now much faster, so they can be included
  101. # in the regular artifact build.
  102. self.labels.append('linux')
  103. if 'musllinux' in platform:
  104. self.labels.append('linux')
  105. def pre_build_jobspecs(self):
  106. return []
  107. def build_jobspec(self, inner_jobs=None):
  108. environ = {}
  109. if inner_jobs is not None:
  110. # set number of parallel jobs when building native extension
  111. # building the native extension is the most time-consuming part of the build
  112. environ['GRPC_PYTHON_BUILD_EXT_COMPILER_JOBS'] = str(inner_jobs)
  113. if self.platform == 'linux_extra':
  114. # Crosscompilation build for armv7 (e.g. Raspberry Pi)
  115. environ['PYTHON'] = '/opt/python/{}/bin/python3'.format(
  116. self.py_version)
  117. environ['PIP'] = '/opt/python/{}/bin/pip3'.format(self.py_version)
  118. environ['GRPC_SKIP_PIP_CYTHON_UPGRADE'] = 'TRUE'
  119. environ['GRPC_SKIP_TWINE_CHECK'] = 'TRUE'
  120. return create_docker_jobspec(
  121. self.name,
  122. 'tools/dockerfile/grpc_artifact_python_linux_{}'.format(
  123. self.arch),
  124. 'tools/run_tests/artifacts/build_artifact_python.sh',
  125. environ=environ,
  126. timeout_seconds=60 * 60)
  127. elif 'manylinux' in self.platform:
  128. if self.arch == 'x86':
  129. environ['SETARCH_CMD'] = 'linux32'
  130. # Inside the manylinux container, the python installations are located in
  131. # special places...
  132. environ['PYTHON'] = '/opt/python/{}/bin/python'.format(
  133. self.py_version)
  134. environ['PIP'] = '/opt/python/{}/bin/pip'.format(self.py_version)
  135. environ['GRPC_SKIP_PIP_CYTHON_UPGRADE'] = 'TRUE'
  136. if self.arch == 'aarch64':
  137. environ['GRPC_SKIP_TWINE_CHECK'] = 'TRUE'
  138. else:
  139. # only run auditwheel if we're not crosscompiling
  140. environ['GRPC_RUN_AUDITWHEEL_REPAIR'] = 'TRUE'
  141. # only build the packages that depend on grpcio-tools
  142. # if we're not crosscompiling.
  143. # - they require protoc to run on current architecture
  144. # - they only have sdist packages anyway, so it's useless to build them again
  145. environ['GRPC_BUILD_GRPCIO_TOOLS_DEPENDENTS'] = 'TRUE'
  146. return create_docker_jobspec(
  147. self.name,
  148. 'tools/dockerfile/grpc_artifact_python_%s_%s' %
  149. (self.platform, self.arch),
  150. 'tools/run_tests/artifacts/build_artifact_python.sh',
  151. environ=environ,
  152. timeout_seconds=60 * 60 * 2)
  153. elif 'musllinux' in self.platform:
  154. environ['PYTHON'] = '/opt/python/{}/bin/python'.format(
  155. self.py_version)
  156. environ['PIP'] = '/opt/python/{}/bin/pip'.format(self.py_version)
  157. environ['GRPC_SKIP_PIP_CYTHON_UPGRADE'] = 'TRUE'
  158. environ['GRPC_RUN_AUDITWHEEL_REPAIR'] = 'TRUE'
  159. environ['GRPC_PYTHON_BUILD_WITH_STATIC_LIBSTDCXX'] = 'TRUE'
  160. return create_docker_jobspec(
  161. self.name,
  162. 'tools/dockerfile/grpc_artifact_python_%s_%s' %
  163. (self.platform, self.arch),
  164. 'tools/run_tests/artifacts/build_artifact_python.sh',
  165. environ=environ,
  166. timeout_seconds=60 * 60 * 2)
  167. elif self.platform == 'windows':
  168. if 'Python27' in self.py_version:
  169. environ['EXT_COMPILER'] = 'mingw32'
  170. else:
  171. environ['EXT_COMPILER'] = 'msvc'
  172. # For some reason, the batch script %random% always runs with the same
  173. # seed. We create a random temp-dir here
  174. dir = ''.join(
  175. random.choice(string.ascii_uppercase) for _ in range(10))
  176. return create_jobspec(self.name, [
  177. 'tools\\run_tests\\artifacts\\build_artifact_python.bat',
  178. self.py_version, '32' if self.arch == 'x86' else '64'
  179. ],
  180. environ=environ,
  181. timeout_seconds=45 * 60,
  182. use_workspace=True)
  183. else:
  184. environ['PYTHON'] = self.py_version
  185. environ['SKIP_PIP_INSTALL'] = 'TRUE'
  186. return create_jobspec(
  187. self.name,
  188. ['tools/run_tests/artifacts/build_artifact_python.sh'],
  189. environ=environ,
  190. timeout_seconds=60 * 60 * 2,
  191. use_workspace=True)
  192. def __str__(self):
  193. return self.name
  194. class RubyArtifact:
  195. """Builds ruby native gem."""
  196. def __init__(self, platform, gem_platform, presubmit=False):
  197. self.name = 'ruby_native_gem_%s_%s' % (platform, gem_platform)
  198. self.platform = platform
  199. self.gem_platform = gem_platform
  200. self.labels = ['artifact', 'ruby', platform, gem_platform]
  201. if presubmit:
  202. self.labels.append('presubmit')
  203. def pre_build_jobspecs(self):
  204. return []
  205. def build_jobspec(self, inner_jobs=None):
  206. environ = {}
  207. if inner_jobs is not None:
  208. # set number of parallel jobs when building native extension
  209. environ['GRPC_RUBY_BUILD_PROCS'] = str(inner_jobs)
  210. # Ruby build uses docker internally and docker cannot be nested.
  211. # We are using a custom workspace instead.
  212. return create_jobspec(self.name, [
  213. 'tools/run_tests/artifacts/build_artifact_ruby.sh',
  214. self.gem_platform
  215. ],
  216. use_workspace=True,
  217. timeout_seconds=90 * 60,
  218. environ=environ)
  219. class CSharpExtArtifact:
  220. """Builds C# native extension library"""
  221. def __init__(self, platform, arch, arch_abi=None, presubmit=False):
  222. self.name = 'csharp_ext_%s_%s' % (platform, arch)
  223. self.platform = platform
  224. self.arch = arch
  225. self.arch_abi = arch_abi
  226. self.labels = ['artifact', 'csharp', platform, arch]
  227. if arch_abi:
  228. self.name += '_%s' % arch_abi
  229. self.labels.append(arch_abi)
  230. if presubmit:
  231. self.labels.append('presubmit')
  232. def pre_build_jobspecs(self):
  233. return []
  234. def build_jobspec(self, inner_jobs=None):
  235. environ = {}
  236. if inner_jobs is not None:
  237. # set number of parallel jobs when building native extension
  238. environ['GRPC_CSHARP_BUILD_EXT_COMPILER_JOBS'] = str(inner_jobs)
  239. if self.arch == 'android':
  240. environ['ANDROID_ABI'] = self.arch_abi
  241. return create_docker_jobspec(
  242. self.name,
  243. 'tools/dockerfile/grpc_artifact_android_ndk',
  244. 'tools/run_tests/artifacts/build_artifact_csharp_android.sh',
  245. environ=environ)
  246. elif self.arch == 'ios':
  247. return create_jobspec(
  248. self.name,
  249. ['tools/run_tests/artifacts/build_artifact_csharp_ios.sh'],
  250. timeout_seconds=60 * 60,
  251. use_workspace=True,
  252. environ=environ)
  253. elif self.platform == 'windows':
  254. return create_jobspec(self.name, [
  255. 'tools\\run_tests\\artifacts\\build_artifact_csharp.bat',
  256. self.arch
  257. ],
  258. timeout_seconds=45 * 60,
  259. use_workspace=True,
  260. environ=environ)
  261. else:
  262. if self.platform == 'linux':
  263. dockerfile_dir = 'tools/dockerfile/grpc_artifact_centos6_{}'.format(
  264. self.arch)
  265. if self.arch == 'aarch64':
  266. # for aarch64, use a dockcross manylinux image that will
  267. # give us both ready to use crosscompiler and sufficient backward compatibility
  268. dockerfile_dir = 'tools/dockerfile/grpc_artifact_python_manylinux2014_aarch64'
  269. return create_docker_jobspec(
  270. self.name,
  271. dockerfile_dir,
  272. 'tools/run_tests/artifacts/build_artifact_csharp.sh',
  273. environ=environ)
  274. else:
  275. return create_jobspec(
  276. self.name,
  277. ['tools/run_tests/artifacts/build_artifact_csharp.sh'],
  278. timeout_seconds=45 * 60,
  279. use_workspace=True,
  280. environ=environ)
  281. def __str__(self):
  282. return self.name
  283. class PHPArtifact:
  284. """Builds PHP PECL package"""
  285. def __init__(self, platform, arch, presubmit=False):
  286. self.name = 'php_pecl_package_{0}_{1}'.format(platform, arch)
  287. self.platform = platform
  288. self.arch = arch
  289. self.labels = ['artifact', 'php', platform, arch]
  290. if presubmit:
  291. self.labels.append('presubmit')
  292. def pre_build_jobspecs(self):
  293. return []
  294. def build_jobspec(self, inner_jobs=None):
  295. del inner_jobs # arg unused as PHP artifact build is basically just packing an archive
  296. if self.platform == 'linux':
  297. return create_docker_jobspec(
  298. self.name,
  299. 'tools/dockerfile/test/php73_zts_debian11_{}'.format(self.arch),
  300. 'tools/run_tests/artifacts/build_artifact_php.sh')
  301. else:
  302. return create_jobspec(
  303. self.name, ['tools/run_tests/artifacts/build_artifact_php.sh'],
  304. use_workspace=True)
  305. class ProtocArtifact:
  306. """Builds protoc and protoc-plugin artifacts"""
  307. def __init__(self, platform, arch, presubmit=False):
  308. self.name = 'protoc_%s_%s' % (platform, arch)
  309. self.platform = platform
  310. self.arch = arch
  311. self.labels = ['artifact', 'protoc', platform, arch]
  312. if presubmit:
  313. self.labels.append('presubmit')
  314. def pre_build_jobspecs(self):
  315. return []
  316. def build_jobspec(self, inner_jobs=None):
  317. environ = {}
  318. if inner_jobs is not None:
  319. # set number of parallel jobs when building protoc
  320. environ['GRPC_PROTOC_BUILD_COMPILER_JOBS'] = str(inner_jobs)
  321. if self.platform != 'windows':
  322. environ['CXXFLAGS'] = ''
  323. environ['LDFLAGS'] = ''
  324. if self.platform == 'linux':
  325. dockerfile_dir = 'tools/dockerfile/grpc_artifact_centos6_{}'.format(
  326. self.arch)
  327. if self.arch == 'aarch64':
  328. # for aarch64, use a dockcross manylinux image that will
  329. # give us both ready to use crosscompiler and sufficient backward compatibility
  330. dockerfile_dir = 'tools/dockerfile/grpc_artifact_protoc_aarch64'
  331. environ['LDFLAGS'] += ' -static-libgcc -static-libstdc++ -s'
  332. return create_docker_jobspec(
  333. self.name,
  334. dockerfile_dir,
  335. 'tools/run_tests/artifacts/build_artifact_protoc.sh',
  336. environ=environ)
  337. else:
  338. environ[
  339. 'CXXFLAGS'] += ' -std=c++11 -stdlib=libc++ %s' % _MACOS_COMPAT_FLAG
  340. return create_jobspec(
  341. self.name,
  342. ['tools/run_tests/artifacts/build_artifact_protoc.sh'],
  343. environ=environ,
  344. timeout_seconds=60 * 60,
  345. use_workspace=True)
  346. else:
  347. vs_tools_architecture = self.arch # architecture selector passed to vcvarsall.bat
  348. environ['ARCHITECTURE'] = vs_tools_architecture
  349. return create_jobspec(
  350. self.name,
  351. ['tools\\run_tests\\artifacts\\build_artifact_protoc.bat'],
  352. environ=environ,
  353. use_workspace=True)
  354. def __str__(self):
  355. return self.name
  356. def _reorder_targets_for_build_speed(targets):
  357. """Reorder targets to achieve optimal build speed"""
  358. # ruby artifact build builds multiple artifacts at once, so make sure
  359. # we start building ruby artifacts first, so that they don't end up
  360. # being a long tail once everything else finishes.
  361. return list(
  362. sorted(targets,
  363. key=lambda target: 0 if target.name.startswith('ruby_') else 1))
  364. def targets():
  365. """Gets list of supported targets"""
  366. return _reorder_targets_for_build_speed([
  367. ProtocArtifact('linux', 'x64', presubmit=True),
  368. ProtocArtifact('linux', 'x86', presubmit=True),
  369. ProtocArtifact('linux', 'aarch64', presubmit=True),
  370. ProtocArtifact('macos', 'x64', presubmit=True),
  371. ProtocArtifact('windows', 'x64', presubmit=True),
  372. ProtocArtifact('windows', 'x86', presubmit=True),
  373. CSharpExtArtifact('linux', 'x64', presubmit=True),
  374. CSharpExtArtifact('linux', 'aarch64', presubmit=True),
  375. CSharpExtArtifact('macos', 'x64', presubmit=True),
  376. CSharpExtArtifact('windows', 'x64', presubmit=True),
  377. CSharpExtArtifact('windows', 'x86', presubmit=True),
  378. CSharpExtArtifact('linux',
  379. 'android',
  380. arch_abi='arm64-v8a',
  381. presubmit=True),
  382. CSharpExtArtifact('linux',
  383. 'android',
  384. arch_abi='armeabi-v7a',
  385. presubmit=True),
  386. CSharpExtArtifact('linux', 'android', arch_abi='x86', presubmit=True),
  387. CSharpExtArtifact('macos', 'ios', presubmit=True),
  388. PythonArtifact('manylinux2014', 'x64', 'cp36-cp36m', presubmit=True),
  389. PythonArtifact('manylinux2014', 'x64', 'cp37-cp37m'),
  390. PythonArtifact('manylinux2014', 'x64', 'cp38-cp38'),
  391. PythonArtifact('manylinux2014', 'x64', 'cp39-cp39'),
  392. PythonArtifact('manylinux2014', 'x64', 'cp310-cp310', presubmit=True),
  393. PythonArtifact('manylinux2014', 'x86', 'cp36-cp36m', presubmit=True),
  394. PythonArtifact('manylinux2014', 'x86', 'cp37-cp37m'),
  395. PythonArtifact('manylinux2014', 'x86', 'cp38-cp38'),
  396. PythonArtifact('manylinux2014', 'x86', 'cp39-cp39'),
  397. PythonArtifact('manylinux2014', 'x86', 'cp310-cp310', presubmit=True),
  398. PythonArtifact('manylinux2010', 'x64', 'cp36-cp36m'),
  399. PythonArtifact('manylinux2010', 'x64', 'cp37-cp37m', presubmit=True),
  400. PythonArtifact('manylinux2010', 'x64', 'cp38-cp38'),
  401. PythonArtifact('manylinux2010', 'x64', 'cp39-cp39'),
  402. PythonArtifact('manylinux2010', 'x86', 'cp36-cp36m'),
  403. PythonArtifact('manylinux2010', 'x86', 'cp37-cp37m', presubmit=True),
  404. PythonArtifact('manylinux2010', 'x86', 'cp38-cp38'),
  405. PythonArtifact('manylinux2010', 'x86', 'cp39-cp39'),
  406. PythonArtifact('manylinux2014', 'aarch64', 'cp36-cp36m',
  407. presubmit=True),
  408. PythonArtifact('manylinux2014', 'aarch64', 'cp37-cp37m'),
  409. PythonArtifact('manylinux2014', 'aarch64', 'cp38-cp38', presubmit=True),
  410. PythonArtifact('manylinux2014', 'aarch64', 'cp39-cp39'),
  411. PythonArtifact('manylinux2014', 'aarch64', 'cp310-cp310'),
  412. PythonArtifact('linux_extra', 'armv7', 'cp36-cp36m', presubmit=True),
  413. PythonArtifact('linux_extra', 'armv7', 'cp37-cp37m'),
  414. PythonArtifact('linux_extra', 'armv7', 'cp38-cp38'),
  415. PythonArtifact('linux_extra', 'armv7', 'cp39-cp39'),
  416. PythonArtifact('linux_extra', 'armv7', 'cp310-cp310', presubmit=True),
  417. PythonArtifact('musllinux_1_1', 'x64', 'cp310-cp310', presubmit=True),
  418. PythonArtifact('musllinux_1_1', 'x64', 'cp36-cp36m', presubmit=True),
  419. PythonArtifact('musllinux_1_1', 'x64', 'cp37-cp37m'),
  420. PythonArtifact('musllinux_1_1', 'x64', 'cp38-cp38'),
  421. PythonArtifact('musllinux_1_1', 'x64', 'cp39-cp39'),
  422. PythonArtifact('musllinux_1_1', 'x86', 'cp310-cp310', presubmit=True),
  423. PythonArtifact('musllinux_1_1', 'x86', 'cp36-cp36m', presubmit=True),
  424. PythonArtifact('musllinux_1_1', 'x86', 'cp37-cp37m'),
  425. PythonArtifact('musllinux_1_1', 'x86', 'cp38-cp38'),
  426. PythonArtifact('musllinux_1_1', 'x86', 'cp39-cp39'),
  427. PythonArtifact('macos', 'x64', 'python3.6', presubmit=True),
  428. PythonArtifact('macos', 'x64', 'python3.7'),
  429. PythonArtifact('macos', 'x64', 'python3.8'),
  430. PythonArtifact('macos', 'x64', 'python3.9'),
  431. PythonArtifact('macos', 'x64', 'python3.10', presubmit=True),
  432. PythonArtifact('windows', 'x86', 'Python36_32bit', presubmit=True),
  433. PythonArtifact('windows', 'x86', 'Python37_32bit'),
  434. PythonArtifact('windows', 'x86', 'Python38_32bit'),
  435. PythonArtifact('windows', 'x86', 'Python39_32bit'),
  436. PythonArtifact('windows', 'x86', 'Python310_32bit', presubmit=True),
  437. PythonArtifact('windows', 'x64', 'Python36', presubmit=True),
  438. PythonArtifact('windows', 'x64', 'Python37'),
  439. PythonArtifact('windows', 'x64', 'Python38'),
  440. PythonArtifact('windows', 'x64', 'Python39'),
  441. PythonArtifact('windows', 'x64', 'Python310', presubmit=True),
  442. RubyArtifact('linux', 'x86-mingw32', presubmit=True),
  443. RubyArtifact('linux', 'x64-mingw32', presubmit=True),
  444. RubyArtifact('linux', 'x86_64-linux', presubmit=True),
  445. RubyArtifact('linux', 'x86-linux', presubmit=True),
  446. RubyArtifact('linux', 'x86_64-darwin', presubmit=True),
  447. RubyArtifact('linux', 'arm64-darwin', presubmit=True),
  448. RubyArtifact('macos', 'darwin', presubmit=True),
  449. PHPArtifact('linux', 'x64', presubmit=True),
  450. PHPArtifact('macos', 'x64', presubmit=True),
  451. ])