binman: Handle repeated bytes for Python 3
[platform/kernel/u-boot.git] / tools / patman / tools.py
1 # SPDX-License-Identifier: GPL-2.0+
2 #
3 # Copyright (c) 2016 Google, Inc
4 #
5
6 import command
7 import glob
8 import os
9 import shutil
10 import sys
11 import tempfile
12
13 import tout
14
15 # Output directly (generally this is temporary)
16 outdir = None
17
18 # True to keep the output directory around after exiting
19 preserve_outdir = False
20
21 # Path to the Chrome OS chroot, if we know it
22 chroot_path = None
23
24 # Search paths to use for Filename(), used to find files
25 search_paths = []
26
27 # Tools and the packages that contain them, on debian
28 packages = {
29     'lz4': 'liblz4-tool',
30     }
31
32 # List of paths to use when looking for an input file
33 indir = []
34
35 def PrepareOutputDir(dirname, preserve=False):
36     """Select an output directory, ensuring it exists.
37
38     This either creates a temporary directory or checks that the one supplied
39     by the user is valid. For a temporary directory, it makes a note to
40     remove it later if required.
41
42     Args:
43         dirname: a string, name of the output directory to use to store
44                 intermediate and output files. If is None - create a temporary
45                 directory.
46         preserve: a Boolean. If outdir above is None and preserve is False, the
47                 created temporary directory will be destroyed on exit.
48
49     Raises:
50         OSError: If it cannot create the output directory.
51     """
52     global outdir, preserve_outdir
53
54     preserve_outdir = dirname or preserve
55     if dirname:
56         outdir = dirname
57         if not os.path.isdir(outdir):
58             try:
59                 os.makedirs(outdir)
60             except OSError as err:
61                 raise CmdError("Cannot make output directory '%s': '%s'" %
62                                 (outdir, err.strerror))
63         tout.Debug("Using output directory '%s'" % outdir)
64     else:
65         outdir = tempfile.mkdtemp(prefix='binman.')
66         tout.Debug("Using temporary directory '%s'" % outdir)
67
68 def _RemoveOutputDir():
69     global outdir
70
71     shutil.rmtree(outdir)
72     tout.Debug("Deleted temporary directory '%s'" % outdir)
73     outdir = None
74
75 def FinaliseOutputDir():
76     global outdir, preserve_outdir
77
78     """Tidy up: delete output directory if temporary and not preserved."""
79     if outdir and not preserve_outdir:
80         _RemoveOutputDir()
81
82 def GetOutputFilename(fname):
83     """Return a filename within the output directory.
84
85     Args:
86         fname: Filename to use for new file
87
88     Returns:
89         The full path of the filename, within the output directory
90     """
91     return os.path.join(outdir, fname)
92
93 def _FinaliseForTest():
94     """Remove the output directory (for use by tests)"""
95     global outdir
96
97     if outdir:
98         _RemoveOutputDir()
99
100 def SetInputDirs(dirname):
101     """Add a list of input directories, where input files are kept.
102
103     Args:
104         dirname: a list of paths to input directories to use for obtaining
105                 files needed by binman to place in the image.
106     """
107     global indir
108
109     indir = dirname
110     tout.Debug("Using input directories %s" % indir)
111
112 def GetInputFilename(fname):
113     """Return a filename for use as input.
114
115     Args:
116         fname: Filename to use for new file
117
118     Returns:
119         The full path of the filename, within the input directory
120     """
121     if not indir:
122         return fname
123     for dirname in indir:
124         pathname = os.path.join(dirname, fname)
125         if os.path.exists(pathname):
126             return pathname
127
128     raise ValueError("Filename '%s' not found in input path (%s) (cwd='%s')" %
129                      (fname, ','.join(indir), os.getcwd()))
130
131 def GetInputFilenameGlob(pattern):
132     """Return a list of filenames for use as input.
133
134     Args:
135         pattern: Filename pattern to search for
136
137     Returns:
138         A list of matching files in all input directories
139     """
140     if not indir:
141         return glob.glob(fname)
142     files = []
143     for dirname in indir:
144         pathname = os.path.join(dirname, pattern)
145         files += glob.glob(pathname)
146     return sorted(files)
147
148 def Align(pos, align):
149     if align:
150         mask = align - 1
151         pos = (pos + mask) & ~mask
152     return pos
153
154 def NotPowerOfTwo(num):
155     return num and (num & (num - 1))
156
157 def PathHasFile(fname):
158     """Check if a given filename is in the PATH
159
160     Args:
161         fname: Filename to check
162
163     Returns:
164         True if found, False if not
165     """
166     for dir in os.environ['PATH'].split(':'):
167         if os.path.exists(os.path.join(dir, fname)):
168             return True
169     return False
170
171 def Run(name, *args, **kwargs):
172     try:
173         return command.Run(name, *args, cwd=outdir, capture=True, **kwargs)
174     except:
175         if not PathHasFile(name):
176             msg = "Plesae install tool '%s'" % name
177             package = packages.get(name)
178             if package:
179                  msg += " (e.g. from package '%s')" % package
180             raise ValueError(msg)
181         raise
182
183 def Filename(fname):
184     """Resolve a file path to an absolute path.
185
186     If fname starts with ##/ and chroot is available, ##/ gets replaced with
187     the chroot path. If chroot is not available, this file name can not be
188     resolved, `None' is returned.
189
190     If fname is not prepended with the above prefix, and is not an existing
191     file, the actual file name is retrieved from the passed in string and the
192     search_paths directories (if any) are searched to for the file. If found -
193     the path to the found file is returned, `None' is returned otherwise.
194
195     Args:
196       fname: a string,  the path to resolve.
197
198     Returns:
199       Absolute path to the file or None if not found.
200     """
201     if fname.startswith('##/'):
202       if chroot_path:
203         fname = os.path.join(chroot_path, fname[3:])
204       else:
205         return None
206
207     # Search for a pathname that exists, and return it if found
208     if fname and not os.path.exists(fname):
209         for path in search_paths:
210             pathname = os.path.join(path, os.path.basename(fname))
211             if os.path.exists(pathname):
212                 return pathname
213
214     # If not found, just return the standard, unchanged path
215     return fname
216
217 def ReadFile(fname):
218     """Read and return the contents of a file.
219
220     Args:
221       fname: path to filename to read, where ## signifiies the chroot.
222
223     Returns:
224       data read from file, as a string.
225     """
226     with open(Filename(fname), 'rb') as fd:
227         data = fd.read()
228     #self._out.Info("Read file '%s' size %d (%#0x)" %
229                    #(fname, len(data), len(data)))
230     return data
231
232 def WriteFile(fname, data):
233     """Write data into a file.
234
235     Args:
236         fname: path to filename to write
237         data: data to write to file, as a string
238     """
239     #self._out.Info("Write file '%s' size %d (%#0x)" %
240                    #(fname, len(data), len(data)))
241     with open(Filename(fname), 'wb') as fd:
242         fd.write(data)
243
244 def GetBytes(byte, size):
245     """Get a string of bytes of a given size
246
247     This handles the unfortunate different between Python 2 and Python 2.
248
249     Args:
250         byte: Numeric byte value to use
251         size: Size of bytes/string to return
252
253     Returns:
254         A bytes type with 'byte' repeated 'size' times
255     """
256     if sys.version_info[0] >= 3:
257         data = bytes([byte]) * size
258     else:
259         data = chr(byte) * size
260     return data