Repository: conan-io/conan

Issue:
Support conan info --json for all outputs, and support stdout
I'm not sure why --json is only supported when --build-order is also passed, but we would like all outputs from `conan info` to support output as `json`. 

Our primary reason is that we'll often be calling `conan info` in order to pass the information to another step in our automation.  If it's JSON, we can then parse the information more easily, and get nice access to the fields in the result like:  info_result.package_folder.  As it stands, we have to have custom parsing for the output format. 

- [x] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.



Discussion:

Patch:
diff --git a/conans/client/build/autotools_environment.py b/conans/client/build/autotools_environment.py
index 91c86a23f..70f6e08f6 100644
--- a/conans/client/build/autotools_environment.py
+++ b/conans/client/build/autotools_environment.py
@@ -11,7 +11,7 @@ from conans.client.build.compiler_flags import (architecture_flag, build_type_de
 from conans.client.build.cppstd_flags import cppstd_flag
 from conans.client.tools.env import environment_append
 from conans.client.tools.oss import OSInfo, args_to_string, cpu_count, cross_building, \
-    detected_architecture, get_gnu_triplet
+    detected_architecture, detected_os, get_gnu_triplet
 from conans.client.tools.win import unix_path
 from conans.errors import ConanException
 from conans.model.build_info import DEFAULT_BIN, DEFAULT_INCLUDE, DEFAULT_LIB, DEFAULT_SHARE
@@ -76,7 +76,7 @@ class AutoToolsBuildEnvironment(object):
         and complex verification"""
 
         arch_detected = detected_architecture() or platform.machine()
-        os_detected = platform.system()
+        os_detected = detected_os() or platform.system()
 
         if os_detected is None or arch_detected is None or self._arch is None or self._os is None:
             return False, False, False
diff --git a/conans/client/build/meson.py b/conans/client/build/meson.py
index 774ee8ce1..1e458690d 100644
--- a/conans/client/build/meson.py
+++ b/conans/client/build/meson.py
@@ -28,7 +28,8 @@ class Meson(object):
         self.backend = backend or "ninja"  # Other backends are poorly supported, not default other.
 
         self.options = dict()
-        self.options['prefix'] = self._conanfile.package_folder
+        if self._conanfile.package_folder:
+            self.options['prefix'] = self._conanfile.package_folder
 
         # C++ standard
         cppstd = self._ss("cppstd")
diff --git a/conans/client/command.py b/conans/client/command.py
index 3efce0530..35ff6432f 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -486,8 +486,7 @@ class Command(object):
                             "specify both install-folder and any setting/option "
                             "it will raise an error.")
         parser.add_argument("-j", "--json", nargs='?', const="1", type=str,
-                            help='Only with --build-order option, return the information in a json.'
-                                 ' e.g --json=/path/to/filename.json or --json to output the json')
+                            help='Path to a json file where the information will be written')
         parser.add_argument("-n", "--only", nargs=1, action=Extender,
                             help="Show only the specified fields: %s. '--paths' information can "
                             "also be filtered with options %s. Use '--only None' to show only "
@@ -496,8 +495,8 @@ class Command(object):
                             help='Print information only for packages that match the filter pattern'
                                  ' e.g., MyPackage/1.2@user/channel or MyPackage*')
 
-        dry_build_help = ("Apply the --build argument to output the information, as it would be done "
-                          "by the install command")
+        dry_build_help = ("Apply the --build argument to output the information, as it would be done"
+                          " by the install command")
         parser.add_argument("-db", "--dry-build", action=Extender, nargs="?", help=dry_build_help)
         build_help = ("Given a build policy, return an ordered list of packages that would be built"
                       " from sources during the install command")
@@ -537,7 +536,11 @@ class Command(object):
                                                        remote_name=args.remote,
                                                        check_updates=args.update,
                                                        install_folder=args.install_folder)
-            self._outputer.nodes_to_build(nodes)
+            if args.json:
+                json_arg = True if args.json == "1" else args.json
+                self._outputer.json_nodes_to_build(nodes, json_arg, get_cwd())
+            else:
+                self._outputer.nodes_to_build(nodes)
 
         # INFO ABOUT DEPS OF CURRENT PROJECT OR REFERENCE
         else:
@@ -564,7 +567,11 @@ class Command(object):
 
             if args.graph:
                 self._outputer.info_graph(args.graph, deps_graph, get_cwd())
-            else:
+            if args.json:
+                json_arg = True if args.json == "1" else args.json
+                self._outputer.json_info(deps_graph, json_arg, get_cwd(), show_paths=args.paths)
+
+            if not args.graph and not args.json:
                 self._outputer.info(deps_graph, only, args.package_filter, args.paths)
 
     def source(self, *args):
diff --git a/conans/client/conan_command_output.py b/conans/client/conan_command_output.py
index 7c7744f0e..b63c48c0a 100644
--- a/conans/client/conan_command_output.py
+++ b/conans/client/conan_command_output.py
@@ -1,13 +1,17 @@
 import json
 import os
+from collections import OrderedDict
 
 from conans.client.graph.graph import RECIPE_CONSUMER, RECIPE_VIRTUAL
+from conans.client.graph.graph import RECIPE_EDITABLE
+from conans.client.installer import build_id
 from conans.client.printer import Printer
 from conans.model.ref import ConanFileReference, PackageReference
+from conans.paths.simple_paths import SimplePaths
 from conans.search.binary_html_table import html_binary_graph
 from conans.unicode import get_cwd
+from conans.util.env_reader import get_env
 from conans.util.files import save
-from conans.client.graph.graph import RECIPE_EDITABLE
 
 
 class CommandOutputer(object):
@@ -82,13 +86,117 @@ class CommandOutputer(object):
     def nodes_to_build(self, nodes_to_build):
         self.user_io.out.info(", ".join(str(n) for n in nodes_to_build))
 
+    def _handle_json_output(self, data, json_output, cwd):
+        json_str = json.dumps(data)
+
+        if json_output is True:
+            self.user_io.out.write(json_str)
+        else:
+            if not os.path.isabs(json_output):
+                json_output = os.path.join(cwd, json_output)
+            save(json_output, json.dumps(data))
+            self.user_io.out.writeln("")
+            self.user_io.out.info("JSON file created at '%s'" % json_output)
+
+    def json_nodes_to_build(self, nodes_to_build, json_output, cwd):
+        data = [str(n) for n in nodes_to_build]
+        self._handle_json_output(data, json_output, cwd)
+
+    def _grab_info_data(self, deps_graph, grab_paths):
+        """ Convert 'deps_graph' into consumible information for json and cli """
+        compact_nodes = OrderedDict()
+        for node in sorted(deps_graph.nodes):
+            compact_nodes.setdefault((node.ref, node.conanfile.info.package_id()), []).append(node)
+
+        ret = []
+        for (ref, package_id), list_nodes in compact_nodes.items():
+            node = list_nodes[0]
+            if node.recipe == RECIPE_VIRTUAL:
+                continue
+
+            item_data = {}
+            conanfile = node.conanfile
+            if node.recipe == RECIPE_CONSUMER:
+                ref = str(conanfile)
+
+            item_data["reference"] = str(ref)
+            item_data["is_ref"] = isinstance(ref, ConanFileReference)
+            item_data["display_name"] = conanfile.display_name
+            item_data["id"] = package_id
+            item_data["build_id"] = build_id(conanfile)
+
+            # Paths
+            if isinstance(ref, ConanFileReference) and grab_paths:
+                item_data["export_folder"] = self.cache.export(ref)
+                item_data["source_folder"] = self.cache.source(ref, conanfile.short_paths)
+                if isinstance(self.cache, SimplePaths):
+                    # @todo: check if this is correct or if it must always be package_id()
+                    bid = build_id(conanfile) or package_id
+                    pref = PackageReference(ref, bid)
+                    item_data["build_folder"] = self.cache.build(pref, conanfile.short_paths)
+
+                    pref = PackageReference(ref, package_id)
+                    item_data["package_folder"] = self.cache.package(pref, conanfile.short_paths)
+
+            try:
+                reg_remote = self.cache.registry.refs.get(ref)
+                if reg_remote:
+                    item_data["remote"] = {"name": reg_remote.name, "url": reg_remote.url}
+            except:
+                pass
+
+            def _add_if_exists(attrib, as_list=False):
+                value = getattr(conanfile, attrib, None)
+                if value:
+                    if not as_list:
+                        item_data[attrib] = value
+                    else:
+                        item_data[attrib] = list(value) if isinstance(value, (list, tuple, set)) \
+                            else [value, ]
+
+            _add_if_exists("url")
+            _add_if_exists("homepage")
+            _add_if_exists("license", as_list=True)
+            _add_if_exists("author")
+            _add_if_exists("topics", as_list=True)
+
+            if isinstance(ref, ConanFileReference):
+                item_data["recipe"] = node.recipe
+
+                if get_env("CONAN_CLIENT_REVISIONS_ENABLED", False) and node.ref.revision:
+                    item_data["revision"] = node.ref.revision
+
+                item_data["binary"] = node.binary
+                if node.binary_remote:
+                    item_data["binary_remote"] = node.binary_remote.name
+
+            node_times = self._read_dates(deps_graph)
+            if node_times and node_times.get(ref, None):
+                item_data["creation_date"] = node_times.get(ref, None)
+
+            if isinstance(ref, ConanFileReference):
+                dependants = [n for node in list_nodes for n in node.inverse_neighbors()]
+                required = [d.conanfile for d in dependants if d.recipe != RECIPE_VIRTUAL]
+                if required:
+                    item_data["required_by"] = [d.display_name for d in required]
+
+            depends = node.neighbors()
+            requires = [d for d in depends if not d.build_require]
+            build_requires = [d for d in depends if d.build_require]
+
+            if requires:
+                item_data["requires"] = [repr(d.ref) for d in requires]
+
+            if build_requires:
+                item_data["build_requires"] = [repr(d.ref) for d in build_requires]
+
+            ret.append(item_data)
+
+        return ret
+
     def info(self, deps_graph, only, package_filter, show_paths):
-        registry = self.cache.registry
-        Printer(self.user_io.out).print_info(deps_graph,
-                                             only, registry,
-                                             node_times=self._read_dates(deps_graph),
-                                             path_resolver=self.cache,
-                                             package_filter=package_filter,
+        data = self._grab_info_data(deps_graph, grab_paths=show_paths)
+        Printer(self.user_io.out).print_info(data, only,  package_filter=package_filter,
                                              show_paths=show_paths)
 
     def info_graph(self, graph_filename, deps_graph, cwd):
@@ -104,6 +212,10 @@ class CommandOutputer(object):
             graph_filename = os.path.join(cwd, graph_filename)
         grapher.graph_file(graph_filename)
 
+    def json_info(self, deps_graph, json_output, cwd, show_paths):
+        data = self._grab_info_data(deps_graph, grab_paths=show_paths)
+        self._handle_json_output(data, json_output, cwd)
+
     def print_search_references(self, search_info, pattern, raw, all_remotes_search):
         printer = Printer(self.user_io.out)
         printer.print_search_recipes(search_info, pattern, raw, all_remotes_search)
diff --git a/conans/client/conf/detect.py b/conans/client/conf/detect.py
index ccc5734e2..4b8048e40 100644
--- a/conans/client/conf/detect.py
+++ b/conans/client/conf/detect.py
@@ -5,7 +5,7 @@ from subprocess import PIPE, Popen, STDOUT
 
 from conans.client.output import Color
 from conans.client.tools.win import latest_visual_studio_version_installed
-from conans.client.tools import OSInfo
+from conans.client.tools import detected_os
 from conans.model.version import Version
 
 
@@ -168,14 +168,6 @@ adjusting 'compiler.libcxx=libstdc++11'
             result.append(("compiler.libcxx", "libCstd"))
 
 
-def detected_os():
-    if OSInfo().is_macos:
-        return "Macos"
-    if OSInfo().is_windows:
-        return "Windows"
-    return platform.system()
-
-
 def _detect_os_arch(result, output):
     architectures = {'i386': 'x86',
                      'i686': 'x86',
diff --git a/conans/client/generators/virtualenv.py b/conans/client/generators/virtualenv.py
index d69e5a0a4..c8f76b43d 100644
--- a/conans/client/generators/virtualenv.py
+++ b/conans/client/generators/virtualenv.py
@@ -17,7 +17,7 @@ class VirtualEnvGenerator(Generator):
     def filename(self):
         return
 
-    def _variable_placeholder(self, flavor, name):
+    def _variable_placeholder(self, flavor, name, append_with_spaces):
         """
         :param flavor: flavor of the execution environment
         :param name: variable name
@@ -28,7 +28,8 @@ class VirtualEnvGenerator(Generator):
             return "%%%s%%" % name
         if flavor == "ps1":
             return "$env:%s" % name
-        return "$%s" % name  # flavor == sh
+        # flavor == sh
+        return "${%s+ $%s}" % (name, name) if append_with_spaces else "${%s+:$%s}" % (name,  name)
 
     def format_values(self, flavor, variables):
         """
@@ -49,8 +50,9 @@ class VirtualEnvGenerator(Generator):
         for name, value in variables:
             # activate values
             if isinstance(value, list):
-                placeholder = self._variable_placeholder(flavor, name)
-                if name in self.append_with_spaces:
+                append_with_spaces = name in self.append_with_spaces
+                placeholder = self._variable_placeholder(flavor, name, append_with_spaces)
+                if append_with_spaces:
                     # Variables joined with spaces look like: CPPFLAGS="one two three"
                     value = " ".join(value+[placeholder])
                     value = "\"%s\"" % value if quote_elements else value
@@ -59,7 +61,10 @@ class VirtualEnvGenerator(Generator):
                     # PATH="one path":"two paths"
                     # Unquoted variables joined with pathset may look like: PATH=one path;two paths
                     value = ["\"%s\"" % v for v in value] if quote_elements else value
-                    value = path_sep.join(value+[placeholder])
+                    if flavor == "sh":
+                        value = path_sep.join(value) + placeholder
+                    else:
+                        value = path_sep.join(value + [placeholder])
             else:
                 # single value
                 value = "\"%s\"" % value if quote_elements else value
diff --git a/conans/client/loader.py b/conans/client/loader.py
index 525ef71b8..9bd73cab0 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -174,7 +174,6 @@ class ConanFileLoader(object):
 
         # 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/client/printer.py b/conans/client/printer.py
index b91458d94..e1d1f8329 100644
--- a/conans/client/printer.py
+++ b/conans/client/printer.py
@@ -1,13 +1,9 @@
 import fnmatch
 from collections import OrderedDict
 
-from conans.client.installer import build_id
 from conans.client.output import Color
 from conans.model.options import OptionsValues
-from conans.model.ref import ConanFileReference, PackageReference
-from conans.paths.simple_paths import SimplePaths
-from conans.util.env_reader import get_env
-from conans.client.graph.graph import RECIPE_CONSUMER, RECIPE_VIRTUAL
+from conans.model.ref import ConanFileReference
 
 
 class Printer(object):
@@ -40,36 +36,8 @@ class Printer(object):
             else:
                 self._out.writeln("%s: %s" % (k, str(v)))
 
-    def _print_paths(self, ref, conan, path_resolver, show):
-        if isinstance(ref, ConanFileReference):
-            if show("export_folder"):
-                path = path_resolver.export(ref)
-                self._out.writeln("    export_folder: %s" % path, Color.BRIGHT_GREEN)
-            if show("source_folder"):
-                path = path_resolver.source(ref, conan.short_paths)
-                self._out.writeln("    source_folder: %s" % path, Color.BRIGHT_GREEN)
-            if show("build_folder") and isinstance(path_resolver, SimplePaths):
-                # @todo: check if this is correct or if it must always be package_id()
-                bid = build_id(conan)
-                if not bid:
-                    bid = conan.info.package_id()
-                path = path_resolver.build(PackageReference(ref, bid), conan.short_paths)
-                self._out.writeln("    build_folder: %s" % path, Color.BRIGHT_GREEN)
-            if show("package_folder") and isinstance(path_resolver, SimplePaths):
-                id_ = conan.info.package_id()
-                path = path_resolver.package(PackageReference(ref, id_), conan.short_paths)
-                self._out.writeln("    package_folder: %s" % path, Color.BRIGHT_GREEN)
-
-    def print_info(self, deps_graph, _info, registry, node_times=None, path_resolver=None,
-                   package_filter=None, show_paths=False):
-        """ Print the dependency information for a conan file
-
-            Attributes:
-                deps_graph: the dependency graph of conan file references to print
-                placeholder_reference: the conan file reference that represents the conan
-                                       file for a project on the path. This may be None,
-                                       in which case the project itself will not be part
-                                       of the printed dependencies.
+    def print_info(self, data, _info, package_filter=None, show_paths=False):
+        """ Print in console the dependency information for a conan file
         """
         if _info is None:  # No filter
             def show(_):
@@ -80,102 +48,77 @@ class Printer(object):
             def show(field):
                 return field in _info_lower
 
-        compact_nodes = OrderedDict()
-        for node in sorted(deps_graph.nodes):
-            compact_nodes.setdefault((node.ref, node.conanfile.info.package_id()), []).append(node)
+        # Handy function to perform a common print task
+        def _print(it_field, show_field=None, name=None, color=Color.BRIGHT_GREEN):
+            show_field = show_field or it_field
+            name = name or it_field
+            if show(show_field) and it_field in it:
+                self._out.writeln("    %s: %s" % (name, it[it_field]), color)
 
-        for (ref, package_id), list_nodes in compact_nodes.items():
-            node = list_nodes[0]
-            conan = node.conanfile
-            if node.recipe == RECIPE_VIRTUAL:
+        # Iteration and printing
+        for it in data:
+            if package_filter and not fnmatch.fnmatch(it["reference"], package_filter):
                 continue
-            if node.recipe == RECIPE_CONSUMER:
-                ref = str(conan)
-            if package_filter and not fnmatch.fnmatch(str(ref), package_filter):
-                continue
-
-            self._out.writeln("%s" % node.conanfile.display_name, Color.BRIGHT_CYAN)
-            try:
-                # Excludes PROJECT fake reference
-                reg_remote = registry.refs.get(ref)
-            except:
-                reg_remote = None
 
-            if show("id"):
-                self._out.writeln("    ID: %s" % package_id, Color.BRIGHT_GREEN)
-            if show("build_id"):
-                bid = build_id(conan)
-                self._out.writeln("    BuildID: %s" % bid, Color.BRIGHT_GREEN)
+            is_ref = it["is_ref"]
 
+            self._out.writeln(it["display_name"], Color.BRIGHT_CYAN)
+            _print("id", name="ID")
+            _print("build_id", name="BuildID")
             if show_paths:
-                self._print_paths(ref, conan, path_resolver, show)
-
-            if isinstance(ref, ConanFileReference) and show("remote"):
-                if reg_remote:
-                    self._out.writeln("    Remote: %s=%s" % (reg_remote.name, reg_remote.url),
+                _print("export_folder")
+                _print("source_folder")
+                _print("build_folder")
+                _print("package_folder")
+
+            if show("remote") and is_ref:
+                if "remote" in it:
+                    self._out.writeln("    Remote: %s=%s" % (it["remote"]["name"],
+                                                             it["remote"]["url"]),
                                       Color.BRIGHT_GREEN)
                 else:
                     self._out.writeln("    Remote: None", Color.BRIGHT_GREEN)
-            url = getattr(conan, "url", None)
-            homepage = getattr(conan, "homepage", None)
-            license_ = getattr(conan, "license", None)
-            author = getattr(conan, "author", None)
-            topics = getattr(conan, "topics", None)
-            if url and show("url"):
-                self._out.writeln("    URL: %s" % url, Color.BRIGHT_GREEN)
-            if homepage and show("homepage"):
-                self._out.writeln("    Homepage: %s" % homepage, Color.BRIGHT_GREEN)
-
-            if license_ and show("license"):
-                if isinstance(license_, (list, tuple, set)):
-                    self._out.writeln("    Licenses: %s" % ", ".join(license_), Color.BRIGHT_GREEN)
-                else:
-                    self._out.writeln("    License: %s" % license_, Color.BRIGHT_GREEN)
-            if author and show("author"):
-                self._out.writeln("    Author: %s" % author, Color.BRIGHT_GREEN)
-            if topics and show("topics"):
-                if isinstance(topics, (list, tuple, set)):
-                    self._out.writeln("    Topics: %s" % ", ".join(topics), Color.BRIGHT_GREEN)
+
+            _print("url", name="URL")
+            _print("homepage", name="Homepage")
+
+            if show("license") and "license" in it:
+                licenses_str = ", ".join(it["license"])
+                lead_str = "Licenses" if len(it["license"]) > 1 else "License"
+                self._out.writeln("    %s: %s" % (lead_str, licenses_str), Color.BRIGHT_GREEN)
+
+            _print("author", name="Author")
+
+            if show("topics") and "topics" in it:
+                self._out.writeln("    Topics: %s" % ", ".join(it["topics"]), Color.BRIGHT_GREEN)
+
+            _print("recipe", name="Recipe", color=None)
+            _print("revision", name="Revision", color=None)
+            _print("binary", name="Binary", color=None)
+
+            if show("binary_remote") and is_ref:
+                if "binary_remote" in it:
+                    self._out.writeln("    Binary remote: %s" % it["binary_remote"])
                 else:
-                    self._out.writeln("    Topics: %s" % topics, Color.BRIGHT_GREEN)
-
-            if isinstance(ref, ConanFileReference) and show("recipe"):  # Excludes PROJECT
-                self._out.writeln("    Recipe: %s" % node.recipe)
-            revisions_enabled = get_env("CONAN_CLIENT_REVISIONS_ENABLED", False)
-            if revisions_enabled:
-                if (isinstance(ref, ConanFileReference) and show("revision") and
-                        node.ref.revision):
-                    self._out.writeln("    Revision: %s" % node.ref.revision)
-            if isinstance(ref, ConanFileReference) and show("binary"):  # Excludes PROJECT
-                self._out.writeln("    Binary: %s" % node.binary)
-            if isinstance(ref, ConanFileReference) and show("binary_remote"):  # Excludes PROJECT
-                self._out.writeln("    Binary remote: %s" % (node.binary_remote.name
-                                                             if node.binary_remote else "None"))
-
-            if node_times and node_times.get(ref, None) and show("date"):
-                self._out.writeln("    Creation date: %s" % node_times.get(ref, None),
-                                  Color.BRIGHT_GREEN)
-
-            dependants = [n for node in list_nodes for n in node.inverse_neighbors()]
-            if isinstance(ref, ConanFileReference) and show("required"):  # Excludes
-                required = [d.conanfile for d in dependants if d.recipe != RECIPE_VIRTUAL]
-                if required:
-                    self._out.writeln("    Required by:", Color.BRIGHT_GREEN)
-                    for d in required:
-                        self._out.writeln("        %s" % d.display_name, Color.BRIGHT_YELLOW)
+                    self._out.writeln("    Binary remote: None")
+
+            _print("creation_date", show_field="date", name="Creation date")
+
+            if show("required") and "required_by" in it:
+                self._out.writeln("    Required by:", Color.BRIGHT_GREEN)
+                for d in it["required_by"]:
+                    self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)
 
             if show("requires"):
-                depends = node.neighbors()
-                requires = [d for d in depends if not d.build_require]
-                build_requires = [d for d in depends if d.build_require]
-                if requires:
+                if "requires" in it:
                     self._out.writeln("    Requires:", Color.BRIGHT_GREEN)
-                    for d in requires:
-                        self._out.writeln("        %s" % repr(d.ref), Color.BRIGHT_YELLOW)
-                if build_requires:
+                    for d in it["requires"]:
+                        self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)
+
+                if "build_requires" in it:
                     self._out.writeln("    Build Requires:", Color.BRIGHT_GREEN)
