blob: 30921a52a0172714c2c3977e2637e9a1d607426d [file] [log] [blame]
Simon Glass50883f92011-07-12 16:19:16 -07001#!/usr/bin/python
2
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""This implements calling of scripts and other utilities/tools.
8
9We support running inside and outside the chroot. To do this, we permit
10a ## prefix which resolves to the chroot. Within the chroot this will be
11/ but outside it will be the full path to the chroot.
12
13So we can use filenames like this:
14
15 ##/usr/share/vboot/bitmaps/make_bmp_images.sh
16
17"""
18
19import optparse
20import os
Simon Glass951a2db2011-07-17 15:58:58 -070021import shutil
Simon Glass50883f92011-07-12 16:19:16 -070022import sys
Simon Glass951a2db2011-07-17 15:58:58 -070023import tempfile
Simon Glass50883f92011-07-12 16:19:16 -070024
25from cros_build_lib import RunCommandCaptureOutput
26import cros_build_lib
Simon Glassab344e32011-07-17 09:17:07 -070027import cros_output
Simon Glass50883f92011-07-12 16:19:16 -070028
29
30class CmdError(Exception):
31 """An error in the execution of a command."""
32 pass
33
34
35class Tools:
36 """A class to encapsulate the external tools we want to run
37
38 This provides convenient functions for running tools inside/outside the
Simon Glass290a1802011-07-17 13:54:32 -070039 chroot.
40
41 Public properties:
42 outdir: The output directory to write output files to.
Simon Glass6e336fb2012-03-09 15:45:40 -080043 search_paths: The list of directories to search for files we are asked
44 to read.
Simon Glass290a1802011-07-17 13:54:32 -070045
46 The tools class also provides common paths:
Simon Glass50883f92011-07-12 16:19:16 -070047
48 chroot_path: chroot directory
49 src_path: source directory
50 script_path: scripts directory (src/scripts)
51 overlay_path: overlays directory (src/overlays)
52 priv_overlay_path: private overlays directory (src/private-overlays)
53 board_path: build directory (/build in chroot)
54 third_party_path: third_parth directory (src/third_party)
55 cros_overlay_path: Chromium OS overlay (src/chromiumos-overlay)
56 """
Simon Glassab344e32011-07-17 09:17:07 -070057 def __init__(self, output):
Simon Glass50883f92011-07-12 16:19:16 -070058 """Set up the tools system.
59
60 Args:
Simon Glassab344e32011-07-17 09:17:07 -070061 output: cros_output object to use for output.
Simon Glass50883f92011-07-12 16:19:16 -070062 """
63 # Detect whether we're inside a chroot or not
64 self.in_chroot = cros_build_lib.IsInsideChroot()
Simon Glassab344e32011-07-17 09:17:07 -070065 self._out = output
Simon Glass64db3062011-07-14 21:58:54 -070066 self._root = None
67 if self.in_chroot:
68 root_dir = os.getenv('CROS_WORKON_SRCROOT')
69 else:
70 repo = cros_build_lib.FindRepoDir()
71 if not repo:
72 raise IOError('Cannot find .repo directory (must be below cwd level)')
73 root_dir = os.path.dirname(repo)
74 self._SetRoot(root_dir)
Simon Glass50883f92011-07-12 16:19:16 -070075
Simon Glassab344e32011-07-17 09:17:07 -070076 self._out.Info("Chroot is at '%s'" % self.chroot_path)
Simon Glass50883f92011-07-12 16:19:16 -070077 self._tools = {
Simon Glass1b8fa152012-03-09 15:22:54 -080078 'make_bmp_image' : '##/usr/share/vboot/bitmaps/make_bmp_images.sh',
79 'bct_dump' : '##/usr/bin/bct_dump',
Rhyland Klein071cf082012-07-18 17:01:52 -040080 'tegrarcm' : '##/usr/bin/tegrarcm',
Simon Glass1b8fa152012-03-09 15:22:54 -080081 'gbb_utility' : '##/usr/bin/gbb_utility',
82 'cbfstool' : '##/usr/bin/cbfstool',
Simon Glass0c2ba482012-03-22 21:57:51 -070083 'fdisk' : '##/sbin/fdisk',
Simon Glass50883f92011-07-12 16:19:16 -070084 }
Simon Glass951a2db2011-07-17 15:58:58 -070085 self.outdir = None # We have no output directory yet
86 self._delete_tempdir = None # And no temporary directory to delete
Simon Glass6e336fb2012-03-09 15:45:40 -080087 self.search_paths = []
Simon Glass951a2db2011-07-17 15:58:58 -070088
Simon Glass1f778c92011-08-09 13:30:43 -070089 def __enter__(self):
90 return self
91
92 def __exit__(self, type, value, traceback):
93 self.FinalizeOutputDir()
94 return False
Simon Glass50883f92011-07-12 16:19:16 -070095
96 def _SetRoot(self, root_dir):
97 """Sets the root directory for the build envionrment.
98
99 The root directory is the one containing .repo, chroot and src.
100
101 This should be called once the root is known. All other parts are
102 calculated from this.
103
104 Args:
Simon Glass05db7fd2011-07-14 22:02:19 -0700105 root_dir: The path to the root directory.
Simon Glass50883f92011-07-12 16:19:16 -0700106 """
107 self._root = os.path.normpath(root_dir)
108
109 # Set up the path to prepend to get to the chroot
110 if self.in_chroot:
111 self.chroot_path = '/'
112 else:
113 self.chroot_path = cros_build_lib.PrependChrootPath('')
114 self.src_path = os.path.join(self._root, 'src')
115 self.script_path = os.path.join(self.src_path, 'scripts')
116 self.overlay_path = os.path.join(self.src_path, 'overlays')
117 self.priv_overlay_path = os.path.join(self.src_path,
118 'private-overlays')
119 self.board_path = os.path.join(self.chroot_path, 'build')
120 self.third_party_path = os.path.join(self.src_path, 'third_party')
121 self.cros_overlay_path = os.path.join(self.third_party_path,
122 'chromiumos-overlay')
123
124 def Filename(self, fname):
125 """Resolve a chroot-relative filename to an absolute path.
126
127 This looks for ## at the beginning of the filename, and changes it to
128 the chroot directory, which will be / if inside the chroot, or a path
129 to the chroot if not.
130
131 Args:
132 fname: Filename to convert.
133
134 Returns
135 Absolute path to filename.
136 """
137 if fname.startswith('##/'):
138 fname = os.path.join(self.chroot_path, fname[3:])
Simon Glass6e336fb2012-03-09 15:45:40 -0800139
140 # Search for a pathname that exists, and return it if found
Simon Glass1841e7d2012-07-11 14:50:05 +0200141 if fname and not os.path.exists(fname):
Simon Glass6e336fb2012-03-09 15:45:40 -0800142 for path in self.search_paths:
143 pathname = os.path.join(path, os.path.basename(fname))
144 if os.path.exists(pathname):
145 return pathname
146
147 # If not found, just return the standard, unchanged path
Simon Glass50883f92011-07-12 16:19:16 -0700148 return fname
149
Simon Glass86d16aa2012-03-09 15:29:05 -0800150 def Run(self, tool, args, cwd=None, sudo=False):
Simon Glass50883f92011-07-12 16:19:16 -0700151 """Run a tool with given arguments.
152
153 The tool name may be used unchanged or substituted with a full path if
154 required.
155
156 The tool and arguments can use ##/ to signify the chroot (at the beginning
157 of the tool/argument).
158
159 Args:
160 tool: Name of tool to run.
161 args: List of arguments to pass to tool.
162 cwd: Directory to change into before running tool (None if none).
Simon Glass86d16aa2012-03-09 15:29:05 -0800163 sudo: True to run the tool with sudo
Simon Glass50883f92011-07-12 16:19:16 -0700164
165 Returns:
166 Output of tool (stdout).
167
168 Raises
169 CmdError if running the tool, or the tool itself creates an error"""
170 if tool in self._tools:
171 tool = self._tools[tool]
172 tool = self.Filename(tool)
173 args = [self.Filename(arg) for arg in args]
174 cmd = [tool] + args
Simon Glass86d16aa2012-03-09 15:29:05 -0800175 if sudo:
176 cmd.insert(0, 'sudo')
Simon Glass50883f92011-07-12 16:19:16 -0700177 try:
Simon Glassab344e32011-07-17 09:17:07 -0700178 rc, stdout, err = RunCommandCaptureOutput(cmd,
179 print_cmd=self._out.verbose > 3, cwd=cwd)
Simon Glass50883f92011-07-12 16:19:16 -0700180 except OSError:
181 raise CmdError('Command not found: %s' % (' '.join(cmd)))
182 if rc:
Simon Glassab344e32011-07-17 09:17:07 -0700183 raise CmdError('Command failed: %s\n%s' % (' '.join(cmd), stdout))
184 self._out.Debug(stdout)
185 return stdout
Simon Glass50883f92011-07-12 16:19:16 -0700186
187 def ReadFile(self, fname):
188 """Read and return the contents of a file.
189
190 Args:
191 fname: path to filename to read, where ## signifiies the chroot.
192
193 Returns:
194 data read from file, as a string.
195 """
196 fd = open(self.Filename(fname), 'rb')
197 data = fd.read()
198 fd.close()
Simon Glassab344e32011-07-17 09:17:07 -0700199 self._out.Info("Read file '%s' size %d (%#0x)" %
200 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700201 return data
202
203 def WriteFile(self, fname, data):
204 """Write data into a file.
205
206 Args:
207 fname: path to filename to write, where ## signifiies the chroot.
208 data: data to write to file, as a string.
209 """
Simon Glassab344e32011-07-17 09:17:07 -0700210 self._out.Info("Write file '%s' size %d (%#0x)" %
211 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700212 fd = open(self.Filename(fname), 'wb')
213 fd.write(data)
214 fd.close()
215
Vic Yanga850b922012-08-11 14:08:43 +0800216 def ReadFileAndConcat(self, filenames):
217 """Read several files and concat them.
218
219 Args:
220 filenames: a list containing name of the files to read.
221
222 Returns:
223 A tuple of a string and two list. The string is the concated data read
224 from file, in the same order as in filenames, aligned to 4-byte. The
225 first list contains the address of each file in the data string and
226 the second one contains the length of each file, both in the same
227 order.
228 """
229 data = ''
230 address = []
231 length = []
232 for fname in filenames:
233 address.append(len(data))
234 content = self.ReadFile(fname)
235 pad_len = ((len(content) + 3) & ~3) - len(content)
236 data += content + '\0' * pad_len
237 length.append(len(content))
238 return data, address, length
239
Simon Glass50883f92011-07-12 16:19:16 -0700240 def GetChromeosVersion(self):
241 """Returns the ChromeOS version string
242
243 This works by finding and executing the version script:
244
245 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
246
247 Returns:
248 Version string in the form '0.14.726.2011_07_07_1635'
249
250 Raises:
251 CmdError: If the version script cannot be found, or is found but cannot
252 be executed.
253 """
254 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
255 'chromeos_version.sh')
256
257 if os.path.exists(version_script):
258 str = self.Run('sh', ['-c', '. %s >/dev/null; '
259 'echo ${CHROMEOS_VERSION_STRING}'
260 % version_script])
261 return str.strip()
262 raise CmdError("Cannot find version script 'chromeos_version.sh'")
263
264 def CheckTool(self, filename, ebuild=None):
265 """Check that the specified tool exists.
266
267 If it does not exist in the PATH, then generate a useful error message
268 indicating how to install the ebuild that contains the required tool.
269
270 Args:
271 filename: filename of tool to look for on path.
272 ebuild: name of ebuild which should be emerged to install this tool,
273 or None if it is the same as the filename.
274
275 Raises:
276 CmdError(msg) if the tool is not found.
277 """
278 try:
279 if filename in self._tools:
280 filename = self._tools[filename]
281 filename = self.Filename(filename)
282 self.Run('which', [filename])
283 except CmdError as err:
284 raise CmdError("The '%s' utility was not found in your path. "
285 "Run the following command in \nyour chroot to install it: "
286 "sudo -E emerge %s" % (filename, ebuild or filename))
287
Simon Glassab344e32011-07-17 09:17:07 -0700288 def OutputSize(self, label, filename, level=cros_output.NOTICE):
289 """Display the filename and size of an object.
290
291 Args:
292 label: Label for this file.
293 filename: Filename to output.
294 level: Verbosity level to attach to this message
295 """
296 filename = self.Filename(filename)
297 size = os.stat(filename).st_size
298 self._out.DoOutput(level, "%s: %s; size: %d / %#x" %
299 (label, filename, size, size))
300
Simon Glass951a2db2011-07-17 15:58:58 -0700301 def PrepareOutputDir(self, outdir, preserve=False):
302 """Select an output directory, ensuring it exists.
303
304 This either creates a temporary directory or checks that the one supplied
305 by the user is valid. For a temporary directory, it makes a note to
306 remove it later if required.
307
308 Args:
309 outdir: Output directory to use, or None to use a temporary dir.
310
311 Raises:
312 OSError: If it cannot create the output directory.
313 """
314 self.outdir = outdir
315 self.preserve_outdir = preserve
316 if self.outdir:
317 if not os.path.isdir(self.outdir):
318 try:
319 os.makedirs(self.outdir)
320 except OSError as err:
321 raise CmdError("Cannot make output directory '%s': '%s'" %
322 (self.outdir, err))
323
324 else:
325 self.outdir = tempfile.mkdtemp()
326 self._delete_tempdir = self.outdir
Simon Glass290a1802011-07-17 13:54:32 -0700327 self._out.Debug("Using temporary directory '%s'" %
328 self._delete_tempdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700329
330 def FinalizeOutputDir(self):
331 """Tidy up the output direcory, deleting it if temporary"""
332 if self._delete_tempdir and not self.preserve_outdir:
333 shutil.rmtree(self._delete_tempdir)
334 self._out.Debug("Deleted temporary directory '%s'" %
335 self._delete_tempdir)
336 self._delete_tempdir = None
337 elif self.outdir:
338 self._out.Debug("Output directory '%s'" % self.outdir)
339
Simon Glass50883f92011-07-12 16:19:16 -0700340def _Test():
341 """Run any built-in tests."""
342 import doctest
343 doctest.testmod()
344
345def main():
346 """Main function for tools.
347
348 We provide a way to call a few of our useful functions.
349
350 TODO(sjg) Move into the Chromite libraries when these are ready.
351 """
352 parser = optparse.OptionParser()
353 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
354 type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
355 '4=debug')
356
357 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
358 help_str += '\tchromeos-version\tDisplay Chrome OS version'
359 parser.usage = help_str
360
361 (options, args) = parser.parse_args(sys.argv)
362 args = args[1:]
363
Simon Glassab344e32011-07-17 09:17:07 -0700364 out = cros_output.Output(options.verbosity)
365 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700366 if not args:
367 parser.error('No command provided')
368 elif args[0] == 'chromeos-version':
369 print tools.GetChromeosVersion()
370 else:
371 parser.error("Unknown command '%s'" % args[0])
372
373if __name__ == '__main__':
374 if sys.argv[1:2] == ["--test"]:
375 _Test(*sys.argv[2:])
376 else:
377 main()