import imp
import inspect
import os
import sys
import uuid

from conans.client.generators import registered_generators
from conans.client.loader_txt import ConanFileTextLoader
from conans.client.tools.files import chdir
from conans.errors import ConanException, NotFoundException
from conans.model.conan_file import ConanFile
from conans.model.conan_generator import Generator
from conans.model.options import OptionsValues
from conans.model.ref import ConanFileReference
from conans.model.settings import Settings
from conans.model.values import Values
from conans.util.files import load


class ProcessedProfile(object):
    def __init__(self, profile, create_reference=None):
        self._settings = profile.processed_settings
        self._user_options = profile.options.copy()

        self._package_settings = profile.package_settings_values
        self._env_values = profile.env_values
        # Make sure the paths are normalized first, so env_values can be just a copy
        self._dev_reference = create_reference


class ConanFileLoader(object):
    def __init__(self, runner, output, python_requires):
        self._runner = runner
        self._output = output
        self._python_requires = python_requires
        sys.modules["conans"].python_requires = python_requires
        self.cached_conanfiles = {}

    def invalidate_caches(self):
        self.cached_conanfiles = {}
        self._python_requires.invalidate_caches()

    def load_class(self, conanfile_path):
        try:
            return self.cached_conanfiles[conanfile_path]
        except KeyError:
            self._python_requires.valid = True
            _, conanfile = parse_conanfile(conanfile_path, self._python_requires)
            self._python_requires.valid = False
            self.cached_conanfiles[conanfile_path] = conanfile
        return conanfile

    def load_export(self, conanfile_path, name, version, user, channel):
        conanfile = self.load_class(conanfile_path)
        # Export does a check on existing name & version
        if "name" in conanfile.__dict__:
            if name and name != conanfile.name:
                raise ConanException("Package recipe exported with name %s!=%s"
                                     % (name, conanfile.name))
        elif not name:
            raise ConanException("conanfile didn't specify name")
        else:
            conanfile.name = name

        if "version" in conanfile.__dict__:
            if version and version != conanfile.version:
                raise ConanException("Package recipe exported with version %s!=%s"
                                     % (version, conanfile.version))
        elif not version:
            raise ConanException("conanfile didn't specify version")
        else:
            conanfile.version = version
        ref = ConanFileReference(conanfile.name, conanfile.version, user, channel)
        return conanfile(self._output, self._runner, str(ref), user, channel)

    @staticmethod
    def _initialize_conanfile(conanfile, processed_profile):
        # Prepare the settings for the loaded conanfile
        # Mixing the global settings with the specified for that name if exist
        tmp_settings = processed_profile._settings.copy()
        if (processed_profile._package_settings and
                conanfile.name in processed_profile._package_settings):
            # Update the values, keeping old ones (confusing assign)
            values_tuple = processed_profile._package_settings[conanfile.name]
            tmp_settings.values = Values.from_list(values_tuple)

        conanfile.initialize(tmp_settings, processed_profile._env_values)

    def load_consumer(self, conanfile_path, processed_profile, name=None, version=None, user=None,
                      channel=None, test=None):

        conanfile_class = self.load_class(conanfile_path)
        if name and conanfile_class.name and name != conanfile_class.name:
            raise ConanException("Package recipe name %s!=%s" % (name, conanfile_class.name))
        if version and conanfile_class.version and version != conanfile_class.version:
            raise ConanException("Package recipe version %s!=%s"
                                 % (version, conanfile_class.version))
        conanfile_class.name = name or conanfile_class.name
        conanfile_class.version = version or conanfile_class.version
        if test:
            display_name = "%s (test package)" % test
        else:
            ref = ConanFileReference(conanfile_class.name, conanfile_class.version, user, channel,
                                     validate=False)
            if ref.name or ref.version or ref.user or ref.channel:
                display_name = "%s (%s)" % (os.path.basename(conanfile_path), ref)
            else:
                display_name = os.path.basename(conanfile_path)
        conanfile = conanfile_class(self._output, self._runner, display_name, user, channel)
        conanfile.in_local_cache = False
        try:
            self._initialize_conanfile(conanfile, processed_profile)

            # The consumer specific
            conanfile.develop = True
            processed_profile._user_options.descope_options(conanfile.name)
            conanfile.options.initialize_upstream(processed_profile._user_options,
                                                  name=conanfile.name)
            processed_profile._user_options.clear_unscoped_options()

            return conanfile
        except Exception as e:  # re-raise with file name
            raise ConanException("%s: %s" % (conanfile_path, str(e)))

    def load_conanfile(self, conanfile_path, processed_profile, ref):
        conanfile_class = self.load_class(conanfile_path)
        conanfile_class.name = ref.name
        conanfile_class.version = ref.version
        conanfile = conanfile_class(self._output, self._runner, str(ref), ref.user, ref.channel)
        if processed_profile._dev_reference and processed_profile._dev_reference == ref:
            conanfile.develop = True
        try:
            self._initialize_conanfile(conanfile, processed_profile)
            return conanfile
        except Exception as e:  # re-raise with file name
            raise ConanException("%s: %s" % (conanfile_path, str(e)))

    def load_conanfile_txt(self, conan_txt_path, processed_profile, ref=None):
        if not os.path.exists(conan_txt_path):
            raise NotFoundException("Conanfile not found!")

        contents = load(conan_txt_path)
        path, basename = os.path.split(conan_txt_path)
        display_name = "%s (%s)" % (basename, ref) if ref and ref.name else basename
        conanfile = self._parse_conan_txt(contents, path, display_name, processed_profile)
        return conanfile

    def _parse_conan_txt(self, contents, path, display_name, processed_profile):
        conanfile = ConanFile(self._output, self._runner, display_name)
        conanfile.initialize(Settings(), processed_profile._env_values)
        # It is necessary to copy the settings, because the above is only a constraint of
        # conanfile settings, and a txt doesn't define settings. Necessary for generators,
        # as cmake_multi, that check build_type.
        conanfile.settings = processed_profile._settings.copy_values()

        try:
            parser = ConanFileTextLoader(contents)
        except Exception as e:
            raise ConanException("%s:\n%s" % (path, str(e)))
        for reference in parser.requirements:
            ConanFileReference.loads(reference)  # Raise if invalid
            conanfile.requires.add(reference)
        for build_reference in parser.build_requirements:
            ConanFileReference.loads(build_reference)
            if not hasattr(conanfile, "build_requires"):
                conanfile.build_requires = []
            conanfile.build_requires.append(build_reference)

        conanfile.generators = parser.generators

        options = OptionsValues.loads(parser.options)
        conanfile.options.values = options
        conanfile.options.initialize_upstream(processed_profile._user_options)

        # imports method
        conanfile.imports = parser.imports_method(conanfile)
        conanfile._conan_env_values.update(processed_profile._env_values)
        return conanfile

    def load_virtual(self, references, processed_profile, scope_options=True,
                     build_requires_options=None):
        # If user don't specify namespace in options, assume that it is
        # for the reference (keep compatibility)
        conanfile = ConanFile(self._output, self._runner, display_name="virtual")
        conanfile.initialize(processed_profile._settings.copy(), processed_profile._env_values)
        conanfile.settings = processed_profile._settings.copy_values()

        for reference in references:
            conanfile.requires.add(reference.full_repr())  # Convert to string necessary
        # Allows options without package namespace in conan install commands:
        #   conan install zlib/1.2.8@lasote/stable -o shared=True
        if scope_options:
            assert len(references) == 1
            processed_profile._user_options.scope_options(references[0].name)
        if build_requires_options:
            conanfile.options.initialize_upstream(build_requires_options)
        else:
            conanfile.options.initialize_upstream(processed_profile._user_options)

        conanfile.generators = []  # remove the default txt generator
        return conanfile