-                    for d in build_requires:
-                        self._out.writeln("        %s" % repr(d.ref), Color.BRIGHT_YELLOW)
+                    for d in it["build_requires"]:
+                        self._out.writeln("        %s" % d, Color.BRIGHT_YELLOW)
 
     def print_search_recipes(self, search_info, pattern, raw, all_remotes_search):
         """ Print all the exported conans information
diff --git a/conans/client/tools/oss.py b/conans/client/tools/oss.py
index 4304b0baa..877a1fa0a 100644
--- a/conans/client/tools/oss.py
+++ b/conans/client/tools/oss.py
@@ -33,6 +33,14 @@ def cpu_count(output=None):
     return 1  # Safe guess
 
 
+def detected_os():
+    if OSInfo().is_macos:
+        return "Macos"
+    if OSInfo().is_windows:
+        return "Windows"
+    return platform.system()
+
+
 def detected_architecture():
     # FIXME: Very weak check but not very common to run conan in other architectures
     machine = platform.machine()
@@ -326,8 +334,7 @@ def cross_building(settings, self_os=None, self_arch=None):
 
 
 def get_cross_building_settings(settings, self_os=None, self_arch=None):
-    build_os = self_os or settings.get_safe("os_build") or \
-               {"Darwin": "Macos"}.get(platform.system(), platform.system())
+    build_os = self_os or settings.get_safe("os_build") or detected_os()
     build_arch = self_arch or settings.get_safe("arch_build") or detected_architecture()
     host_os = settings.get_safe("os")
     host_arch = settings.get_safe("arch")
diff --git a/conans/util/locks.py b/conans/util/locks.py
index c9f7d20f2..f7b8f34d3 100644
--- a/conans/util/locks.py
+++ b/conans/util/locks.py
@@ -50,7 +50,7 @@ class Lock(object):
 
     @property
     def files(self):
-        return (self._count_file, self._count_lock_file)
+        return self._count_file, self._count_lock_file
 
     def _info_locked(self):
         if self._first_lock:


Test patch:
diff --git a/conans/test/functional/command/info_command_test.py b/conans/test/functional/command/info_command_test.py
index 2bc228baf..22da91958 100644
--- a/conans/test/functional/command/info_command_test.py
+++ b/conans/test/functional/command/info_command_test.py
@@ -3,6 +3,7 @@ import unittest
 
 from conans.paths import CONANFILE
 from conans.test.utils.cpp_test_files import cpp_hello_conan_files
+from conans.test.utils.test_files import temp_folder
 from conans.test.utils.tools import TestClient, TestServer
 from conans.util.files import load, save
 
@@ -14,40 +15,26 @@ class InfoTest(unittest.TestCase):
         self.servers = {"default": test_server}
         self.clients = {}
 
-    def _export(self, name=0, version=None, deps=None):
-        client = TestClient(servers=self.servers, users={"default": [("lu", "mypass")]})
-        self.clients[name] = client
-        # Not necessary to actually build binaries
-        files = cpp_hello_conan_files(name, version, deps, build=False)
-        client.save(files, clean_first=True)
-        client.run("export . lu/st")
-        client.run("upload %s/%s@lu/st" % (name, version))
-
-    def assert_last_line(self, client, line):
-        lastline = str(client.user_io.out).splitlines()[-1]
-        self.assertEquals(lastline, line)
-
-    def info_build_test(self):
-        """Test that the output of 'conan info --build' is correct
-
-                    +-----------+
-           +------> |  H0       | <--------+
-           |        +------+----+          |
-   private |               ^               |private
-           |               |               |
-      +----+-----+    +----+------+   +----+------+
-      |  H1a     |    | H1b       |   | H1c       |
-      +----+-----+    +-----------+   +----+------+
-           ^                               ^
-           |                               |
-           |                               |
-+----------+-+                     +-------+------+
-|  H2a       | <------+    +-----> |   H2c        |
-+------------+        |    |       +--------------+
-                      |    |
-                  +---+----+---+
-                  |  H3        |
-                  +------------+
+        # Build a complex graph
+        """
+                            +-----------+
+                   +------> |  H0       | <--------+
+                   |        +------+----+          |
+           private |               ^               |private
+                   |               |               |
+              +----+-----+    +----+------+   +----+------+
+              |  H1a     |    | H1b       |   | H1c       |
+              +----+-----+    +-----------+   +----+------+
+                   ^                               ^
+                   |                               |
+                   |                               |
+        +----------+-+                     +-------+------+
+        |  H2a       | <------+    +-----> |   H2c        |
+        +------------+        |    |       +--------------+
+                              |    |
+                          +---+----+---+
+                          |  H3        |
+                          +------------+
 
         """
 
