blob: 0d7e3bd56b207dfc4ae0300fa13a46a3793ca4e1 [file] [log] [blame]
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -08001#!/usr/bin/env python
2# Copyright 2016, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Builds gRPC distribution artifacts."""
32
33import argparse
34import atexit
35import dockerjob
36import itertools
37import jobset
38import json
39import multiprocessing
40import os
41import re
42import subprocess
43import sys
44import time
45import uuid
46
47# Docker doesn't clean up after itself, so we do it on exit.
48atexit.register(lambda: subprocess.call(['stty', 'echo']))
49
50ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
51os.chdir(ROOT)
52
53
Jan Tattermusch7eba1722016-01-19 08:43:00 -080054def create_docker_jobspec(name, dockerfile_dir, shell_command, environ={},
55 flake_retries=0, timeout_retries=0):
56 """Creates jobspec for a task running under docker."""
57 environ = environ.copy()
58 environ['RUN_COMMAND'] = shell_command
59
60 #docker_args = ['-v', '%s/artifacts:/var/local/jenkins/grpc/artifacts' % ROOT]
61 docker_args=[]
62 for k,v in environ.iteritems():
63 docker_args += ['-e', '%s=%s' % (k, v)]
64 docker_env = {'DOCKERFILE_DIR': dockerfile_dir,
65 'DOCKER_RUN_SCRIPT': 'tools/jenkins/docker_run.sh',
66 'OUTPUT_DIR': 'artifacts'}
67 jobspec = jobset.JobSpec(
68 cmdline=['tools/jenkins/build_and_run_docker.sh'] + docker_args,
69 environ=docker_env,
70 shortname='build_artifact.%s' % (name),
71 timeout_seconds=30*60,
72 flake_retries=flake_retries,
73 timeout_retries=timeout_retries)
74 return jobspec
75
76
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -080077def create_jobspec(name, cmdline, environ=None, shell=False,
78 flake_retries=0, timeout_retries=0):
79 """Creates jobspec."""
Jan Tattermusch7eba1722016-01-19 08:43:00 -080080 jobspec = jobset.JobSpec(
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -080081 cmdline=cmdline,
82 environ=environ,
83 shortname='build_artifact.%s' % (name),
84 timeout_seconds=5*60,
85 flake_retries=flake_retries,
86 timeout_retries=timeout_retries,
87 shell=shell)
Jan Tattermusch7eba1722016-01-19 08:43:00 -080088 return jobspec
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -080089
90
91def macos_arch_env(arch):
92 """Returns environ specifying -arch arguments for make."""
93 if arch == 'x86':
94 arch_arg = '-arch i386'
95 elif arch == 'x64':
96 arch_arg = '-arch x86_64'
97 else:
98 raise Exception('Unsupported arch')
99 return {'CFLAGS': arch_arg, 'LDFLAGS': arch_arg}
100
101
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -0800102class CSharpExtArtifact:
103 """Builds C# native extension library"""
104
105 def __init__(self, platform, arch):
106 self.name = 'csharp_ext_%s_%s' % (platform, arch)
107 self.platform = platform
108 self.arch = arch
109 self.labels = ['csharp', platform, arch]
110
111 def pre_build_jobspecs(self):
112 if self.platform == 'windows':
113 return [create_jobspec('prebuild_%s' % self.name,
114 ['tools\\run_tests\\pre_build_c.bat'],
115 shell=True,
116 flake_retries=5,
117 timeout_retries=2)]
118 else:
119 return []
120
121 def build_jobspec(self):
122 if self.platform == 'windows':
123 msbuild_platform = 'Win32' if self.arch == 'x86' else self.arch
124 return create_jobspec(self.name,
125 ['vsprojects\\build_vs2013.bat',
126 'vsprojects\\grpc_csharp_ext.sln',
127 '/p:Configuration=Release',
128 '/p:PlatformToolset=v120',
129 '/p:Platform=%s' % msbuild_platform],
130 shell=True)
Jan Tattermusch7eba1722016-01-19 08:43:00 -0800131 if self.platform == 'linux':
132 environ = {'CONFIG': 'opt'}
133 return create_docker_jobspec(self.name,
134 'tools/jenkins/grpc_artifact_linux_%s' % self.arch,
135 'tools/run_tests/build_artifact_csharp.sh')
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -0800136 else:
137 environ = {'CONFIG': 'opt'}
138 if self.platform == 'macos':
139 environ.update(macos_arch_env(self.arch))
140 return create_jobspec(self.name,
Jan Tattermusch7eba1722016-01-19 08:43:00 -0800141 ['tools/run_tests/build_artifact_csharp.sh'],
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -0800142 environ=environ)
143
144 def __str__(self):
145 return self.name
146
147
148_ARTIFACTS = [
Jan Tattermusch7eba1722016-01-19 08:43:00 -0800149 CSharpExtArtifact('linux', 'x86'),
Jan Tattermusch4ecf24b2016-01-15 09:12:11 -0800150 CSharpExtArtifact('linux', 'x64'),
151 CSharpExtArtifact('macos', 'x86'),
152 CSharpExtArtifact('macos', 'x64'),
153 CSharpExtArtifact('windows', 'x86'),
154 CSharpExtArtifact('windows', 'x64')
155]
156
157
158def _create_build_map():
159 """Maps artifact names and labels to list of artifacts to be built."""
160 artifact_build_map = dict([(artifact.name, [artifact])
161 for artifact in _ARTIFACTS])
162 if len(_ARTIFACTS) > len(artifact_build_map.keys()):
163 raise Exception('Artifact names need to be unique')
164
165 label_build_map = {}
166 label_build_map['all'] = [a for a in _ARTIFACTS] # to build all artifacts
167 for artifact in _ARTIFACTS:
168 for label in artifact.labels:
169 if label in label_build_map:
170 label_build_map[label].append(artifact)
171 else:
172 label_build_map[label] = [artifact]
173
174 if set(artifact_build_map.keys()).intersection(label_build_map.keys()):
175 raise Exception('Artifact names need to be distinct from label names')
176 return dict( artifact_build_map.items() + label_build_map.items())
177
178
179_BUILD_MAP = _create_build_map()
180
181argp = argparse.ArgumentParser(description='Builds distribution artifacts.')
182argp.add_argument('-b', '--build',
183 choices=sorted(_BUILD_MAP.keys()),
184 nargs='+',
185 default=['all'],
186 help='Artifact name or artifact label to build.')
187argp.add_argument('-f', '--filter',
188 choices=sorted(_BUILD_MAP.keys()),
189 nargs='+',
190 default=[],
191 help='Filter artifacts to build with AND semantics.')
192argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
193argp.add_argument('-t', '--travis',
194 default=False,
195 action='store_const',
196 const=True)
197
198args = argp.parse_args()
199
200# Figure out which artifacts to build
201artifacts = []
202for label in args.build:
203 artifacts += _BUILD_MAP[label]
204
205# Among target selected by -b, filter out those that don't match the filter
206artifacts = [a for a in artifacts if all(f in a.labels for f in args.filter)]
207artifacts = sorted(set(artifacts))
208
209# Execute pre-build phase
210prebuild_jobs = []
211for artifact in artifacts:
212 prebuild_jobs += artifact.pre_build_jobspecs()
213if prebuild_jobs:
214 num_failures, _ = jobset.run(
215 prebuild_jobs, newline_on_success=True, maxjobs=args.jobs)
216 if num_failures != 0:
217 jobset.message('FAILED', 'Pre-build phase failed.', do_newline=True)
218 sys.exit(1)
219
220build_jobs = []
221for artifact in artifacts:
222 build_jobs.append(artifact.build_jobspec())
223if not build_jobs:
224 print 'Nothing to build.'
225 sys.exit(1)
226
227jobset.message('START', 'Building artifacts.', do_newline=True)
228num_failures, _ = jobset.run(
229 build_jobs, newline_on_success=True, maxjobs=args.jobs)
230if num_failures == 0:
231 jobset.message('SUCCESS', 'All artifacts built successfully.',
232 do_newline=True)
233else:
234 jobset.message('FAILED', 'Failed to build artifacts.',
235 do_newline=True)
236 sys.exit(1)