conans/client/importer.py
import fnmatch
import os
import stat
from conans.client import tools
from conans.client.file_copier import FileCopier, report_copied_files
from conans.client.output import ScopedOutput
from conans.errors import ConanException
from conans.model.conan_file import get_env_context_manager
from conans.model.manifest import FileTreeManifest
from conans.util.dates import timestamp_now
from conans.util.env_reader import get_env
from conans.util.files import load, md5sum, mkdir
IMPORTS_MANIFESTS = "conan_imports_manifest.txt"
def undo_imports(current_path, output):
manifest_path = os.path.join(current_path, IMPORTS_MANIFESTS)
try:
manifest_content = load(manifest_path)
except Exception:
raise ConanException("Cannot load file %s" % manifest_path)
try:
manifest = FileTreeManifest.loads(manifest_content)
except Exception:
raise ConanException("Wrong manifest file format %s" % manifest_path)
not_removed = 0
files = manifest.files()
for filepath in files:
if not os.path.exists(filepath):
output.warn("File doesn't exist: %s" % filepath)
continue
try:
os.remove(filepath)
except OSError:
output.error("Cannot remove file (open or busy): %s" % filepath)
not_removed += 1
if not_removed:
raise ConanException("Cannot remove %s or more imported files" % not_removed)
output.success("Removed %s imported files" % (len(files)))
try:
os.remove(manifest_path)
output.success("Removed imports manifest file: %s" % manifest_path)
except Exception:
raise ConanException("Cannot remove manifest file (open or busy): %s" % manifest_path)
def _report_save_manifest(copied_files, output, dest_folder, manifest_name):
report_copied_files(copied_files, output)
if copied_files:
date = timestamp_now()
file_dict = {}
for f in copied_files:
abs_path = os.path.join(dest_folder, f)
file_dict[f] = md5sum(abs_path)
manifest = FileTreeManifest(date, file_dict)
manifest.save(dest_folder, manifest_name)
def _make_files_writable(file_names):
if not get_env("CONAN_READ_ONLY_CACHE", False):
return
for file_name in file_names:
os.chmod(file_name, os.stat(file_name).st_mode | stat.S_IWRITE)
def run_imports(conanfile):
if not hasattr(conanfile, "imports"):
return []
mkdir(conanfile.imports_folder)
file_importer = _FileImporter(conanfile, conanfile.imports_folder)
conanfile.copy = file_importer
with get_env_context_manager(conanfile):
with tools.chdir(conanfile.imports_folder):
conanfile.imports()
copied_files = file_importer.copied_files
_make_files_writable(copied_files)
import_output = ScopedOutput("%s imports()" % conanfile.display_name, conanfile.output)
_report_save_manifest(copied_files, import_output, conanfile.imports_folder, IMPORTS_MANIFESTS)
return copied_files
def remove_imports(conanfile, copied_files, output):
if not getattr(conanfile, "keep_imports", False):
for f in copied_files:
try:
os.remove(f)
except OSError:
output.warn("Unable to remove imported file from build: %s" % f)
def run_deploy(conanfile, install_folder):
deploy_output = ScopedOutput("%s deploy()" % conanfile.display_name, conanfile.output)
file_importer = _FileImporter(conanfile, install_folder)
package_copied = set()
# This is necessary to capture FileCopier full destination paths
# Maybe could be improved in FileCopier
def file_copier(*args, **kwargs):
file_copy = FileCopier([conanfile.package_folder], install_folder)
copied = file_copy(*args, **kwargs)
_make_files_writable(copied)
package_copied.update(copied)
conanfile.copy_deps = file_importer
conanfile.copy = file_copier
conanfile.folders.set_base_install(install_folder)
with get_env_context_manager(conanfile):
with tools.chdir(install_folder):
conanfile.deploy()
copied_files = file_importer.copied_files
copied_files.update(package_copied)
_report_save_manifest(copied_files, deploy_output, install_folder, "deploy_manifest.txt")
class _FileImporter(object):
""" manages the copy of files, resources, libs from the local store to the user
space. E.g.: shared libs, dlls, they will be in the package folder of your
configuration in the store. But you dont want to add every package to the
system PATH. Those shared libs can be copied to the user folder, close to
the exes where they can be found without modifying the path.
Useful also for copying other resources as images or data files.
It can be also used for Golang projects, in which the packages are always
source based and need to be copied to the user folder to be built
"""
def __init__(self, conanfile, dst_folder):
self._conanfile = conanfile
self._dst_folder = dst_folder
self.copied_files = set()
def __call__(self, pattern, dst="", src="", root_package=None, folder=False,
ignore_case=True, excludes=None, keep_path=True):
"""
param pattern: an fnmatch file pattern of the files that should be copied. Eg. *.dll
param dst: the destination local folder, wrt to current conanfile dir, to which
the files will be copied. Eg: "bin"
param src: the source folder in which those files will be searched. This folder
will be stripped from the dst name. Eg.: lib/Debug/x86
param root_package: fnmatch pattern of the package name ("OpenCV", "Boost") from
which files will be copied. Default: all packages in deps
"""
if os.path.isabs(dst):
real_dst_folder = dst
else:
real_dst_folder = os.path.normpath(os.path.join(self._dst_folder, dst))
pkgs = (self._conanfile.deps_cpp_info.dependencies if not root_package else
[(pkg, cpp_info) for pkg, cpp_info in self._conanfile.deps_cpp_info.dependencies
if fnmatch.fnmatch(pkg, root_package)])
symbolic_dir_name = src[1:] if src.startswith("@") else None
src_dirs = [src] # hardcoded src="bin" origin
for pkg_name, cpp_info in pkgs:
final_dst_path = os.path.join(real_dst_folder, pkg_name) if folder else real_dst_folder
file_copier = FileCopier([cpp_info.rootpath], final_dst_path)
if symbolic_dir_name: # Syntax for package folder symbolic names instead of hardcoded
try:
src_dirs = getattr(cpp_info, symbolic_dir_name)
if not isinstance(src_dirs, list): # it can return a "config" CppInfo item!
raise AttributeError
except AttributeError:
raise ConanException("Import from unknown package folder '@%s'"
% symbolic_dir_name)
if cpp_info.components:
for comp_name, comp in cpp_info.components.items():
src_dir = getattr(comp, symbolic_dir_name)
if isinstance(src_dirs, list): # it can return a "config" CppInfo item!
src_dirs += src_dir
for src_dir in src_dirs:
files = file_copier(pattern, src=src_dir, links=True, ignore_case=ignore_case,
excludes=excludes, keep_path=keep_path)
self.copied_files.update(files)