@@ -63,6 +50,21 @@ class InfoTest(unittest.TestCase):
         self._export("H3", "0.1", deps=["H2a/0.1@lu/st",
                                         "H2c/0.1@lu/st"])
 
+    def _export(self, name=0, version=None, deps=None):
+        client = TestClient(servers=self.servers, users={"default": [("lu", "mypass")]})
+        self.clients[name] = client
+        # Not necessary to actually build binaries
+        files = cpp_hello_conan_files(name, version, deps, build=False)
+        client.save(files, clean_first=True)
+        client.run("export . lu/st")
+        client.run("upload %s/%s@lu/st" % (name, version))
+
+    def assert_last_line(self, client, line):
+        lastline = str(client.user_io.out).splitlines()[-1]
+        self.assertEquals(lastline, line)
+
+    def info_build_test(self):
+        """ Test that the output of 'conan info --build' is correct """
         # If we install H3 we need to build all except H1b
         self.clients["H3"].run("info . --build missing")
         self.assert_last_line(self.clients["H3"],
@@ -118,3 +120,19 @@ class InfoTest(unittest.TestCase):
         self.clients["H3"].run("info . --build outdated")
         self.assert_last_line(self.clients["H3"],
                               "H0/0.1@lu/st, H1a/0.1@lu/st, H2a/0.1@lu/st, H2c/0.1@lu/st")
+
+    def test_json_output_build(self):
+        """ Test that the output of 'conan info --build' to json file is correct """
+        json_file = os.path.join(temp_folder(), "output.json")
+        json_output = '["H0/0.1@lu/st", "H1a/0.1@lu/st", "H1c/0.1@lu/st", ' \
+                      '"H2a/0.1@lu/st", "H2c/0.1@lu/st"]'
+
+        self.clients["H3"].run("info . --build missing --json=\"{}\"".format(json_file))
+        self.assertEqual(load(json_file), json_output)
+
+        json_file = os.path.join(temp_folder(), "output.json")
+        self.clients["H3"].run("info . --build missing --json".format(json_file))
+        self.assert_last_line(self.clients["H3"], json_output)
+
+
+
diff --git a/conans/test/functional/command/info_test.py b/conans/test/functional/command/info_test.py
index 3e8d67a26..92a93a078 100644
--- a/conans/test/functional/command/info_test.py
+++ b/conans/test/functional/command/info_test.py
@@ -1,3 +1,4 @@
+import json
 import os
 import re
 import textwrap
@@ -383,6 +384,28 @@ class MyTest(ConanFile):
 
         self.assertIn(expected_output, clean_output(self.client.user_io.out))
 
+    def test_json_info_outputs(self):
+        self.client = TestClient()
+        self._create("LibA", "0.1")
+        self._create("LibE", "0.1")
+        self._create("LibF", "0.1")
+
+        self._create("LibB", "0.1", ["LibA/0.1@lasote/stable", "LibE/0.1@lasote/stable"])
+        self._create("LibC", "0.1", ["LibA/0.1@lasote/stable", "LibF/0.1@lasote/stable"])
+
+        self._create("LibD", "0.1", ["LibB/0.1@lasote/stable", "LibC/0.1@lasote/stable"],
+                     export=False)
+
+        json_file = os.path.join(self.client.current_folder, "output.json")
+        self.client.run("info . -u --json=\"{}\"".format(json_file))
+
+        # Check a couple of values in the generated JSON
+        content = json.loads(load(json_file))
+        self.assertEqual(content[0]["reference"], "LibA/0.1@lasote/stable")
+        self.assertEqual(content[0]["license"][0], "MIT")
+        self.assertEqual(content[1]["url"], "myurl")
+        self.assertEqual(content[1]["required_by"][0], "conanfile.py (LibD/0.1@None/None)")
+
     def build_order_test(self):
         self.client = TestClient()
         self._create("Hello0", "0.1")
diff --git a/conans/test/functional/command/install_test.py b/conans/test/functional/command/install_test.py
index 9f83e663b..6d4df60de 100644
--- a/conans/test/functional/command/install_test.py
+++ b/conans/test/functional/command/install_test.py
@@ -2,7 +2,7 @@ import os
 import platform
 import unittest
 
-from conans.client.conf.detect import detected_os
+from conans.client.tools.oss import detected_os
 from conans.model.info import ConanInfo
 from conans.model.ref import ConanFileReference, PackageReference
 from conans.paths import CONANFILE, CONANFILE_TXT, CONANINFO
diff --git a/conans/test/functional/configuration/profile_test.py b/conans/test/functional/configuration/profile_test.py
index e1ce97765..1031a3c61 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 @@ class AConan(ConanFile):
             self.run("SET")
         else:
             self.run("env")
-
 """
 
 
@@ -44,23 +44,36 @@ 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+:$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 +161,15 @@ class ProfileTest(unittest.TestCase):
         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/functional/environment/__init__.py b/conans/test/functional/environment/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conans/test/integration/conan_env_test.py b/conans/test/functional/environment/apply_environment_test.py
similarity index 99%
rename from conans/test/integration/conan_env_test.py
rename to conans/test/functional/environment/apply_environment_test.py
index 4309c6996..68ca53265 100644
--- a/conans/test/integration/conan_env_test.py
+++ b/conans/test/functional/environment/apply_environment_test.py
@@ -438,8 +438,9 @@ class HelloConan(ConanFile):
         if platform.system() == "Windows":
             self.assertIn('var2=value3;value2;%var2%', activate_contents)
         else:
-            self.assertIn('var2="value3":"value2":$var2', activate_contents)
-            self.assertIn('CPPFLAGS="OtherFlag=2 MYCPPFLAG=1 $CPPFLAGS"', activate_contents)
+            self.assertIn('var2="value3":"value2"${var2+:$var2}', activate_contents)
+            self.assertIn('CPPFLAGS="OtherFlag=2 MYCPPFLAG=1 ${CPPFLAGS+ $CPPFLAGS}"',
+                          activate_contents)
         self.assertIn("Another value", activate_contents)
         if platform.system() == "Windows":
             self.assertIn("PATH=/dir", activate_contents)
@@ -673,7 +674,7 @@ PATH=["path_from_A"]
 PATH=["path_from_B"]""", info)
         if platform.system() != "Windows":
             activate = load(os.path.join(client.current_folder, "activate.sh"))
