blob: 7d962fdaee015ff3e3caae2bcd19f1cf3da77a93 [file] [log] [blame]
#
# 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))