Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(140)

Side by Side Diff: third_party/instrumented_libraries/download_build_install.py

Issue 1012823003: Instrumented libraries: move all scripts to scripts/. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rename run-before-build -> pre-build Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | third_party/instrumented_libraries/fix_rpaths.sh » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Downloads, builds (with instrumentation) and installs shared libraries."""
7
8 import argparse
9 import os
10 import platform
11 import re
12 import shlex
13 import shutil
14 import subprocess
15 import sys
16
17 SCRIPT_ABSOLUTE_PATH = os.path.dirname(os.path.abspath(__file__))
18
19 def unescape_flags(s):
20 """Un-escapes build flags received from GYP.
21
22 GYP escapes build flags as if they are to be inserted directly into a command
23 line, wrapping each flag in double quotes. When flags are passed via
24 CFLAGS/LDFLAGS instead, double quotes must be dropped.
25 """
26 return ' '.join(shlex.split(s))
27
28
29 def real_path(path_relative_to_script):
30 """Returns the absolute path to a file.
31
32 GYP generates paths relative to the location of the .gyp file, which coincides
33 with the location of this script. This function converts them to absolute
34 paths.
35 """
36 return os.path.realpath(os.path.join(SCRIPT_ABSOLUTE_PATH,
37 path_relative_to_script))
38
39
40 class InstrumentedPackageBuilder(object):
41 """Checks out and builds a single instrumented package."""
42 def __init__(self, args, clobber):
43 self._cc = args.cc
44 self._cxx = args.cxx
45 self._extra_configure_flags = args.extra_configure_flags
46 self._jobs = args.jobs
47 self._libdir = args.libdir
48 self._package = args.package
49 self._patch = real_path(args.patch) if args.patch else None
50 self._run_before_build = \
51 real_path(args.run_before_build) if args.run_before_build else None
52 self._sanitizer = args.sanitizer
53 self._verbose = args.verbose
54 self._clobber = clobber
55 self._working_dir = os.path.join(
56 real_path(args.intermediate_dir), self._package, '')
57
58 product_dir = real_path(args.product_dir)
59 self._destdir = os.path.join(
60 product_dir, 'instrumented_libraries', self._sanitizer)
61 self._source_archives_dir = os.path.join(
62 product_dir, 'instrumented_libraries', 'sources', self._package)
63
64 self._cflags = unescape_flags(args.cflags)
65 if args.sanitizer_blacklist:
66 blacklist_file = real_path(args.sanitizer_blacklist)
67 self._cflags += ' -fsanitize-blacklist=%s' % blacklist_file
68
69 self._ldflags = unescape_flags(args.ldflags)
70
71 self.init_build_env()
72
73 # Initialized later.
74 self._source_dir = None
75 self._source_archives = None
76
77 def init_build_env(self):
78 self._build_env = os.environ.copy()
79
80 self._build_env['CC'] = self._cc
81 self._build_env['CXX'] = self._cxx
82
83 self._build_env['CFLAGS'] = self._cflags
84 self._build_env['CXXFLAGS'] = self._cflags
85 self._build_env['LDFLAGS'] = self._ldflags
86
87 if self._sanitizer == 'asan':
88 # Do not report leaks during the build process.
89 self._build_env['ASAN_OPTIONS'] = \
90 '%s:detect_leaks=0' % self._build_env.get('ASAN_OPTIONS', '')
91
92 # libappindicator1 needs this.
93 self._build_env['CSC'] = '/usr/bin/mono-csc'
94
95 def shell_call(self, command, env=None, cwd=None):
96 """Wrapper around subprocess.Popen().
97
98 Calls command with specific environment and verbosity using
99 subprocess.Popen().
100 """
101 child = subprocess.Popen(
102 command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
103 env=env, shell=True, cwd=cwd)
104 stdout, stderr = child.communicate()
105 if self._verbose or child.returncode:
106 print stdout
107 if child.returncode:
108 raise Exception('Failed to run: %s' % command)
109
110 def maybe_download_source(self):
111 """Checks out the source code (if needed).
112
113 Checks out the source code for the package, if required (i.e. unless running
114 in no-clobber mode). Initializes self._source_dir and self._source_archives.
115 """
116 get_fresh_source = self._clobber or not os.path.exists(self._working_dir)
117 if get_fresh_source:
118 self.shell_call('rm -rf %s' % self._working_dir)
119 os.makedirs(self._working_dir)
120 self.shell_call('apt-get source %s' % self._package,
121 cwd=self._working_dir)
122
123 (dirpath, dirnames, filenames) = os.walk(self._working_dir).next()
124
125 if len(dirnames) != 1:
126 raise Exception(
127 '`apt-get source %s\' must create exactly one subdirectory.'
128 % self._package)
129 self._source_dir = os.path.join(dirpath, dirnames[0], '')
130
131 if len(filenames) == 0:
132 raise Exception('Can\'t find source archives after `apt-get source %s\'.'
133 % self._package)
134 self._source_archives = \
135 [os.path.join(dirpath, filename) for filename in filenames]
136
137 return get_fresh_source
138
139 def patch_source(self):
140 if self._patch:
141 self.shell_call('patch -p1 -i %s' % self._patch, cwd=self._source_dir)
142 if self._run_before_build:
143 self.shell_call(self._run_before_build, cwd=self._source_dir)
144
145 def copy_source_archives(self):
146 """Copies the downloaded source archives to the output dir.
147
148 For license compliance purposes, every Chromium build that includes
149 instrumented libraries must include their full source code.
150 """
151 self.shell_call('rm -rf %s' % self._source_archives_dir)
152 os.makedirs(self._source_archives_dir)
153 for filename in self._source_archives:
154 shutil.copy(filename, self._source_archives_dir)
155 if self._patch:
156 shutil.copy(self._patch, self._source_archives_dir)
157
158 def download_build_install(self):
159 got_fresh_source = self.maybe_download_source()
160 if got_fresh_source:
161 self.patch_source()
162 self.copy_source_archives()
163
164 self.shell_call('mkdir -p %s' % self.dest_libdir())
165
166 try:
167 self.build_and_install()
168 except Exception as exception:
169 print 'ERROR: Failed to build package %s. Have you run ' \
170 'src/third_party/instrumented_libraries/install-build-deps.sh?' % \
171 self._package
172 print
173 raise
174
175 # Touch a text file to indicate package is installed.
176 stamp_file = os.path.join(self._destdir, '%s.txt' % self._package)
177 open(stamp_file, 'w').close()
178
179 # Remove downloaded package and generated temporary build files. Failed
180 # builds intentionally skip this step to help debug build failures.
181 if self._clobber:
182 self.shell_call('rm -rf %s' % self._working_dir)
183
184 def fix_rpaths(self, directory):
185 # TODO(earthdok): reimplement fix_rpaths.sh in Python.
186 script = real_path('fix_rpaths.sh')
187 self.shell_call("%s %s" % (script, directory))
188
189 def temp_dir(self):
190 """Returns the directory which will be passed to `make install'."""
191 return os.path.join(self._source_dir, 'debian', 'instrumented_build')
192
193 def temp_libdir(self):
194 """Returns the directory under temp_dir() containing the DSOs."""
195 return os.path.join(self.temp_dir(), self._libdir)
196
197 def dest_libdir(self):
198 """Returns the final location of the DSOs."""
199 return os.path.join(self._destdir, self._libdir)
200
201 def make(self, args, jobs=None, env=None, cwd=None):
202 """Invokes `make'.
203
204 Invokes `make' with the specified args, using self._build_env and
205 self._source_dir by default.
206 """
207 if jobs is None:
208 jobs = self._jobs
209 if cwd is None:
210 cwd = self._source_dir
211 if env is None:
212 env = self._build_env
213 cmd = ['make', '-j%s' % jobs] + args
214 self.shell_call(' '.join(cmd), env=env, cwd=cwd)
215
216 def make_install(self, args, **kwargs):
217 """Invokes `make install'."""
218 self.make(['install'] + args, **kwargs)
219
220 def build_and_install(self):
221 """Builds and installs the DSOs.
222
223 Builds the package with ./configure + make, installs it to a temporary
224 location, then moves the relevant files to their permanent location.
225 """
226 configure_cmd = './configure --libdir=/%s/ %s' % (
227 self._libdir, self._extra_configure_flags)
228 self.shell_call(configure_cmd, env=self._build_env, cwd=self._source_dir)
229
230 # Some makefiles use BUILDROOT or INSTALL_ROOT instead of DESTDIR.
231 args = ['DESTDIR', 'BUILDROOT', 'INSTALL_ROOT']
232 make_args = ['%s=%s' % (name, self.temp_dir()) for name in args]
233 self.make(make_args)
234
235 # Some packages don't support parallel install. Use -j1 always.
236 self.make_install(make_args, jobs=1)
237
238 # .la files are not needed, nuke them.
239 self.shell_call('rm %s/*.la -f' % self.temp_libdir())
240
241 self.fix_rpaths(self.temp_libdir())
242
243 # Now move the contents of the temporary destdir to their final place.
244 # We only care for the contents of LIBDIR.
245 self.shell_call('cp %s/* %s/ -rdf' % (self.temp_libdir(),
246 self.dest_libdir()))
247
248
249 class LibcapBuilder(InstrumentedPackageBuilder):
250 def build_and_install(self):
251 # libcap2 doesn't have a configure script
252 build_args = ['CC', 'CXX', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS']
253 make_args = [
254 '%s="%s"' % (name, self._build_env[name]) for name in build_args
255 ]
256 self.make(make_args)
257
258 install_args = [
259 'DESTDIR=%s' % self.temp_dir(),
260 'lib=%s' % self._libdir,
261 # Skip a step that requires sudo.
262 'RAISE_SETFCAP=no'
263 ]
264 self.make_install(install_args)
265
266 self.fix_rpaths(self.temp_libdir())
267
268 # Now move the contents of the temporary destdir to their final place.
269 # We only care for the contents of LIBDIR.
270 self.shell_call('cp %s/* %s/ -rdf' % (self.temp_libdir(),
271 self.dest_libdir()))
272
273
274 class Libpci3Builder(InstrumentedPackageBuilder):
275 def package_version(self):
276 """Guesses libpci3 version from source directory name."""
277 dir_name = os.path.split(os.path.normpath(self._source_dir))[-1]
278 match = re.match('pciutils-(\d+\.\d+\.\d+)', dir_name)
279 if match is None:
280 raise Exception(
281 'Unable to guess libpci3 version from directory name: %s' % dir_name)
282 return match.group(1)
283
284 def temp_libdir(self):
285 # DSOs have to be picked up from <source_dir>/lib, since `make install'
286 # doesn't actualy install them anywhere.
287 return os.path.join(self._source_dir, 'lib')
288
289 def build_and_install(self):
290 # pciutils doesn't have a configure script
291 # This build process follows debian/rules.
292 self.shell_call('mkdir -p %s-udeb/usr/bin' % self.temp_dir())
293
294 build_args = ['CC', 'CXX', 'CFLAGS', 'CXXFLAGS', 'LDFLAGS']
295 make_args = [
296 '%s="%s"' % (name, self._build_env[name]) for name in build_args
297 ]
298 make_args += [
299 'LIBDIR=/%s/' % self._libdir,
300 'PREFIX=/usr',
301 'SBINDIR=/usr/bin',
302 'IDSDIR=/usr/share/misc',
303 'SHARED=yes',
304 # pciutils-3.2.1 (Trusty) fails to build due to unresolved libkmod
305 # symbols. The binary package has no dependencies on libkmod, so it
306 # looks like it was actually built without libkmod support.
307 'LIBKMOD=no',
308 ]
309 self.make(make_args)
310
311 # `make install' is not needed.
312 self.fix_rpaths(self.temp_libdir())
313
314 # Now install the DSOs to their final place.
315 self.shell_call(
316 'install -m 644 %s/libpci.so* %s' % (self.temp_libdir(),
317 self.dest_libdir()))
318 self.shell_call(
319 'ln -sf libpci.so.%s %s/libpci.so.3' % (self.package_version(),
320 self.dest_libdir()))
321
322
323 class NSSBuilder(InstrumentedPackageBuilder):
324 def build_and_install(self):
325 # NSS uses a build system that's different from configure/make/install. All
326 # flags must be passed as arguments to make.
327 make_args = [
328 # Do an optimized build.
329 'BUILD_OPT=1',
330 # CFLAGS/CXXFLAGS should not be used, as doing so overrides the flags in
331 # the makefile completely. The only way to append our flags is to tack
332 # them onto CC/CXX.
333 'CC="%s %s"' % (self._build_env['CC'], self._build_env['CFLAGS']),
334 'CXX="%s %s"' % (self._build_env['CXX'], self._build_env['CXXFLAGS']),
335 # We need to override ZDEFS_FLAG at least to avoid -Wl,-z,defs, which
336 # is not compatible with sanitizers. We also need some way to pass
337 # LDFLAGS without overriding the defaults. Conveniently, ZDEF_FLAG is
338 # always appended to link flags when building NSS on Linux, so we can
339 # just add our LDFLAGS here.
340 'ZDEFS_FLAG="-Wl,-z,nodefs %s"' % self._build_env['LDFLAGS'],
341 'NSPR_INCLUDE_DIR=/usr/include/nspr',
342 'NSPR_LIB_DIR=%s' % self.dest_libdir(),
343 'NSS_ENABLE_ECC=1'
344 ]
345 if platform.architecture()[0] == '64bit':
346 make_args.append('USE_64=1')
347
348 # Make sure we don't override the default flags in the makefile.
349 for variable in ['CFLAGS', 'CXXFLAGS', 'LDFLAGS']:
350 del self._build_env[variable]
351
352 # Hardcoded paths.
353 temp_dir = os.path.join(self._source_dir, 'nss')
354 temp_libdir = os.path.join(temp_dir, 'lib')
355
356 # Parallel build is not supported. Also, the build happens in
357 # <source_dir>/nss.
358 self.make(make_args, jobs=1, cwd=temp_dir)
359
360 self.fix_rpaths(temp_libdir)
361
362 # 'make install' is not supported. Copy the DSOs manually.
363 for (dirpath, dirnames, filenames) in os.walk(temp_libdir):
364 for filename in filenames:
365 if filename.endswith('.so'):
366 full_path = os.path.join(dirpath, filename)
367 if self._verbose:
368 print 'download_build_install.py: installing %s' % full_path
369 shutil.copy(full_path, self.dest_libdir())
370
371
372 def main():
373 parser = argparse.ArgumentParser(
374 description='Download, build and install an instrumented package.')
375
376 parser.add_argument('-j', '--jobs', type=int, default=1)
377 parser.add_argument('-p', '--package', required=True)
378 parser.add_argument(
379 '-i', '--product-dir', default='.',
380 help='Relative path to the directory with chrome binaries')
381 parser.add_argument(
382 '-m', '--intermediate-dir', default='.',
383 help='Relative path to the directory for temporary build files')
384 parser.add_argument('--extra-configure-flags', default='')
385 parser.add_argument('--cflags', default='')
386 parser.add_argument('--ldflags', default='')
387 parser.add_argument('-s', '--sanitizer', required=True,
388 choices=['asan', 'msan', 'tsan'])
389 parser.add_argument('-v', '--verbose', action='store_true')
390 parser.add_argument('--cc')
391 parser.add_argument('--cxx')
392 parser.add_argument('--patch', default='')
393 # This should be a shell script to run before building specific libraries.
394 # This will be run after applying the patch above.
395 parser.add_argument('--run-before-build', default='')
396 parser.add_argument('--build-method', default='destdir')
397 parser.add_argument('--sanitizer-blacklist', default='')
398 # The LIBDIR argument to configure/make.
399 parser.add_argument('--libdir', default='lib')
400
401 # Ignore all empty arguments because in several cases gyp passes them to the
402 # script, but ArgumentParser treats them as positional arguments instead of
403 # ignoring (and doesn't have such options).
404 args = parser.parse_args([arg for arg in sys.argv[1:] if len(arg) != 0])
405
406 # Clobber by default, unless the developer wants to hack on the package's
407 # source code.
408 clobber = \
409 (os.environ.get('INSTRUMENTED_LIBRARIES_NO_CLOBBER', '') != '1')
410
411 if args.build_method == 'destdir':
412 builder = InstrumentedPackageBuilder(args, clobber)
413 elif args.build_method == 'custom_nss':
414 builder = NSSBuilder(args, clobber)
415 elif args.build_method == 'custom_libcap':
416 builder = LibcapBuilder(args, clobber)
417 elif args.build_method == 'custom_libpci3':
418 builder = Libpci3Builder(args, clobber)
419 else:
420 raise Exception('Unrecognized build method: %s' % args.build_method)
421
422 builder.download_build_install()
423
424 if __name__ == '__main__':
425 main()
OLDNEW
« no previous file with comments | « no previous file | third_party/instrumented_libraries/fix_rpaths.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698