-            self.assertIn('PATH="path_from_A":"path_from_B":$PATH', activate)
+            self.assertIn('PATH="path_from_A":"path_from_B"${PATH+:$PATH}', activate)
         else:
             activate = load(os.path.join(client.current_folder, "activate.bat"))
             self.assertIn('PATH=path_from_A;path_from_B;%PATH%', activate)
diff --git a/conans/test/functional/generators/virtualenv_test.py b/conans/test/functional/generators/virtualenv_test.py
index df5209594..f1bb743b3 100644
--- a/conans/test/functional/generators/virtualenv_test.py
+++ b/conans/test/functional/generators/virtualenv_test.py
@@ -1,5 +1,6 @@
 import copy
 import os
+import platform
 import unittest
 
 from conans.client import tools
@@ -106,22 +107,26 @@ virtualenv
         self.assertIn('export OLD_PS1', activate)
         self.assertIn('PS1="(conanenv) $PS1"', activate)
         self.assertIn('export PS1', activate)
-        self.assertIn('BASE_LIST="dummyValue1":"dummyValue2":"baseValue1":"baseValue2":$BASE_LIST', activate)
+        self.assertIn('BASE_LIST="dummyValue1":"dummyValue2":"baseValue1":"baseValue2"${'
+                      'BASE_LIST+:$BASE_LIST}', activate)
         self.assertIn('export BASE_LIST', activate)
         self.assertIn('BASE_VAR="baseValue"', activate)
         self.assertIn('export BASE_VAR', activate)
