blob: 38412a2ef20efbe79fa34d36a00de16411c8a713 [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
21import sys
22
23from cros_build_lib import RunCommandCaptureOutput
24import cros_build_lib
Simon Glassab344e32011-07-17 09:17:07 -070025import cros_output
Simon Glass50883f92011-07-12 16:19:16 -070026
27
28class CmdError(Exception):
29 """An error in the execution of a command."""
30 pass
31
32
33class Tools:
34 """A class to encapsulate the external tools we want to run
35
36 This provides convenient functions for running tools inside/outside the
37 chroot. It also provides common paths:
38
39 chroot_path: chroot directory
40 src_path: source directory
41 script_path: scripts directory (src/scripts)
42 overlay_path: overlays directory (src/overlays)
43 priv_overlay_path: private overlays directory (src/private-overlays)
44 board_path: build directory (/build in chroot)
45 third_party_path: third_parth directory (src/third_party)
46 cros_overlay_path: Chromium OS overlay (src/chromiumos-overlay)
47 """
Simon Glassab344e32011-07-17 09:17:07 -070048 def __init__(self, output):
Simon Glass50883f92011-07-12 16:19:16 -070049 """Set up the tools system.
50
51 Args:
Simon Glassab344e32011-07-17 09:17:07 -070052 output: cros_output object to use for output.
Simon Glass50883f92011-07-12 16:19:16 -070053 """
54 # Detect whether we're inside a chroot or not
55 self.in_chroot = cros_build_lib.IsInsideChroot()
Simon Glassab344e32011-07-17 09:17:07 -070056 self._out = output
Simon Glass64db3062011-07-14 21:58:54 -070057 self._root = None
58 if self.in_chroot:
59 root_dir = os.getenv('CROS_WORKON_SRCROOT')
60 else:
61 repo = cros_build_lib.FindRepoDir()
62 if not repo:
63 raise IOError('Cannot find .repo directory (must be below cwd level)')
64 root_dir = os.path.dirname(repo)
65 self._SetRoot(root_dir)
Simon Glass50883f92011-07-12 16:19:16 -070066
Simon Glassab344e32011-07-17 09:17:07 -070067 self._out.Info("Chroot is at '%s'" % self.chroot_path)
Simon Glass50883f92011-07-12 16:19:16 -070068 self._tools = {
69 'make_bmp_image' : '##/usr/share/vboot/bitmaps/make_bmp_images.sh'
70 }
71
72 def _SetRoot(self, root_dir):
73 """Sets the root directory for the build envionrment.
74
75 The root directory is the one containing .repo, chroot and src.
76
77 This should be called once the root is known. All other parts are
78 calculated from this.
79
80 Args:
Simon Glass05db7fd2011-07-14 22:02:19 -070081 root_dir: The path to the root directory.
Simon Glass50883f92011-07-12 16:19:16 -070082 """
83 self._root = os.path.normpath(root_dir)
84
85 # Set up the path to prepend to get to the chroot
86 if self.in_chroot:
87 self.chroot_path = '/'
88 else:
89 self.chroot_path = cros_build_lib.PrependChrootPath('')
90 self.src_path = os.path.join(self._root, 'src')
91 self.script_path = os.path.join(self.src_path, 'scripts')
92 self.overlay_path = os.path.join(self.src_path, 'overlays')
93 self.priv_overlay_path = os.path.join(self.src_path,
94 'private-overlays')
95 self.board_path = os.path.join(self.chroot_path, 'build')
96 self.third_party_path = os.path.join(self.src_path, 'third_party')
97 self.cros_overlay_path = os.path.join(self.third_party_path,
98 'chromiumos-overlay')
99
100 def Filename(self, fname):
101 """Resolve a chroot-relative filename to an absolute path.
102
103 This looks for ## at the beginning of the filename, and changes it to
104 the chroot directory, which will be / if inside the chroot, or a path
105 to the chroot if not.
106
107 Args:
108 fname: Filename to convert.
109
110 Returns
111 Absolute path to filename.
112 """
113 if fname.startswith('##/'):
114 fname = os.path.join(self.chroot_path, fname[3:])
115 return fname
116
117 def Run(self, tool, args, cwd=None):
118 """Run a tool with given arguments.
119
120 The tool name may be used unchanged or substituted with a full path if
121 required.
122
123 The tool and arguments can use ##/ to signify the chroot (at the beginning
124 of the tool/argument).
125
126 Args:
127 tool: Name of tool to run.
128 args: List of arguments to pass to tool.
129 cwd: Directory to change into before running tool (None if none).
130
131 Returns:
132 Output of tool (stdout).
133
134 Raises
135 CmdError if running the tool, or the tool itself creates an error"""
136 if tool in self._tools:
137 tool = self._tools[tool]
138 tool = self.Filename(tool)
139 args = [self.Filename(arg) for arg in args]
140 cmd = [tool] + args
141 try:
Simon Glassab344e32011-07-17 09:17:07 -0700142 rc, stdout, err = RunCommandCaptureOutput(cmd,
143 print_cmd=self._out.verbose > 3, cwd=cwd)
Simon Glass50883f92011-07-12 16:19:16 -0700144 except OSError:
145 raise CmdError('Command not found: %s' % (' '.join(cmd)))
146 if rc:
Simon Glassab344e32011-07-17 09:17:07 -0700147 raise CmdError('Command failed: %s\n%s' % (' '.join(cmd), stdout))
148 self._out.Debug(stdout)
149 return stdout
Simon Glass50883f92011-07-12 16:19:16 -0700150
151 def ReadFile(self, fname):
152 """Read and return the contents of a file.
153
154 Args:
155 fname: path to filename to read, where ## signifiies the chroot.
156
157 Returns:
158 data read from file, as a string.
159 """
160 fd = open(self.Filename(fname), 'rb')
161 data = fd.read()
162 fd.close()
Simon Glassab344e32011-07-17 09:17:07 -0700163 self._out.Info("Read file '%s' size %d (%#0x)" %
164 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700165 return data
166
167 def WriteFile(self, fname, data):
168 """Write data into a file.
169
170 Args:
171 fname: path to filename to write, where ## signifiies the chroot.
172 data: data to write to file, as a string.
173 """
Simon Glassab344e32011-07-17 09:17:07 -0700174 self._out.Info("Write file '%s' size %d (%#0x)" %
175 (fname, len(data), len(data)))
Simon Glass50883f92011-07-12 16:19:16 -0700176 fd = open(self.Filename(fname), 'wb')
177 fd.write(data)
178 fd.close()
179
180 def GetChromeosVersion(self):
181 """Returns the ChromeOS version string
182
183 This works by finding and executing the version script:
184
185 src/third_party/chromiumos-overlay/chromeos/config/chromeos_version.sh
186
187 Returns:
188 Version string in the form '0.14.726.2011_07_07_1635'
189
190 Raises:
191 CmdError: If the version script cannot be found, or is found but cannot
192 be executed.
193 """
194 version_script = os.path.join(self.cros_overlay_path, 'chromeos', 'config',
195 'chromeos_version.sh')
196
197 if os.path.exists(version_script):
198 str = self.Run('sh', ['-c', '. %s >/dev/null; '
199 'echo ${CHROMEOS_VERSION_STRING}'
200 % version_script])
201 return str.strip()
202 raise CmdError("Cannot find version script 'chromeos_version.sh'")
203
204 def CheckTool(self, filename, ebuild=None):
205 """Check that the specified tool exists.
206
207 If it does not exist in the PATH, then generate a useful error message
208 indicating how to install the ebuild that contains the required tool.
209
210 Args:
211 filename: filename of tool to look for on path.
212 ebuild: name of ebuild which should be emerged to install this tool,
213 or None if it is the same as the filename.
214
215 Raises:
216 CmdError(msg) if the tool is not found.
217 """
218 try:
219 if filename in self._tools:
220 filename = self._tools[filename]
221 filename = self.Filename(filename)
222 self.Run('which', [filename])
223 except CmdError as err:
224 raise CmdError("The '%s' utility was not found in your path. "
225 "Run the following command in \nyour chroot to install it: "
226 "sudo -E emerge %s" % (filename, ebuild or filename))
227
Simon Glassab344e32011-07-17 09:17:07 -0700228 def OutputSize(self, label, filename, level=cros_output.NOTICE):
229 """Display the filename and size of an object.
230
231 Args:
232 label: Label for this file.
233 filename: Filename to output.
234 level: Verbosity level to attach to this message
235 """
236 filename = self.Filename(filename)
237 size = os.stat(filename).st_size
238 self._out.DoOutput(level, "%s: %s; size: %d / %#x" %
239 (label, filename, size, size))
240
Simon Glass50883f92011-07-12 16:19:16 -0700241def _Test():
242 """Run any built-in tests."""
243 import doctest
244 doctest.testmod()
245
246def main():
247 """Main function for tools.
248
249 We provide a way to call a few of our useful functions.
250
251 TODO(sjg) Move into the Chromite libraries when these are ready.
252 """
253 parser = optparse.OptionParser()
254 parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
255 type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
256 '4=debug')
257
258 help_str = '%s [options] cmd [args]\n\nAvailable commands:\n' % sys.argv[0]
259 help_str += '\tchromeos-version\tDisplay Chrome OS version'
260 parser.usage = help_str
261
262 (options, args) = parser.parse_args(sys.argv)
263 args = args[1:]
264
Simon Glassab344e32011-07-17 09:17:07 -0700265 out = cros_output.Output(options.verbosity)
266 tools = Tools(out)
Simon Glass50883f92011-07-12 16:19:16 -0700267 if not args:
268 parser.error('No command provided')
269 elif args[0] == 'chromeos-version':
270 print tools.GetChromeosVersion()
271 else:
272 parser.error("Unknown command '%s'" % args[0])
273
274if __name__ == '__main__':
275 if sys.argv[1:2] == ["--test"]:
276 _Test(*sys.argv[2:])
277 else:
278 main()