def _parse_module(conanfile_module, module_id):
    """ Parses a python in-memory module, to extract the classes, mainly the main
    class defining the Recipe, but also process possible existing generators
    @param conanfile_module: the module to be processed
    @return: the main ConanFile class from the module
    """
    result = None
    for name, attr in conanfile_module.__dict__.items():
        if (name.startswith("_") or not inspect.isclass(attr) or
                attr.__dict__.get("__module__") != module_id):
            continue

        if issubclass(attr, ConanFile) and attr != ConanFile:
            if result is None:
                result = attr
            else:
                raise ConanException("More than 1 conanfile in the file")
        elif issubclass(attr, Generator) and attr != Generator:
            registered_generators.add(attr.__name__, attr, custom=True)

    if result is None:
        raise ConanException("No subclass of ConanFile")

    return result


def parse_conanfile(conanfile_path, python_requires):
    with python_requires.capture_requires() as py_requires:
        module, filename = _parse_conanfile(conanfile_path)
        try:
            conanfile = _parse_module(module, filename)
            conanfile.python_requires = py_requires
            return module, conanfile
        except Exception as e:  # re-raise with file name
            raise ConanException("%s: %s" % (conanfile_path, str(e)))


def _parse_conanfile(conan_file_path):
    """ From a given path, obtain the in memory python import module
    """

    if not os.path.exists(conan_file_path):
        raise NotFoundException("%s not found!" % conan_file_path)

    module_id = str(uuid.uuid1())
    current_dir = os.path.dirname(conan_file_path)
    sys.path.insert(0, current_dir)
    try:
        old_modules = list(sys.modules.keys())
        with chdir(current_dir):
            sys.dont_write_bytecode = True
            loaded = imp.load_source(module_id, conan_file_path)
            sys.dont_write_bytecode = False

        # These lines are necessary, otherwise local conanfile imports with same name
        # collide, but no error, and overwrite other packages imports!!
        added_modules = set(sys.modules).difference(old_modules)
        for added in added_modules:
            module = sys.modules[added]
            if module:
                try:
                    folder = os.path.dirname(module.__file__)
                except AttributeError:  # some module doesn't have __file__
                    pass
                else:
                    if folder.startswith(current_dir):
                        module = sys.modules.pop(added)
                        sys.modules["%s.%s" % (module_id, added)] = module
    except Exception:
        import traceback
        trace = traceback.format_exc().split('\n')
        raise ConanException("Unable to load conanfile in %s\n%s" % (conan_file_path,
                                                                     '\n'.join(trace[3:])))
    finally:
        sys.path.pop(0)

    return loaded, module_id

