conan-io/conan

View on GitHub
conans/model/editable_layout.py

Summary

Maintainability
A
0 mins
Test Coverage
# coding=utf-8
import os
from collections import OrderedDict

import six
from six.moves import configparser

from conans.errors import ConanException
from conans.model.ref import ConanFileReference, check_valid_ref
from conans.util.files import load
from conans.util.templates import render_layout_file

DEFAULT_LAYOUT_FILE = "default"
LAYOUTS_FOLDER = 'layouts'


def get_editable_abs_path(path, cwd, cache_folder):
    # Check the layout file exists, is correct, and get its abs-path
    if path:
        layout_abs_path = os.path.normpath(os.path.join(cwd, path))
        if not os.path.isfile(layout_abs_path):
            layout_abs_path = os.path.join(cache_folder, LAYOUTS_FOLDER, path)
        if not os.path.isfile(layout_abs_path):
            raise ConanException("Couldn't find layout file: %s" % path)
        return layout_abs_path

    # Default only in cache
    layout_default_path = os.path.join(cache_folder, LAYOUTS_FOLDER, DEFAULT_LAYOUT_FILE)
    if os.path.isfile(layout_default_path):
        return layout_default_path


class EditableLayout(object):
    BUILD_FOLDER = "build_folder"
    SOURCE_FOLDER = "source_folder"
    cpp_info_dirs = ['includedirs', 'libdirs', 'resdirs', 'bindirs', 'builddirs', 'srcdirs', 'frameworkdirs']
    folders = [BUILD_FOLDER, SOURCE_FOLDER]

    def __init__(self, filepath):
        self._filepath = filepath

    def folder(self, ref, name, settings, options):
        _, folders = self._load_data(ref, settings=settings, options=options)
        try:
            path = folders.get(str(ref)) or folders.get(None) or {}
            return path[name]
        except KeyError:
            return None

    @staticmethod
    def _work_on_item(value):
        value = value.replace('\\', '/')
        return value

    def _parse_layout_file(self, ref, settings, options):
        content = load(self._filepath)
        try:
            content = render_layout_file(content, ref=ref, settings=settings, options=options)

            parser = configparser.ConfigParser(allow_no_value=True)
            parser.optionxform = str
            if six.PY3:
                parser.read_string(content)
            else:
                parser.readfp(six.StringIO(content))
        except (configparser.Error, ConanException) as e:
            raise ConanException("Error parsing layout file '%s' (for reference '%s')\n%s" %
                                 (self._filepath, str(ref), str(e)))

        return parser

    def _load_data(self, ref, settings, options):
        parser = self._parse_layout_file(ref, settings, options)

        # Build a convenient data structure
        data = OrderedDict()
        folders = {}
        for section in parser.sections():
            reference, section_name = section.rsplit(":", 1) if ':' in section else (None, section)

            if section_name in EditableLayout.folders:
                items = [k for k, _ in parser.items(section)] or [""]
                if len(items) > 1:
                    raise ConanException("'%s' with more than one value in layout file: %s"
                                         % (section_name, self._filepath))
                folders.setdefault(reference, {})[section_name] = self._work_on_item(items[0])
                continue

            if section_name not in EditableLayout.cpp_info_dirs:
                raise ConanException("Wrong cpp_info field '%s' in layout file: %s"
                                     % (section_name, self._filepath))
            if reference:
                if not check_valid_ref(reference):
                    raise ConanException("Wrong package reference '%s' in layout file: %s"
                                         % (reference, self._filepath))
                else:
                    r = ConanFileReference.loads(reference, validate=True)
                    if r.revision:
                        raise ConanException("Don't provide revision in Editable layouts")

            data.setdefault(reference, {})[section_name] =\
                [self._work_on_item(k) for k, _ in parser.items(section)]
        return data, folders

    def apply_to(self, ref, cpp_info, settings=None, options=None):
        data, _ = self._load_data(ref, settings=settings, options=options)

        # Apply the data to the cpp_info
        data = data.get(str(ref)) or data.get(None) or {}

        try:
            for key, items in data.items():
                setattr(cpp_info, key, items)
        except Exception as e:
            raise ConanException("Error applying layout in '%s': %s" % (str(ref), str(e)))