-        self.assertIn('CPPFLAGS="-flag1 -flag2 -baseFlag1 -baseFlag2 $CPPFLAGS"', activate)
+        self.assertIn('CPPFLAGS="-flag1 -flag2 -baseFlag1 -baseFlag2 ${CPPFLAGS+ $CPPFLAGS}"',
+                      activate)
         self.assertIn('export CPPFLAGS', activate)
         self.assertIn('SPECIAL_VAR="dummyValue"', activate)
         self.assertIn('export SPECIAL_VAR', activate)
         self.assertIn('BCKW_SLASH="dummy\\value"', activate)
         self.assertIn('export BCKW_SLASH', activate)
         if os_info.is_windows:
-            self.assertIn('LD_LIBRARY_PATH="dummydir\\lib":"basedir\\lib":$LD_LIBRARY_PATH', activate)
-            self.assertIn('PATH="dummydir\\bin":"basedir\\bin":"samebin":$PATH', activate)
+            self.assertIn('LD_LIBRARY_PATH="dummydir\\lib":"basedir\\lib"${'
+                          'LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}', activate)
+            self.assertIn('PATH="dummydir\\bin":"basedir\\bin":"samebin"${PATH+:$PATH}', activate)
         else:
-            self.assertIn('LD_LIBRARY_PATH="dummydir/lib":"basedir/lib":$LD_LIBRARY_PATH', activate)
-            self.assertIn('PATH="dummydir/bin":"basedir/bin":"samebin":$PATH', activate)
+            self.assertIn('LD_LIBRARY_PATH="dummydir/lib":"basedir/lib"${'
+                          'LD_LIBRARY_PATH+:$LD_LIBRARY_PATH}', activate)
+            self.assertIn('PATH="dummydir/bin":"basedir/bin":"samebin"${PATH+:$PATH}', activate)
         self.assertIn('export LD_LIBRARY_PATH', activate)
         self.assertIn('export PATH', activate)
         deactivate = load(os.path.join(client.current_folder, "deactivate.sh"))
