blob: 586f5a646e2dc63640e2509b2b03a06c1353b937 [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
81 def __del__(self):
82 self.FinalizeOutputDir()
Simon Glass50883f92011-07-12 16:19:16 -070083
84 def _SetRoot(self, root_dir):
85 """Sets the root directory for the build envionrment.
86
87 The root directory is the one containing .repo, chroot and src.
88
89 This should be called once the root is known. All other parts are
90 calculated from this.
91
92 Args:
Simon Glass05db7fd2011-07-14 22:02:19 -070093 root_dir: The path to the root directory.
Simon Glass50883f92011-07-12 16:19:16 -070094 """
95 self._root = os.path.normpath(root_dir)
96
97 # Set up the path to prepend to get to the chroot
98 if self.in_chroot:
99 self.chroot_path = '/'
100 else:
101 self.chroot_path = cros_build_lib.PrependChrootPath('')
102 self.src_path = os.path.join(self._root, 'src')
103 self.script_path = os.path.join(self.src_path, 'scripts')
104 self.overlay_path = os.path.join(self.src_path, 'overlays')
105 self.priv_overlay_path = os.path.join(self.src_path,
106 'private-overlays')
107 self.board_path = os.path.join(self.chroot_path, 'build')
108 self.third_party_path = os.path.join(self.src_path, 'third_party')
109 self.cros_overlay_path = os.path.join(self.third_party_path,
110 'chromiumos-overlay')
111
112 def Filename(self, fname):
113 """Resolve a chroot-relative filename to an absolute path.
114
115 This looks for ## at the beginning of the filename, and changes it to
116 the chroot directory, which will be / if inside the chroot, or a path
117 to the chroot if not.
118
119 Args:
120 fname: Filename to convert.
121
122 Returns
123 Absolute path to filename.
124 """
125 if fname.startswith('##/'):
126 fname = os.path.join(self.chroot_path, fname[3:])
127 return fname
128
129 def Run(self, tool, args, cwd=None):
130 """Run a tool with given arguments.
131
132 The tool name may be used unchanged or substituted with a full path if
133 required.
134
135 The tool and arguments can use ##/ to signify the chroot (at the beginning
136 of the tool/argument).
137
138 Args:
139 tool: Name of tool to run.
140 args: List of arguments to pass to tool.
141 cwd: Directory to change into before running tool (None if none).
142
143 Returns:
144 Output of tool (stdout).
145
146 Raises
147 CmdError if running the tool, or the tool itself creates an error"""
148 if tool in self._tools:
149 tool = self._tools[tool]
150 tool = self.Filename(tool)
151 args = [self.Filename(arg) for arg in args]
152 cmd = [tool] + args
153 try:
Simon Glassab344e32011-07-17 09:17:07 -0700154 rc, stdout, err = RunCommandCaptureOutput(cmd,
155 print_cmd=self._out.verbose > 3, cwd=cwd)
Simon Glass50883f92011-07-12 16:19:16 -0700156 except OSError:
157 raise CmdError('Command not found: %s' % (' '.join(cmd)))
158 if rc:
Simon Glassab344e32011-07-17 09:17:07 -0700159 raise CmdError('Command failed: %s\n%s' % (' '.join(cmd), stdout))
160 self._out.Debug(stdout)
161 return stdout
Simon Glass50883f92011-07-12 16:19:16 -0700162
163 def ReadFile(self, fname):
164 """Read and return the contents of a file.
165
166 Args:
167 fname: path to filename to read, where ## signifiies the chroot.
168
169 Returns:
170 data read from file, as a string.
171 """
172 fd = open(self.Filename(fname), 'rb')
173 data = fd.read()
174 fd.close()
Simon Glassab344e32011-07-17 09:17:07 -0700175 self._out.Info("Read file '%s' size %d (%#0x)" %
176 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700177 return data
178
179 def WriteFile(self, fname, data):
180 """Write data into a file.
181
182 Args:
183 fname: path to filename to write, where ## signifiies the chroot.
184 data: data to write to file, as a string.
185 """
Simon Glassab344e32011-07-17 09:17:07 -0700186 self._out.Info("Write file '%s' size %d (%#0x)" %
187 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700188 fd = open(self.Filename(fname), 'wb')
189 fd.write(data)
190 fd.close()
191
192 def GetChromeosVersion(self):
193 """Returns the ChromeOS version string
194
195 This works by finding and executing the version script:
196
197 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
198
199 Returns:
200 Version string in the form '0.14.726.2011_07_07_1635'
201
202 Raises:
203 CmdError: If the version script cannot be found, or is found but cannot
204 be executed.
205 """
206 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
207 'chromeos_version.sh')
208
209 if os.path.exists(version_script):
210 str = self.Run('sh', ['-c', '. %s >/dev/null; '
211 'echo ${CHROMEOS_VERSION_STRING}'
212 % version_script])
213 return str.strip()
214 raise CmdError("Cannot find version script 'chromeos_version.sh'")
215
216 def CheckTool(self, filename, ebuild=None):
217 """Check that the specified tool exists.
218
219 If it does not exist in the PATH, then generate a useful error message
220 indicating how to install the ebuild that contains the required tool.
221
222 Args:
223 filename: filename of tool to look for on path.
224 ebuild: name of ebuild which should be emerged to install this tool,
225 or None if it is the same as the filename.
226
227 Raises:
228 CmdError(msg) if the tool is not found.
229 """
230 try:
231 if filename in self._tools:
232 filename = self._tools[filename]
233 filename = self.Filename(filename)
234 self.Run('which', [filename])
235 except CmdError as err:
236 raise CmdError("The '%s' utility was not found in your path. "
237 "Run the following command in \nyour chroot to install it: "
238 "sudo -E emerge %s" % (filename, ebuild or filename))
239
Simon Glassab344e32011-07-17 09:17:07 -0700240 def OutputSize(self, label, filename, level=cros_output.NOTICE):
241 """Display the filename and size of an object.
242
243 Args:
244 label: Label for this file.
245 filename: Filename to output.
246 level: Verbosity level to attach to this message
247 """
248 filename = self.Filename(filename)
249 size = os.stat(filename).st_size
250 self._out.DoOutput(level, "%s: %s; size: %d / %#x" %
251 (label, filename, size, size))
252
Simon Glass951a2db2011-07-17 15:58:58 -0700253 def PrepareOutputDir(self, outdir, preserve=False):
254 """Select an output directory, ensuring it exists.
255
256 This either creates a temporary directory or checks that the one supplied
257 by the user is valid. For a temporary directory, it makes a note to
258 remove it later if required.
259
260 Args:
261 outdir: Output directory to use, or None to use a temporary dir.
262
263 Raises:
264 OSError: If it cannot create the output directory.
265 """
266 self.outdir = outdir
267 self.preserve_outdir = preserve
268 if self.outdir:
269 if not os.path.isdir(self.outdir):
270 try:
271 os.makedirs(self.outdir)
272 except OSError as err:
273 raise CmdError("Cannot make output directory '%s': '%s'" %
274 (self.outdir, err))
275
276 else:
277 self.outdir = tempfile.mkdtemp()
278 self._delete_tempdir = self.outdir
Simon Glass290a1802011-07-17 13:54:32 -0700279 self._out.Debug("Using temporary directory '%s'" %
280 self._delete_tempdir)
Simon Glass951a2db2011-07-17 15:58:58 -0700281
282 def FinalizeOutputDir(self):
283 """Tidy up the output direcory, deleting it if temporary"""
284 if self._delete_tempdir and not self.preserve_outdir:
285 shutil.rmtree(self._delete_tempdir)
286 self._out.Debug("Deleted temporary directory '%s'" %
287 self._delete_tempdir)
288 self._delete_tempdir = None
289 elif self.outdir:
290 self._out.Debug("Output directory '%s'" % self.outdir)
291
Simon Glass50883f92011-07-12 16:19:16 -0700292def _Test():
293 """Run any built-in tests."""
294 import doctest
295 doctest.testmod()
296
297def main():
298 """Main function for tools.
299
300 We provide a way to call a few of our useful functions.
301
302 TODO(sjg) Move into the Chromite libraries when these are ready.
303 """
304 parser = optparse.OptionParser()
305 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
306 type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
307 '4=debug')
308
309 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
310 help_str += '\tchromeos-version\tDisplay Chrome OS version'
311 parser.usage = help_str
312
313 (options, args) = parser.parse_args(sys.argv)
314 args = args[1:]
315
Simon Glassab344e32011-07-17 09:17:07 -0700316 out = cros_output.Output(options.verbosity)
317 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700318 if not args:
319 parser.error('No command provided')
320 elif args[0] == 'chromeos-version':
321 print tools.GetChromeosVersion()
322 else:
323 parser.error("Unknown command '%s'" % args[0])
324
325if __name__ == '__main__':
326 if sys.argv[1:2] == ["--test"]:
327 _Test(*sys.argv[2:])
328 else:
329 main()