blob: d5367ec841af5612a84f0d1711d55406ca0bc18f [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',
80 'nvflash' : '##/usr/bin/nvflash',
81 '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
216 def GetChromeosVersion(self):
217 """Returns the ChromeOS version string
218
219 This works by finding and executing the version script:
220
221 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
222
223 Returns:
224 Version string in the form '0.14.726.2011_07_07_1635'
225
226 Raises:
227 CmdError: If the version script cannot be found, or is found but cannot
228 be executed.
229 """
230 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
231 'chromeos_version.sh')
232
233 if os.path.exists(version_script):
234 str = self.Run('sh', ['-c', '. %s >/dev/null; '
235 'echo ${CHROMEOS_VERSION_STRING}'
236 % version_script])
237 return str.strip()
238 raise CmdError("Cannot find version script 'chromeos_version.sh'")
239
240 def CheckTool(self, filename, ebuild=None):
241 """Check that the specified tool exists.
242
243 If it does not exist in the PATH, then generate a useful error message
244 indicating how to install the ebuild that contains the required tool.
245
246 Args:
247 filename: filename of tool to look for on path.
248 ebuild: name of ebuild which should be emerged to install this tool,
249 or None if it is the same as the filename.
250
251 Raises:
252 CmdError(msg) if the tool is not found.
253 """
254 try:
255 if filename in self._tools:
256 filename = self._tools[filename]
257 filename = self.Filename(filename)
258 self.Run('which', [filename])
259 except CmdError as err:
260 raise CmdError("The '%s' utility was not found in your path. "
261 "Run the following command in \nyour chroot to install it: "
262 "sudo -E emerge %s" % (filename, ebuild or filename))
263
Simon Glassab344e32011-07-17 09:17:07 -0700264 def OutputSize(self, label, filename, level=cros_output.NOTICE):
265 """Display the filename and size of an object.
266
267 Args:
268 label: Label for this file.
269 filename: Filename to output.
270 level: Verbosity level to attach to this message
271 """
272 filename = self.Filename(filename)
273 size = os.stat(filename).st_size
274 self._out.DoOutput(level, "%s: %s; size: %d / %#x" %
275 (label, filename, size, size))
276
Simon Glass951a2db2011-07-17 15:58:58 -0700277 def PrepareOutputDir(self, outdir, preserve=False):
278 """Select an output directory, ensuring it exists.
279
280 This either creates a temporary directory or checks that the one supplied
281 by the user is valid. For a temporary directory, it makes a note to
282 remove it later if required.
283
284 Args:
285 outdir: Output directory to use, or None to use a temporary dir.
286
287 Raises:
288 OSError: If it cannot create the output directory.
289 """
290 self.outdir = outdir
291 self.preserve_outdir = preserve
292 if self.outdir:
293 if not os.path.isdir(self.outdir):
294 try:
295 os.makedirs(self.outdir)
296 except OSError as err:
297 raise CmdError("Cannot make output directory '%s': '%s'" %
298 (self.outdir, err))
299
300 else:
301 self.outdir = tempfile.mkdtemp()
302 self._delete_tempdir = self.outdir
Simon Glass290a1802011-07-17 13:54:32 -0700303 self._out.Debug("Using temporary directory '%s'" %
304 self._delete_tempdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700305
306 def FinalizeOutputDir(self):
307 """Tidy up the output direcory, deleting it if temporary"""
308 if self._delete_tempdir and not self.preserve_outdir:
309 shutil.rmtree(self._delete_tempdir)
310 self._out.Debug("Deleted temporary directory '%s'" %
311 self._delete_tempdir)
312 self._delete_tempdir = None
313 elif self.outdir:
314 self._out.Debug("Output directory '%s'" % self.outdir)
315
Simon Glass50883f92011-07-12 16:19:16 -0700316def _Test():
317 """Run any built-in tests."""
318 import doctest
319 doctest.testmod()
320
321def main():
322 """Main function for tools.
323
324 We provide a way to call a few of our useful functions.
325
326 TODO(sjg) Move into the Chromite libraries when these are ready.
327 """
328 parser = optparse.OptionParser()
329 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
330 type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
331 '4=debug')
332
333 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
334 help_str += '\tchromeos-version\tDisplay Chrome OS version'
335 parser.usage = help_str
336
337 (options, args) = parser.parse_args(sys.argv)
338 args = args[1:]
339
Simon Glassab344e32011-07-17 09:17:07 -0700340 out = cros_output.Output(options.verbosity)
341 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700342 if not args:
343 parser.error('No command provided')
344 elif args[0] == 'chromeos-version':
345 print tools.GetChromeosVersion()
346 else:
347 parser.error("Unknown command '%s'" % args[0])
348
349if __name__ == '__main__':
350 if sys.argv[1:2] == ["--test"]:
351 _Test(*sys.argv[2:])
352 else:
353 main()