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