Build step fails on special files
Metadata
Current evaluation
No evaluation has been recorded for this issue yet.
Issue body
### Bug Description
The build step uses `shutil.copytree` which fails on special files. See [failing test](https://github.com/canonical/craft-parts/actions/runs/22070744732/job/63774393159?pr=1415#step:4:145)
### To Reproduce
Run build step with special file in source directory
### Relevant log output
```shell
/home/runner/work/craft-parts/craft-parts/tests/integration/executor/test_organize.py:60:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/home/runner/work/craft-parts/craft-parts/craft_parts/executor/executor.py:353: in execute
self._executor.execute(actions, stdout=stdout, stderr=stderr)
/home/runner/work/craft-parts/craft-parts/craft_parts/executor/executor.py:153: in execute
self._run_action(act, stdout=stdout, stderr=stderr)
/home/runner/work/craft-parts/craft-parts/craft_parts/executor/executor.py:246: in _run_action
handler.run_action(action, stdout=stdout, stderr=stderr)
/home/runner/work/craft-parts/craft-parts/craft_parts/executor/part_handler.py:313: in run_action
state = handler(step_info, stdout=stdout, stderr=stderr)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/home/runner/work/craft-parts/craft-parts/craft_parts/executor/part_handler.py:463: in _run_build
shutil.copytree(
/usr/lib/python3.12/shutil.py:600: in copytree
return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
entries = [<DirEntry 'bar.fifo'>, <DirEntry 'dev'>, <DirEntry 'qux'>, <DirEntry 'foo.txt'>]
src = PosixPath('/tmp/pytest-of-root/pytest-0/test_organize_special_files0/parts/p1/src')
dst = PosixPath('/tmp/pytest-of-root/pytest-0/test_organize_special_files0/parts/p1/build')
symlinks = True, ignore = None
copy_function = <function copy2 at 0x7f8dc5222c00>
ignore_dangling_symlinks = False, dirs_exist_ok = False
def _copytree(entries, src, dst, symlinks, ignore, copy_function,
ignore_dangling_symlinks, dirs_exist_ok=False):
if ignore is not None:
ignored_names = ignore(os.fspath(src), [x.name for x in entries])
else:
ignored_names = ()
os.makedirs(dst, exist_ok=dirs_exist_ok)
errors = []
use_srcentry = copy_function is copy2 or copy_function is copy
for srcentry in entries:
if srcentry.name in ignored_names:
continue
srcname = os.path.join(src, srcentry.name)
dstname = os.path.join(dst, srcentry.name)
srcobj = srcentry if use_srcentry else srcname
try:
is_symlink = srcentry.is_symlink()
if is_symlink and os.name == 'nt':
# Special check for directory junctions, which appear as
# symlinks but we want to recurse.
lstat = srcentry.stat(follow_symlinks=False)
if lstat.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT:
is_symlink = False
if is_symlink:
linkto = os.readlink(srcname)
if symlinks:
# We can't just leave it to `copy_function` because legacy
# code with a custom `copy_function` may rely on copytree
# doing the right thing.
os.symlink(linkto, dstname)
copystat(srcobj, dstname, follow_symlinks=not symlinks)
else:
# ignore dangling symlink if the flag is on
if not os.path.exists(linkto) and ignore_dangling_symlinks:
continue
# otherwise let the copy occur. copy2 will raise an error
if srcentry.is_dir():
copytree(srcobj, dstname, symlinks, ignore,
copy_function, ignore_dangling_symlinks,
dirs_exist_ok)
else:
copy_function(srcobj, dstname)
elif srcentry.is_dir():
copytree(srcobj, dstname, symlinks, ignore, copy_function,
ignore_dangling_symlinks, dirs_exist_ok)
else:
# Will raise a SpecialFileError for unsupported file types
copy_function(srcobj, dstname)
# catch the Error from the recursive copytree so that we can
# continue with other files
except Error as err:
errors.extend(err.args[0])
except OSError as why:
errors.append((srcname, dstname, str(why)))
try:
copystat(src, dst)
except OSError as why:
# Copying file access times may fail on Windows
if getattr(why, 'winerror', None) is None:
errors.append((src, dst, str(why)))
if errors:
> raise Error(errors)
E shutil.Error: [('/tmp/pytest-of-root/pytest-0/test_organize_special_files0/parts/p1/src/bar.fifo', '/tmp/pytest-of-root/pytest-0/test_organize_special_files0/parts/p1/build/bar.fifo', '`/tmp/pytest-of-root/pytest-0/test_organize_special_files0/parts/p1/src/bar.fifo` is a named pipe')]
```
Evaluation history
No evaluation history available.