blob: 8d8bed2bba19354e0379a4f8d4e7d54821999662 [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.
43
44 The tools class also provides common paths:
Simon Glass50883f92011-07-12 16:19:16 -070045
46 chroot_path: chroot directory
47 src_path: source directory
48 script_path: scripts directory (src/scripts)
49 overlay_path: overlays directory (src/overlays)
50 priv_overlay_path: private overlays directory (src/private-overlays)
51 board_path: build directory (/build in chroot)
52 third_party_path: third_parth directory (src/third_party)
53 cros_overlay_path: Chromium OS overlay (src/chromiumos-overlay)
54 """
Simon Glassab344e32011-07-17 09:17:07 -070055 def __init__(self, output):
Simon Glass50883f92011-07-12 16:19:16 -070056 """Set up the tools system.
57
58 Args:
Simon Glassab344e32011-07-17 09:17:07 -070059 output: cros_output object to use for output.
Simon Glass50883f92011-07-12 16:19:16 -070060 """
61 # Detect whether we're inside a chroot or not
62 self.in_chroot = cros_build_lib.IsInsideChroot()
Simon Glassab344e32011-07-17 09:17:07 -070063 self._out = output
Simon Glass64db3062011-07-14 21:58:54 -070064 self._root = None
65 if self.in_chroot:
66 root_dir = os.getenv('CROS_WORKON_SRCROOT')
67 else:
68 repo = cros_build_lib.FindRepoDir()
69 if not repo:
70 raise IOError('Cannot find .repo directory (must be below cwd level)')
71 root_dir = os.path.dirname(repo)
72 self._SetRoot(root_dir)
Simon Glass50883f92011-07-12 16:19:16 -070073
Simon Glassab344e32011-07-17 09:17:07 -070074 self._out.Info("Chroot is at '%s'" % self.chroot_path)
Simon Glass50883f92011-07-12 16:19:16 -070075 self._tools = {
76 'make_bmp_image' : '##/usr/share/vboot/bitmaps/make_bmp_images.sh'
77 }
Simon Glass951a2db2011-07-17 15:58:58 -070078 self.outdir = None # We have no output directory yet
79 self._delete_tempdir = None # And no temporary directory to delete
80
Simon Glass1f778c92011-08-09 13:30:43 -070081 def __enter__(self):
82 return self
83
84 def __exit__(self, type, value, traceback):
85 self.FinalizeOutputDir()
86 return False
Simon Glass50883f92011-07-12 16:19:16 -070087
88 def _SetRoot(self, root_dir):
89 """Sets the root directory for the build envionrment.
90
91 The root directory is the one containing .repo, chroot and src.
92
93 This should be called once the root is known. All other parts are
94 calculated from this.
95
96 Args:
Simon Glass05db7fd2011-07-14 22:02:19 -070097 root_dir: The path to the root directory.
Simon Glass50883f92011-07-12 16:19:16 -070098 """
99 self._root = os.path.normpath(root_dir)
100
101 # Set up the path to prepend to get to the chroot
102 if self.in_chroot:
103 self.chroot_path = '/'
104 else:
105 self.chroot_path = cros_build_lib.PrependChrootPath('')
106 self.src_path = os.path.join(self._root, 'src')
107 self.script_path = os.path.join(self.src_path, 'scripts')
108 self.overlay_path = os.path.join(self.src_path, 'overlays')
109 self.priv_overlay_path = os.path.join(self.src_path,
110 'private-overlays')
111 self.board_path = os.path.join(self.chroot_path, 'build')
112 self.third_party_path = os.path.join(self.src_path, 'third_party')
113 self.cros_overlay_path = os.path.join(self.third_party_path,
114 'chromiumos-overlay')
115
116 def Filename(self, fname):
117 """Resolve a chroot-relative filename to an absolute path.
118
119 This looks for ## at the beginning of the filename, and changes it to
120 the chroot directory, which will be / if inside the chroot, or a path
121 to the chroot if not.
122
123 Args:
124 fname: Filename to convert.
125
126 Returns
127 Absolute path to filename.
128 """
129 if fname.startswith('##/'):
130 fname = os.path.join(self.chroot_path, fname[3:])
131 return fname
132
133 def Run(self, tool, args, cwd=None):
134 """Run a tool with given arguments.
135
136 The tool name may be used unchanged or substituted with a full path if
137 required.
138
139 The tool and arguments can use ##/ to signify the chroot (at the beginning
140 of the tool/argument).
141
142 Args:
143 tool: Name of tool to run.
144 args: List of arguments to pass to tool.
145 cwd: Directory to change into before running tool (None if none).
146
147 Returns:
148 Output of tool (stdout).
149
150 Raises
151 CmdError if running the tool, or the tool itself creates an error"""
152 if tool in self._tools:
153 tool = self._tools[tool]
154 tool = self.Filename(tool)
155 args = [self.Filename(arg) for arg in args]
156 cmd = [tool] + args
157 try:
Simon Glassab344e32011-07-17 09:17:07 -0700158 rc, stdout, err = RunCommandCaptureOutput(cmd,
159 print_cmd=self._out.verbose > 3, cwd=cwd)
Simon Glass50883f92011-07-12 16:19:16 -0700160 except OSError:
161 raise CmdError('Command not found: %s' % (' '.join(cmd)))
162 if rc:
Simon Glassab344e32011-07-17 09:17:07 -0700163 raise CmdError('Command failed: %s\n%s' % (' '.join(cmd), stdout))
164 self._out.Debug(stdout)
165 return stdout
Simon Glass50883f92011-07-12 16:19:16 -0700166
167 def ReadFile(self, fname):
168 """Read and return the contents of a file.
169
170 Args:
171 fname: path to filename to read, where ## signifiies the chroot.
172
173 Returns:
174 data read from file, as a string.
175 """
176 fd = open(self.Filename(fname), 'rb')
177 data = fd.read()
178 fd.close()
Simon Glassab344e32011-07-17 09:17:07 -0700179 self._out.Info("Read file '%s' size %d (%#0x)" %
180 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700181 return data
182
183 def WriteFile(self, fname, data):
184 """Write data into a file.
185
186 Args:
187 fname: path to filename to write, where ## signifiies the chroot.
188 data: data to write to file, as a string.
189 """
Simon Glassab344e32011-07-17 09:17:07 -0700190 self._out.Info("Write file '%s' size %d (%#0x)" %
191 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700192 fd = open(self.Filename(fname), 'wb')
193 fd.write(data)
194 fd.close()
195
196 def GetChromeosVersion(self):
197 """Returns the ChromeOS version string
198
199 This works by finding and executing the version script:
200
201 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
202
203 Returns:
204 Version string in the form '0.14.726.2011_07_07_1635'
205
206 Raises:
207 CmdError: If the version script cannot be found, or is found but cannot
208 be executed.
209 """
210 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
211 'chromeos_version.sh')
212
213 if os.path.exists(version_script):
214 str = self.Run('sh', ['-c', '. %s >/dev/null; '
215 'echo ${CHROMEOS_VERSION_STRING}'
216 % version_script])
217 return str.strip()
218 raise CmdError("Cannot find version script 'chromeos_version.sh'")
219
220 def CheckTool(self, filename, ebuild=None):
221 """Check that the specified tool exists.
222
223 If it does not exist in the PATH, then generate a useful error message
224 indicating how to install the ebuild that contains the required tool.
225
226 Args:
227 filename: filename of tool to look for on path.
228 ebuild: name of ebuild which should be emerged to install this tool,
229 or None if it is the same as the filename.
230
231 Raises:
232 CmdError(msg) if the tool is not found.
233 """
234 try:
235 if filename in self._tools:
236 filename = self._tools[filename]
237 filename = self.Filename(filename)
238 self.Run('which', [filename])
239 except CmdError as err:
240 raise CmdError("The '%s' utility was not found in your path. "
241 "Run the following command in \nyour chroot to install it: "
242 "sudo -E emerge %s" % (filename, ebuild or filename))
243
Simon Glassab344e32011-07-17 09:17:07 -0700244 def OutputSize(self, label, filename, level=cros_output.NOTICE):
245 """Display the filename and size of an object.
246
247 Args:
248 label: Label for this file.
249 filename: Filename to output.
250 level: Verbosity level to attach to this message
251 """
252 filename = self.Filename(filename)
253 size = os.stat(filename).st_size
254 self._out.DoOutput(level, "%s: %s; size: %d / %#x" %
255 (label, filename, size, size))
256
Simon Glass951a2db2011-07-17 15:58:58 -0700257 def PrepareOutputDir(self, outdir, preserve=False):
258 """Select an output directory, ensuring it exists.
259
260 This either creates a temporary directory or checks that the one supplied
261 by the user is valid. For a temporary directory, it makes a note to
262 remove it later if required.
263
264 Args:
265 outdir: Output directory to use, or None to use a temporary dir.
266
267 Raises:
268 OSError: If it cannot create the output directory.
269 """
270 self.outdir = outdir
271 self.preserve_outdir = preserve
272 if self.outdir:
273 if not os.path.isdir(self.outdir):
274 try:
275 os.makedirs(self.outdir)
276 except OSError as err:
277 raise CmdError("Cannot make output directory '%s': '%s'" %
278 (self.outdir, err))
279
280 else:
281 self.outdir = tempfile.mkdtemp()
282 self._delete_tempdir = self.outdir
Simon Glass290a1802011-07-17 13:54:32 -0700283 self._out.Debug("Using temporary directory '%s'" %
284 self._delete_tempdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700285
286 def FinalizeOutputDir(self):
287 """Tidy up the output direcory, deleting it if temporary"""
288 if self._delete_tempdir and not self.preserve_outdir:
289 shutil.rmtree(self._delete_tempdir)
290 self._out.Debug("Deleted temporary directory '%s'" %
291 self._delete_tempdir)
292 self._delete_tempdir = None
293 elif self.outdir:
294 self._out.Debug("Output directory '%s'" % self.outdir)
295
Simon Glass50883f92011-07-12 16:19:16 -0700296def _Test():
297 """Run any built-in tests."""
298 import doctest
299 doctest.testmod()
300
301def main():
302 """Main function for tools.
303
304 We provide a way to call a few of our useful functions.
305
306 TODO(sjg) Move into the Chromite libraries when these are ready.
307 """
308 parser = optparse.OptionParser()
309 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
310 type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
311 '4=debug')
312
313 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
314 help_str += '\tchromeos-version\tDisplay Chrome OS version'
315 parser.usage = help_str
316
317 (options, args) = parser.parse_args(sys.argv)
318 args = args[1:]
319
Simon Glassab344e32011-07-17 09:17:07 -0700320 out = cros_output.Output(options.verbosity)
321 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700322 if not args:
323 parser.error('No command provided')
324 elif args[0] == 'chromeos-version':
325 print tools.GetChromeosVersion()
326 else:
327 parser.error("Unknown command '%s'" % args[0])
328
329if __name__ == '__main__':
330 if sys.argv[1:2] == ["--test"]:
331 _Test(*sys.argv[2:])
332 else:
333 main()