@@ -169,3 +174,23 @@ virtualenv
                "BCKW_SLASH": "old_BCKW_SLASH"}
         with tools.environment_append(env):
             self.basic_test(posix_empty_vars=False)
+
+    @unittest.skipIf(platform.system() == "Windows", "Needs bash")
+    def conditional_parameter_expansion_test(self):
+        # https://github.com/conan-io/conan/issues/3911
+        client = TestClient()
+        client.save({"conanfile.txt": ""})
+        client.run("profile new default --detect")
+        client.run("profile update env.PREPEND_VAR=[1,2,three] default")
+        client.run("install . -g virtualenv")
+        activate = load(os.path.join(client.current_folder, "activate.sh"))
+        self.assertIn("PREPEND_VAR=\"1\":\"2\":\"three\"${PREPEND_VAR+:$PREPEND_VAR}", activate)
+        client.runner("%s -c 'source \"%s/activate.sh\" && env'" % (OSInfo.bash_path(),
+                                                                    client.current_folder))
+        # Check no trailing path separator ":"
+        self.assertNotIn("PREPEND_VAR=1:2:three:", client.out)
+        self.assertIn("PREPEND_VAR=1:2:three", client.out)  # Check correct value
+        # Check old value is preserved
+        client.runner("%s -c 'export PREPEND_VAR=kk && source \"%s/activate.sh\" && env'" %
+                      (OSInfo.bash_path(), client.current_folder))
+        self.assertIn("PREPEND_VAR=1:2:three:kk", client.out)
diff --git a/conans/test/unittests/client/build/meson_test.py b/conans/test/unittests/client/build/meson_test.py
index 50dd0a1e8..53eccd7bc 100644
--- a/conans/test/unittests/client/build/meson_test.py
+++ b/conans/test/unittests/client/build/meson_test.py
@@ -137,3 +137,29 @@ class MesonTest(unittest.TestCase):
 
         meson.install()
         self.assertEquals("ninja -C \"%s\" %s" % (build_expected, args_to_string(["install"])), conan_file.command)