conans/client/loader.py
import imp
import inspect
import os
import sys
import uuid

from conans.client.generators import registered_generators
from conans.client.loader_txt import ConanFileTextLoader
from conans.client.tools.files import chdir
from conans.errors import ConanException, NotFoundException
from conans.model.conan_file import ConanFile
from conans.model.conan_generator import Generator
from conans.model.options import OptionsValues
from conans.model.ref import ConanFileReference
from conans.model.settings import Settings
from conans.model.values import Values
from conans.util.files import load


class ProcessedProfile(object):
    def __init__(self, profile, create_reference=None):
        self._settings = profile.processed_settings
        self._user_options = profile.options.copy()

        self._package_settings = profile.package_settings_values
        self._env_values = profile.env_values
        # Make sure the paths are normalized first, so env_values can be just a copy
        self._dev_reference = create_reference


class ConanFileLoader(object):
    def __init__(self, runner, output, python_requires):
        self._runner = runner
        self._output = output
        self._python_requires = python_requires
        sys.modules["conans"].python_requires = python_requires
        self.cached_conanfiles = {}

    def invalidate_caches(self):
        self.cached_conanfiles = {}
        self._python_requires.invalidate_caches()

    def load_class(self, conanfile_path):
        try:
            return self.cached_conanfiles[conanfile_path]
        except KeyError:
            self._python_requires.valid = True
            _, conanfile = parse_conanfile(conanfile_path, self._python_requires)
            self._python_requires.valid = False
            self.cached_conanfiles[conanfile_path] = conanfile
        return conanfile

    def load_export(self, conanfile_path, name, version, user, channel):
        conanfile = self.load_class(conanfile_path)
        # Export does a check on existing name & version
        if "name" in conanfile.__dict__:
            if name and name != conanfile.name:
                raise ConanException("Package recipe exported with name %s!=%s"
                                     % (name, conanfile.name))
        elif not name:
            raise ConanException("conanfile didn't specify name")
        else:
            conanfile.name = name

        if "version" in conanfile.__dict__:
            if version and version != conanfile.version:
                raise ConanException("Package recipe exported with version %s!=%s"
                                     % (version, conanfile.version))
        elif not version:
            raise ConanException("conanfile didn't specify version")
        else:
            conanfile.version = version
        ref = ConanFileReference(conanfile.name, conanfile.version, user, channel)
        return conanfile(self._output, self._runner, str(ref), user, channel)

    @staticmethod
    def _initialize_conanfile(conanfile, processed_profile):
        # Prepare the settings for the loaded conanfile
        # Mixing the global settings with the specified for that name if exist
        tmp_settings = processed_profile._settings.copy()
        if (processed_profile._package_settings and
                conanfile.name in processed_profile._package_settings):
            # Update the values, keeping old ones (confusing assign)
            values_tuple = processed_profile._package_settings[conanfile.name]
            tmp_settings.values = Values.from_list(values_tuple)

        conanfile.initialize(tmp_settings, processed_profile._env_values)

    def load_consumer(self, conanfile_path, processed_profile, name=None, version=None, user=None,
                      channel=None, test=None):

        conanfile_class = self.load_class(conanfile_path)
        if name and conanfile_class.name and name != conanfile_class.name:
            raise ConanException("Package recipe name %s!=%s" % (name, conanfile_class.name))
        if version and conanfile_class.version and version != conanfile_class.version:
            raise ConanException("Package recipe version %s!=%s"
                                 % (version, conanfile_class.version))
        conanfile_class.name = name or conanfile_class.name
        conanfile_class.version = version or conanfile_class.version
        if test:
            display_name = "%s (test package)" % test
        else:
            ref = ConanFileReference(conanfile_class.name, conanfile_class.version, user, channel,
                                     validate=False)
            if ref.name or ref.version or ref.user or ref.channel:
                display_name = "%s (%s)" % (os.path.basename(conanfile_path), ref)
            else:
                display_name = os.path.basename(conanfile_path)
        conanfile = conanfile_class(self._output, self._runner, display_name, user, channel)
        conanfile.in_local_cache = False
        try:
            self._initialize_conanfile(conanfile, processed_profile)

            # The consumer specific
            conanfile.develop = True
            processed_profile._user_options.descope_options(conanfile.name)
            conanfile.options.initialize_upstream(processed_profile._user_options,
                                                  name=conanfile.name)
            processed_profile._user_options.clear_unscoped_options()

            return conanfile
        except Exception as e:  # re-raise with file name
            raise ConanException("%s: %s" % (conanfile_path, str(e)))

    def load_conanfile(self, conanfile_path, processed_profile, ref):
        conanfile_class = self.load_class(conanfile_path)
        conanfile_class.name = ref.name
        conanfile_class.version = ref.version
        conanfile = conanfile_class(self._output, self._runner, str(ref), ref.user, ref.channel)
        if processed_profile._dev_reference and processed_profile._dev_reference == ref:
            conanfile.develop = True
        try:
            self._initialize_conanfile(conanfile, processed_profile)
            return conanfile
        except Exception as e:  # re-raise with file name
            raise ConanException("%s: %s" % (conanfile_path, str(e)))

    def load_conanfile_txt(self, conan_txt_path, processed_profile, ref=None):
        if not os.path.exists(conan_txt_path):
            raise NotFoundException("Conanfile not found!")

        contents = load(conan_txt_path)
        path, basename = os.path.split(conan_txt_path)
        display_name = "%s (%s)" % (basename, ref) if ref and ref.name else basename
        conanfile = self._parse_conan_txt(contents, path, display_name, processed_profile)
        return conanfile

    def _parse_conan_txt(self, contents, path, display_name, processed_profile):
        conanfile = ConanFile(self._output, self._runner, display_name)
        conanfile.initialize(Settings(), processed_profile._env_values)
        # It is necessary to copy the settings, because the above is only a constraint of
        # conanfile settings, and a txt doesn't define settings. Necessary for generators,
        # as cmake_multi, that check build_type.
        conanfile.settings = processed_profile._settings.copy_values()

        try:
            parser = ConanFileTextLoader(contents)
        except Exception as e:
            raise ConanException("%s:\n%s" % (path, str(e)))
        for reference in parser.requirements:
            ConanFileReference.loads(reference)  # Raise if invalid
            conanfile.requires.add(reference)
        for build_reference in parser.build_requirements:
            ConanFileReference.loads(build_reference)
            if not hasattr(conanfile, "build_requires"):
                conanfile.build_requires = []
            conanfile.build_requires.append(build_reference)

        conanfile.generators = parser.generators

        options = OptionsValues.loads(parser.options)
        conanfile.options.values = options
        conanfile.options.initialize_upstream(processed_profile._user_options)

        # imports method
        conanfile.imports = parser.imports_method(conanfile)
        return conanfile

    def load_virtual(self, references, processed_profile, scope_options=True,
                     build_requires_options=None):
        # If user don't specify namespace in options, assume that it is
        # for the reference (keep compatibility)
        conanfile = ConanFile(self._output, self._runner, display_name="virtual")
        conanfile.initialize(processed_profile._settings.copy(), processed_profile._env_values)
        conanfile.settings = processed_profile._settings.copy_values()

        for reference in references:
            conanfile.requires.add(reference.full_repr())  # Convert to string necessary
        # Allows options without package namespace in conan install commands:
        #   conan install zlib/1.2.8@lasote/stable -o shared=True
        if scope_options:
            assert len(references) == 1
            processed_profile._user_options.scope_options(references[0].name)
        if build_requires_options:
            conanfile.options.initialize_upstream(build_requires_options)
        else:
            conanfile.options.initialize_upstream(processed_profile._user_options)

        conanfile.generators = []  # remove the default txt generator
        return conanfile


