| # |
| # Copyright (C) 2016 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| |
| """A class describing a sysroot.""" |
| |
| |
| import glob |
| import os |
| import shutil |
| |
| |
| class Sysroot(object): |
| """A wrapper around basic os/file system operations as pertains to a |
| sysroot. |
| |
| Provides access to copying in files to a sysroot, making directories, or |
| writing new files without needing to know exactly where the sysroot is |
| based. |
| |
| Attributes: |
| path - The path at which the sysroot is based. |
| copy_newer_only - only copy files if the src is newer. |
| """ |
| |
| def __init__(self, path, copy_newer_only=False): |
| self.path = path |
| if not os.path.isdir(path): |
| os.makedirs(path) |
| self._copy_newer_only = copy_newer_only |
| |
| def Destroy(self): |
| """Remove the sysroot and all subdirs.""" |
| shutil.rmtree(self.path) |
| |
| def __str__(self): |
| return self.path |
| |
| def Path(self, *args): |
| """Get an absolute path given a path relative to the sysroot.""" |
| return os.path.join(self.path, *args) |
| |
| def Makedirs(self, *path): |
| """Make directories under the sysroot. |
| |
| Only creates directories if they don't already exist. |
| """ |
| if not self.HasDir(*path): |
| os.makedirs(self.Path(*path)) |
| |
| def HasDir(self, *path): |
| """Checks if the sysroot has a given directory.""" |
| return os.path.isdir(self.Path(*path)) |
| |
| def _src_is_newer(self, src, real_dest): |
| """Returns True is the src should replace the dest.""" |
| if self._copy_newer_only == False: |
| return True |
| # If the src doesn't exist, we force the copy such that |
| # the caller can raise the appropriate exception. |
| if not os.path.exists(src): |
| return True |
| if not os.path.exists(real_dest): |
| return True |
| src_st = os.stat(src) |
| return src_st.st_mtime - os.stat(real_dest).st_mtime > 0 |
| |
| def _copy_times(self, src, real_dest): |
| """Nearly synchronizes the atime and mtime of the src and dest. |
| |
| To enable reliable copy_newer_only behavior, we always set |
| the mtime to 1 second later than the original to offset |
| precision issues. Because of those issues, it is beneficial |
| to call _copy_times even after copy2 or copystat. |
| """ |
| if os.path.islink(src): |
| # Until os.futime/os.lutime, symlink times cannot be changed. |
| return |
| src_st = os.lstat(src) |
| os.utime(real_dest, (src_st.st_atime, src_st.st_mtime + 1)) |
| |
| def AddFile(self, src, dest=''): |
| """Copies a file from src to dest. |
| |
| Args: |
| src: the file to copy. |
| dest: where to copy to. If a directory, uses the filename from src. |
| The dest directory must already exist. |
| Raises: |
| IOError if the copy fails. |
| """ |
| |
| try: |
| tgt = self.Path(dest) |
| if self._src_is_newer(src, tgt): |
| shutil.copy2(src, tgt) |
| self._copy_times(src, tgt) |
| except (IOError, OSError) as e: |
| raise IOError(('Failed to add file {0} to ' |
| 'sysroot {1} dest {2}: {3}').format( |
| src, self, dest, e)) |
| |
| def AddSymlink(self, src, dest=''): |
| """Creates a symlink at dest using src. |
| |
| Args: |
| src - the symlink to copy. |
| dest - where to create a symlink. If a directory, uses the filename |
| from src. The dest directory must already exist. |
| |
| Raises: |
| IOError if the symlink fails. |
| """ |
| try: |
| tgt = self.Path(dest) |
| if self._src_is_newer(src, tgt): |
| linkto = os.readlink(src) |
| os.symlink(linkto, tgt) |
| except (IOError, OSError) as e: |
| raise IOError(('Failed to add symlink {0} to ' |
| 'sysroot {1} dest {2}: {3}').format( |
| src, self, dest, e)) |
| |
| def AddDir(self, src, dest='', recurse=True, filter_func=None, |
| symlinks=False): |
| """Copies all folders and files from external src to dest in sysroot. |
| |
| This function does not use shutil.copytree since that function |
| does not support copying (and merging) into existing folders, but |
| the implementation is based on the copytree implementation provided |
| in the python documentation. |
| |
| Does not require dest dir to exist. |
| |
| Args: |
| src - dir to copy files from (absolute path) |
| dest - (optional) dir to copy files to (relative to self.path). |
| Default ''. |
| recurse - (optional) True to recursively add subdirs. Default True. |
| filter - (optional) a function for filenames that returns true if |
| the file should be copied, false otherwise. Default identity |
| function. |
| symlinks - (optional) True to copy symlinks as symlinks. Default |
| False. |
| |
| Returns: |
| List of added files |
| |
| Raises: |
| IOError if any part of the copy fails. |
| """ |
| # Default filter is to not filter anything. |
| filter_func = filter_func or bool |
| |
| # Copy the dir. |
| self.Makedirs(dest) |
| shutil.copystat(src, self.Path(dest)) |
| |
| # Copy the files. |
| names = os.listdir(src) |
| errors = [] |
| added = [] |
| for name in names: |
| srcname = os.path.join(src, name) |
| destname = os.path.join(dest, name) |
| try: |
| if symlinks and os.path.islink(srcname): |
| self.AddSymlink(srcname, destname) |
| added.append(destname) |
| elif os.path.isdir(srcname): |
| if recurse: |
| added += self.AddDir(srcname, destname, recurse, |
| filter_func, symlinks) |
| elif filter_func(name): |
| added.append(destname) |
| self.AddFile(srcname, destname) |
| except IOError as e: |
| errors.append('Failed to copy {0} to {1}: {2}'.format(srcname, |
| destname, |
| e)) |
| |
| if errors: |
| raise IOError(str(errors)) |
| return added |
| |
| def AddGlob(self, src, dest='', recurse=False, filter_func=None, |
| symlinks=False): |
| """Copies matching folders and files from external src to dest in |
| sysroot. |
| |
| This function uses AddDir and AddFile above. |
| |
| Does not require dest dir to exist. |
| |
| Args: |
| src - glob to copy files from (absolute glob path) |
| dest - (optional) dir to copy files to (relative to self.path). |
| Default ''. |
| recurse - (optional) True to recursively add subdirs. Default False. |
| filter - (optional) a function for filenames that returns true if |
| the file should be copied, false otherwise. Default identity |
| function. |
| symlinks - (optional) True to copy symlinks as symlinks. Default |
| False. |
| |
| Returns: |
| List of added files |
| |
| Raises: |
| IOError if any part of the copy fails. |
| """ |
| # Default filter is to not filter anything. |
| filter_func = filter_func or bool |
| |
| added = [] |
| for srcname in glob.glob(src): |
| name = os.path.basename(srcname) |
| destname = os.path.join(dest, name) |
| if symlinks and os.path.islink(srcname): |
| if filter_func(name): |
| self.AddSymlink(srcname, destname) |
| added.append(destname) |
| elif os.path.isdir(srcname): |
| if recurse: |
| added += self.AddDir(srcname, destname, recurse=True, |
| symlinks=symlinks) |
| elif filter_func(name): |
| added.append(destname) |
| self.Makedirs(os.path.dirname(destname)) |
| self.AddFile(srcname, destname) |
| |
| if len(added) == 0: |
| raise IOError(('Failed to add glob {0} to ' |
| 'sysroot {1} dest {2}: no matching files').format( |
| src, self, dest)) |
| |
| return added |
| |
| def WriteFile(self, dest, contents): |
| """Write a new file in the sysroot. |
| |
| Args: |
| dest - the path (relative to the sysroot) to add the file to. |
| contents - the desired contents of the file. |
| Raises: |
| IOError if the write fails. |
| """ |
| try: |
| self.Makedirs(os.path.dirname(dest)) |
| with open(self.Path(dest), 'w') as f: |
| f.write(contents) |
| except IOError as e: |
| raise IOError('Failed to write file {1} in sysroot {2}: {3}'.format( |
| dest, self, e)) |