+
+    def prefix_test(self):
+        conan_file = ConanFileMock()
+        conan_file.settings = Settings()
+        conan_file.package_folder = os.getcwd()
+        expected_prefix = '-Dprefix="%s"' % os.getcwd()
+        meson = Meson(conan_file)
+        meson.configure()
+        self.assertIn(expected_prefix, conan_file.command)
+        meson.build()
+        self.assertIn("ninja -C", conan_file.command)
+        meson.install()
+        self.assertIn("ninja -C", conan_file.command)
+
+    def no_prefix_test(self):
+        conan_file = ConanFileMock()
+        conan_file.settings = Settings()
+        conan_file.package_folder = None
+        meson = Meson(conan_file)
+        meson.configure()
+        self.assertNotIn('-Dprefix', conan_file.command)
+        meson.build()
+        self.assertIn("ninja -C", conan_file.command)
+        with self.assertRaises(TypeError):
+            meson.install()
+
diff --git a/conans/test/unittests/client/conf/detect/detected_os_test.py b/conans/test/unittests/client/conf/detect/detected_os_test.py
index e194f7cfb..59379cbf2 100644
--- a/conans/test/unittests/client/conf/detect/detected_os_test.py
+++ b/conans/test/unittests/client/conf/detect/detected_os_test.py
@@ -4,8 +4,7 @@
 
 import unittest
 from mock import mock