def _parse_module(conanfile_module, module_id):
    """ Parses a python in-memory module, to extract the classes, mainly the main
    class defining the Recipe, but also process possible existing generators
    @param conanfile_module: the module to be processed
    @return: the main ConanFile class from the module
    """
    result = None
    for name, attr in conanfile_module.__dict__.items():
        if (name.startswith("_") or not inspect.isclass(attr) or
                attr.__dict__.get("__module__") != module_id):
            continue

        if issubclass(attr, ConanFile) and attr != ConanFile:
            if result is None:
                result = attr
            else:
                raise ConanException("More than 1 conanfile in the file")
        elif issubclass(attr, Generator) and attr != Generator:
            registered_generators.add(attr.__name__, attr, custom=True)

    if result is None:
        raise ConanException("No subclass of ConanFile")

    return result


def parse_conanfile(conanfile_path, python_requires):
    with python_requires.capture_requires() as py_requires:
        module, filename = _parse_conanfile(conanfile_path)
        try:
            conanfile = _parse_module(module, filename)
            conanfile.python_requires = py_requires
            return module, conanfile
        except Exception as e:  # re-raise with file name
            raise ConanException("%s: %s" % (conanfile_path, str(e)))


def _parse_conanfile(conan_file_path):
    """ From a given path, obtain the in memory python import module
    """

    if not os.path.exists(conan_file_path):
        raise NotFoundException("%s not found!" % conan_file_path)

    module_id = str(uuid.uuid1())
    current_dir = os.path.dirname(conan_file_path)
    sys.path.insert(0, current_dir)
    try:
        old_modules = list(sys.modules.keys())
        with chdir(current_dir):
            sys.dont_write_bytecode = True
            loaded = imp.load_source(module_id, conan_file_path)
            sys.dont_write_bytecode = False

        # These lines are necessary, otherwise local conanfile imports with same name
        # collide, but no error, and overwrite other packages imports!!
        added_modules = set(sys.modules).difference(old_modules)
        for added in added_modules:
            module = sys.modules[added]
            if module:
                try:
                    folder = os.path.dirname(module.__file__)
                except AttributeError:  # some module doesn't have __file__
                    pass
                else:
                    if folder.startswith(current_dir):
                        module = sys.modules.pop(added)
                        sys.modules["%s.%s" % (module_id, added)] = module
    except Exception:
        import traceback
        trace = traceback.format_exc().split('\n')
        raise ConanException("Unable to load conanfile in %s\n%s" % (conan_file_path,
                                                                     '\n'.join(trace[3:])))
    finally:
        sys.path.pop(0)

    return loaded, module_id

conans/client/loader.py
diff --git a/conans/client/loader.py b/conans/client/loader.py
index 525ef71b8ca..9bd73cab09c 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -174,7 +174,6 @@ def _parse_conan_txt(self, contents, path, display_name, processed_profile):
 
         # imports method
         conanfile.imports = parser.imports_method(conanfile)
-        conanfile._conan_env_values.update(processed_profile._env_values)
         return conanfile
 
     def load_virtual(self, references, processed_profile, scope_options=True,
diff --git a/conans/test/functional/configuration/profile_test.py b/conans/test/functional/configuration/profile_test.py
index e1ce9776577..c7c8d7c261e 100644
--- a/conans/test/functional/configuration/profile_test.py
+++ b/conans/test/functional/configuration/profile_test.py
@@ -1,4 +1,5 @@
 import os
+import platform
 import unittest
 from collections import OrderedDict
 from textwrap import dedent
@@ -27,7 +28,6 @@ def build(self):
             self.run("SET")
         else:
             self.run("env")
-
 """
 
 
@@ -44,23 +44,37 @@ class ProfileTest(unittest.TestCase):
     def setUp(self):
         self.client = TestClient()
 
+    def profile_conanfile_txt_test(self):
+        """
+        Test prepended env variables are applied correctrly from a profile
+        """
+        self.client.save({"conanfile.txt": ""})
+        create_profile(self.client.cache.profiles_path, "envs", settings={},
+                       env=[("A_VAR", "A_VALUE"), ("PREPEND_VAR", ["new_path", "other_path"])],
+                       package_env={"Hello0": [("OTHER_VAR", "2")]})
+        self.client.run("install . -pr envs -g virtualenv")
+        content = load(os.path.join(self.client.current_folder, "activate.sh"))
+        self.assertIn(":".join(["PREPEND_VAR=\"new_path\"", "\"other_path\"", "$PREPEND_VAR"]),
+                      content)
+        if platform.system() == "Windows":
+            content = load(os.path.join(self.client.current_folder, "activate.bat"))
+            self.assertIn(";".join(["PREPEND_VAR=new_path", "other_path", "%PREPEND_VAR%"]),
+                          content)
+
     def test_profile_relative_cwd(self):
-        client = TestClient()
-        client.save({"conanfile.txt": "",
-                     "sub/sub/profile": ""})
-        client.current_folder = os.path.join(client.current_folder, "sub")
-        client.run("install .. -pr=sub/profile2", assert_error=True)
-        self.assertIn("ERROR: Profile not found: sub/profile2", client.out)
-        client.run("install .. -pr=sub/profile")
-        self.assertIn("conanfile.txt: Installing package", client.out)
+        self.client.save({"conanfile.txt": "", "sub/sub/profile": ""})
+        self.client.current_folder = os.path.join(self.client.current_folder, "sub")
+        self.client.run("install .. -pr=sub/profile2", assert_error=True)
+        self.assertIn("ERROR: Profile not found: sub/profile2", self.client.out)
+        self.client.run("install .. -pr=sub/profile")
+        self.assertIn("conanfile.txt: Installing package", self.client.out)
 
     def base_profile_generated_test(self):
         """we are testing that the default profile is created (when not existing, fresh install)
          even when you run a create with a profile"""
-        client = TestClient()
-        client.save({CONANFILE: conanfile_scope_env,
-                     "myprofile": "include(default)\n[settings]\nbuild_type=Debug"})
-        client.run("create . conan/testing --profile myprofile")
+        self.client.save({CONANFILE: conanfile_scope_env,
+                          "myprofile": "include(default)\n[settings]\nbuild_type=Debug"})
+        self.client.run("create . conan/testing --profile myprofile")
 
     def bad_syntax_test(self):
         self.client.save({CONANFILE: conanfile_scope_env})
@@ -148,11 +162,15 @@ def install_profile_env_test(self):
         files["conanfile.py"] = conanfile_scope_env
 
         create_profile(self.client.cache.profiles_path, "envs", settings={},
-                       env=[("A_VAR", "A_VALUE")], package_env={"Hello0": [("OTHER_VAR", "2")]})
+                       env=[("A_VAR", "A_VALUE"),
+                            ("PREPEND_VAR", ["new_path", "other_path"])],
+                       package_env={"Hello0": [("OTHER_VAR", "2")]})
 
         self.client.save(files)
         self.client.run("export . lasote/stable")
         self.client.run("install Hello0/0.1@lasote/stable --build missing -pr envs")
+        self._assert_env_variable_printed("PREPEND_VAR", os.pathsep.join(["new_path", "other_path"]))
+        self.assertEqual(1, str(self.client.out).count("PREPEND_VAR=new_path"))  # prepended once
         self._assert_env_variable_printed("A_VAR", "A_VALUE")
         self._assert_env_variable_printed("OTHER_VAR", "2")
 
diff --git a/conans/test/unittests/client/generators/virtualenv_test.py b/conans/test/unittests/client/generators/virtualenv_test.py
new file mode 100644
index 00000000000..458160b04ff
--- /dev/null
+++ b/conans/test/unittests/client/generators/virtualenv_test.py
@@ -0,0 +1,22 @@
+import platform
+import unittest
+
+from conans import ConanFile, Settings
+from conans.client.generators.virtualenv import VirtualEnvGenerator
+from conans.model.env_info import EnvValues
+from conans.test.utils.tools import TestBufferConanOutput
+
+
+class VirtualenvGeneratorTest(unittest.TestCase):
+
+    def prepend_values_test(self):
+        """
+        Check list values are only prepended once
+        """
+        conanfile = ConanFile(TestBufferConanOutput(), None)
+        conanfile.initialize(Settings({}), EnvValues.loads("PATH=[1,2,three]"))
+        gen = VirtualEnvGenerator(conanfile)
+        content = gen.content
+        self.assertIn("PATH=\"1\":\"2\":\"three\":$PATH", content["activate.sh"])
+        if platform.system() == "Windows":
+            self.assertIn("PATH=1;2;three;%PATH%", content["activate.bat"])
diff --git a/conans/test/unittests/client/loader_test.py b/conans/test/unittests/client/loader_test.py
new file mode 100644
index 00000000000..5ae0a4d96ee
--- /dev/null
+++ b/conans/test/unittests/client/loader_test.py
@@ -0,0 +1,69 @@
+import os
+import textwrap
+import unittest
+
+from conans import Settings
+from conans.client.graph.python_requires import ConanPythonRequire
+from conans.client.loader import ConanFileLoader
+from conans.model.env_info import EnvValues
+from conans.model.profile import Profile
+from conans.model.ref import ConanFileReference
+from conans.test.utils.conanfile import MockSettings
+from conans.test.utils.runner import TestRunner
+from conans.test.utils.test_files import temp_folder
+from conans.test.utils.tools import TestBufferConanOutput
+from conans.util.files import save
+
+
+class LoadConanfileTxtTest(unittest.TestCase):
+
+    def setUp(self):
+        settings = Settings()
+        self.profile = Profile()
+        self.profile._settings = settings
+        self.profile._user_options = None
+        self.profile._env_values = None
+        self.conanfile_txt_path = os.path.join(temp_folder(), "conanfile.txt")
+        output = TestBufferConanOutput()
+        self.loader = ConanFileLoader(TestRunner(output), output, None)
+
+    def env_test(self):
+        env_values = EnvValues()
+        env_values.add("PREPEND_PATH", ["hello", "bye"])
+        env_values.add("VAR", ["var_value"])
+        self.profile._env_values = env_values
+        save(self.conanfile_txt_path, "")
+        conanfile = self.loader.load_conanfile_txt(self.conanfile_txt_path, self.profile)
+        self.assertEquals(conanfile.env, {"PREPEND_PATH": ["hello", "bye"], "VAR": ["var_value"]})
+
+
+class LoadConanfileTest(unittest.TestCase):
+
+    def setUp(self):
+        settings = Settings()
+        self.profile = Profile()
+        self.profile._settings = settings
+        self.profile._user_options = None
+        self.profile._env_values = None
+        self.profile._dev_reference = None
+        self.profile._package_settings = None
+        self.conanfile_path = os.path.join(temp_folder(), "conanfile.py")
+        output = TestBufferConanOutput()
+        self.loader = ConanFileLoader(TestRunner(output), output, ConanPythonRequire(None, None))
+
+    def env_test(self):
+        env_values = EnvValues()
+        env_values.add("PREPEND_PATH", ["hello", "bye"])
+        env_values.add("VAR", ["var_value"])
+        self.profile._env_values = env_values
+        save(self.conanfile_path,
+             textwrap.dedent("""
+                from conans import ConanFile
+                
+                class TestConan(ConanFile):
+                    name = "hello"
+                    version = "1.0"
+             """))
+        ref = ConanFileReference("hello", "1.0", "user", "channel")
+        conanfile = self.loader.load_conanfile(self.conanfile_path, self.profile, ref)
+        self.assertEquals(conanfile.env, {"PREPEND_PATH": ["hello", "bye"], "VAR": ["var_value"]})

Virtualenv generator prepends duplicated values in env vars from profile
With a *conanfile.txt* empty and a profile with:

```
[env]
PREPEND_VAR = ['kk','pp']
```

Commands:

```
$ conan install . -g virtualenv
```

In activate.sh there is:
```
PREPEND_VAR="kk":"pp":"kk":"pp"
```
and in *activate.bat* you alaso have:
```
SET PREPEND_VAR=kk;pp;kk;pp
```

So at least variables that should be pretended are applied twice.