-from conans.client.conf.detect import detected_os
-from conans.client.tools import OSInfo
+from conans.client.tools.oss import detected_os, OSInfo
 
 
 class DetectedOSTest(unittest.TestCase):
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 000000000..94911f093
--- /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+:$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 000000000..5ae0a4d96
--- /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"]})
diff --git a/conans/test/unittests/client/tools/oss/__init__.py b/conans/test/unittests/client/tools/oss/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/conans/test/unittests/client/tools/oss/__init__.py
@@ -0,0 +1,1 @@
+
diff --git a/conans/test/unittests/client/tools/oss/get_cross_building_settings_test.py b/conans/test/unittests/client/tools/oss/get_cross_building_settings_test.py
new file mode 100644
index 000000000..f8b765dbd
--- /dev/null
+++ b/conans/test/unittests/client/tools/oss/get_cross_building_settings_test.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from mock import mock
+import unittest
+
+from conans.client.tools.oss import get_cross_building_settings, OSInfo
+from conans.test.utils.conanfile import MockSettings
+
+
+class GetCrossBuildSettingsTest(unittest.TestCase):
+    def test_windows(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Windows')),\
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")),\
+             mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"),\
+             mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Windows", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_cygwin(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='CYGWIN_NT-10.0')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")), \
+             mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"), \
+             mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Windows", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_msys(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MSYS_NT-10.0')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")), \
+             mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"), \
+             mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Windows", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_mingw32(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MINGW32_NT-10.0')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")), \
+             mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"), \
+             mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Windows", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_mingw64(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='MINGW64_NT-10.0')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")), \
+             mock.patch.object(OSInfo, "get_win_version_name", return_value="Windows 98"), \
+             mock.patch.object(OSInfo, "get_win_os_version", return_value="4.0"):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Windows", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_linux(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Linux')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")), \
+             mock.patch.object(OSInfo, '_get_linux_distro_info'):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Linux", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_macos(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='Darwin')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("Macos", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_freebsd(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='FreeBSD')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("FreeBSD", build_os)
+            self.assertEqual("x86_64", build_arch)
+
+    def test_solaris(self):
+        with mock.patch("platform.system", mock.MagicMock(return_value='SunOS')), \
+             mock.patch("platform.machine", mock.MagicMock(return_value="x86_64")):
+            settings = MockSettings({})
+            build_os, build_arch, _, _ = get_cross_building_settings(settings)
+            self.assertEqual("SunOS", build_os)
+            self.assertEqual("x86_64", build_arch)