Repository: conan-io/conan

Issue:
Clang 6.0 implications in cppstd flags
We have added to the `settings.yml` clang 6.0, but we have to review the `cppstd` flags and `package_id` to adjust it correctly and knowing the default one of the compiler to keep the compatibility of packages that do not specify the `cppstd` setting.


Discussion:
memsharded: Good news are that no package in conan-center uses this setting.

Patch:
diff --git a/.ci/jenkins/runner.py b/.ci/jenkins/runner.py
index cf747a165..513de3284 100644
--- a/.ci/jenkins/runner.py
+++ b/.ci/jenkins/runner.py
@@ -65,6 +65,7 @@ def run_tests(module_path, pyver, source_folder, tmp_folder,
 
     env = get_environ(tmp_folder)
     env["PYTHONPATH"] = source_folder
+    env["CONAN_RECIPE_LINTER"] = "False"
     env["CONAN_LOGGING_LEVEL"] = "50" if platform.system() == "Darwin" else "50"
     env["CHANGE_AUTHOR_DISPLAY_NAME"] = ""
     with chdir(source_folder):
diff --git a/conans/client/build/compiler_flags.py b/conans/client/build/compiler_flags.py
index 6763defaa..8a483fb63 100644
--- a/conans/client/build/compiler_flags.py
+++ b/conans/client/build/compiler_flags.py
@@ -64,9 +64,9 @@ def libcxx_flag(compiler, libcxx):
             return '-stdlib=libc++'
     elif str(compiler) == 'sun-cc':
         return ({"libCstd": "-library=Cstd",
-                         "libstdcxx": "-library=stdcxx4",
-                         "libstlport": "-library=stlport4",
-                         "libstdc++": "-library=stdcpp"}.get(libcxx, ""))
+                            "libstdcxx": "-library=stdcxx4",
+                            "libstlport": "-library=stlport4",
+                            "libstdc++": "-library=stdcpp"}.get(libcxx, ""))
     return ""
 
 
diff --git a/conans/client/build/cppstd_flags.py b/conans/client/build/cppstd_flags.py
index 407cca443..e5ca936c3 100644
--- a/conans/client/build/cppstd_flags.py
+++ b/conans/client/build/cppstd_flags.py
@@ -24,16 +24,18 @@ def cppstd_flag(compiler, compiler_version, cppstd):
 
 
 def cppstd_default(compiler, compiler_version):
-
     default = {"gcc": _gcc_cppstd_default(compiler_version),
-               "clang": "gnu++98",
-               "apple-clang": "gnu++98",
+               "clang": _clang_cppstd_default(compiler_version),
+               "apple-clang": "gnu98",
                "Visual Studio": _visual_cppstd_default(compiler_version)}.get(str(compiler), None)
     return default
 
 
-def _gcc_cppstd_default(compiler_version):
+def _clang_cppstd_default(compiler_version):
+    return "gnu98" if Version(compiler_version) < "6.0" else "gnu14"
+
 
+def _gcc_cppstd_default(compiler_version):
     return "gnu98" if Version(compiler_version) < "6.1" else "gnu14"
 
 
@@ -113,12 +115,17 @@ def _cppstd_clang(clang_version, cppstd):
     if Version(clang_version) >= "3.5":
         v14 = "c++14"
         vgnu14 = "gnu++14"
-        v17 = "c++1z"
-        vgnu17 = "gnu++1z"
     elif Version(clang_version) >= "3.4":
         v14 = "c++1y"
         vgnu14 = "gnu++1y"
 
+    if Version(clang_version) >= "5":
+        v17 = "c++17"
+        vgnu17 = "gnu++17"
+    elif Version(clang_version) >= "3.5":
+        v17 = "c++1z"
+        vgnu17 = "gnu++1z"
+
     flag = {"98": v98, "gnu98": vgnu98,
             "11": v11, "gnu11": vgnu11,
             "14": v14, "gnu14": vgnu14,
diff --git a/conans/client/cmd/export_linter.py b/conans/client/cmd/export_linter.py
index 2205b56e2..20f63976e 100644
--- a/conans/client/cmd/export_linter.py
+++ b/conans/client/cmd/export_linter.py
@@ -1,14 +1,12 @@
 import os
 import json
 import sys
-import six
-from six import StringIO
-
-from pylint.reporters.json import JSONReporter
-from pylint.lint import Run
+import platform
 
 from conans.client.output import Color
 from conans.errors import ConanException
+from subprocess import PIPE, Popen
+from conans import __path__ as root_path
 
 
 def conan_linter(conanfile_path, out):
@@ -18,76 +16,48 @@ def conan_linter(conanfile_path, out):
     apply_lint = os.environ.get("CONAN_RECIPE_LINTER", True)
     if not apply_lint or apply_lint == "False":
         return
+
+    dir_path = os.path.dirname(root_path[0])
+    dirname = os.path.dirname(conanfile_path)
+    hook = '--init-hook="import sys;sys.path.extend([\'%s\', \'%s\'])"' % (dirname, dir_path)
+
     try:
-        dirname = os.path.dirname(conanfile_path)
-        sys.path.append(dirname)
-        py3_msgs = _lint_py3(conanfile_path)
+        py3_msgs = None
+        msgs, py3_msgs = _normal_linter(conanfile_path, hook)
+    except Exception as e:
+        out.warn("Failed pylint: %s" % e)
+    else:
         if py3_msgs:
             out.writeln("Python 3 incompatibilities\n    ERROR: %s"
                         % "\n    ERROR: ".join(py3_msgs),
                         front=Color.BRIGHT_MAGENTA)
-        msgs = _normal_linter(conanfile_path)
         if msgs:
             out.writeln("Linter warnings\n    WARN: %s" % "\n    WARN: ".join(msgs),
                         front=Color.MAGENTA)
         pylint_werr = os.environ.get("CONAN_PYLINT_WERR", None)
         if pylint_werr and (py3_msgs or msgs):
             raise ConanException("Package recipe has linter errors. Please fix them.")
-    finally:
-        sys.path.pop()
-
-
-class _WritableObject(object):
-    def __init__(self):
-        self.content = []
-
-    def write(self, st):
-        self.content.append(st)
 
 
 def _runner(args):
-    try:
-        output = _WritableObject()
-        stdout_ = sys.stderr
-        stream = StringIO()
-        sys.stderr = stream
-        Run(args, reporter=JSONReporter(output), exit=False)
-    finally:
-        sys.stderr = stdout_
-    try:
-        output = "".join(output.content)
-        return json.loads(output)
-    except ValueError:
-        return []
+    command = ["pylint",  "--output-format=json"] + args
+    command = " ".join(command)
+    shell = True if platform.system() != "Windows" else False
+    proc = Popen(command, shell=shell, bufsize=10, stdout=PIPE, stderr=PIPE)
+    stdout, _ = proc.communicate()
+    return json.loads(stdout) if stdout else {}
 
 
-def _lint_py3(conanfile_path):
-    if six.PY3:
-        return
-
-    args = ['--py3k', "--reports=no", "--disable=no-absolute-import", "--persistent=no",
-            conanfile_path]
-
-    output_json = _runner(args)
-
-    result = []
-    for msg in output_json:
-        if msg.get("type") in ("warning", "error"):
-            result.append("Py3 incompatibility. Line %s: %s"
-                          % (msg.get("line"), msg.get("message")))
-    return result
-
-
-def _normal_linter(conanfile_path):
-    args = ["--reports=no", "--disable=no-absolute-import", "--persistent=no", conanfile_path]
+def _normal_linter(conanfile_path, hook):
+    args = ['--py3k', "--enable=all", "--reports=no", "--disable=no-absolute-import", "--persistent=no",
+            hook, '"%s"' % conanfile_path]
     pylintrc = os.environ.get("CONAN_PYLINTRC", None)
     if pylintrc:
         if not os.path.exists(pylintrc):
             raise ConanException("File %s defined by PYLINTRC doesn't exist" % pylintrc)
-        args.append('--rcfile=%s' % pylintrc)
+        args.append('--rcfile="%s"' % pylintrc)
 
     output_json = _runner(args)
-
     dynamic_fields = ("source_folder", "build_folder", "package_folder", "info_build",
                       "build_requires", "info")
 
@@ -107,9 +77,14 @@ def _normal_linter(conanfile_path):
         return True
 
     result = []
+    py3msgs = []
     for msg in output_json:
         if msg.get("type") in ("warning", "error"):
-            if _accept_message(msg):
+            message_id = msg.get("symbol")
+            if message_id in ("print-statement", "dict-iter-method"):
+                py3msgs.append("Py3 incompatibility. Line %s: %s"
+                               % (msg.get("line"), msg.get("message")))
+            elif _accept_message(msg):
                 result.append("Linter. Line %s: %s" % (msg.get("line"), msg.get("message")))
 
-    return result
+    return result, py3msgs
diff --git a/conans/client/command.py b/conans/client/command.py
index b27c913c5..df9034e9a 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -35,10 +35,13 @@ class Extender(argparse.Action):
             # share this destination.
             parser.set_defaults(**{self.dest: None})
 
-        try:
-            dest.extend(values)
-        except ValueError:
+        if isinstance(values, str):
             dest.append(values)
+        elif values:
+            try:
+                dest.extend(values)
+            except ValueError:
+                dest.append(values)
 
 
 class OnceArgument(argparse.Action):
@@ -1235,7 +1238,7 @@ def _add_common_install_arguments(parser, build_help):
                              '-e CXX=/usr/bin/clang++',
                         nargs=1, action=Extender)
     if build_help:
-        parser.add_argument("-b", "--build", action=Extender, nargs="*", help=build_help)
+        parser.add_argument("-b", "--build", action=Extender, nargs="?", help=build_help)
 
 
 _help_build_policies = '''Optional, use it to choose if you want to build from sources:
diff --git a/conans/client/manager.py b/conans/client/manager.py
index 6fb69bba0..d5415ace3 100644
--- a/conans/client/manager.py
+++ b/conans/client/manager.py
@@ -538,7 +538,9 @@ class ConanManager(object):
             if not remote:
                 remote = remote_proxy.registry.default_remote.name
             name, password = self._user_io.request_login(remote_name=remote, username=name)
-        return remote_proxy.authenticate(name, password)
+
+        all_remotes = True if remote is None else False
+        return remote_proxy.authenticate(name, password, all_remotes=all_remotes)
 
     def get_path(self, reference, package_id=None, path=None, remote=None):
         remote_proxy = ConanProxy(self._client_cache, self._user_io, self._remote_manager, remote)
diff --git a/conans/client/proxy.py b/conans/client/proxy.py
index 19f114f3e..bdfd72041 100644
--- a/conans/client/proxy.py
+++ b/conans/client/proxy.py
@@ -395,13 +395,22 @@ class ConanProxy(object):
             output.warn('Binary for %s not in remote: %s' % (package_id, str(e)))
             return False
 
-    def authenticate(self, name, password):
-        if not name:  # List all users, from all remotes
-            remotes = self._registry.remotes
-            if not remotes:
-                self._out.error("No remotes defined")
-            for remote in remotes:
-                self._remote_manager.authenticate(remote, None, None)
-            return
-        remote, _ = self._get_remote()
-        return self._remote_manager.authenticate(remote, name, password)
+    def authenticate(self, name, password, all_remotes=False):
+        """
+        Manage user auth against remote.
+        Also displays a list of authenticated users against remote(s) if user name is evaluated to False.
+
+        :param name: user name string
+        :param password: password string
+        :param all_remotes: True/False to use all available remotes to display a list of authenticated users if
+                            user name is evaluated to False.
+        """
+        current_remote, _ = self._get_remote()
+
+        if name:
+            return self._remote_manager.authenticate(current_remote, name, password)
+
+        # List all users from required remotes
+        remotes = self._registry.remotes if all_remotes else [current_remote]
+        for remote in remotes:
+            self._remote_manager.authenticate(remote, None, None)
diff --git a/conans/client/tools/oss.py b/conans/client/tools/oss.py
index cd7aa79b3..d0713c0e5 100644
--- a/conans/client/tools/oss.py
+++ b/conans/client/tools/oss.py
@@ -123,7 +123,7 @@ class OSInfo(object):
     def with_zypper(self):
         return self.is_linux and self.linux_distro in \
             ("opensuse", "sles")
-    
+
     @staticmethod
     def get_win_os_version():
         """
diff --git a/pyinstaller.py b/pyinstaller.py
index 485d832a4..368963699 100644
--- a/pyinstaller.py
+++ b/pyinstaller.py
@@ -87,7 +87,7 @@ def pyinstall(source_folder):
     conan_path = os.path.join(source_folder, 'conans', 'conan.py')
     conan_server_path = os.path.join(source_folder, 'conans', 'conan_server.py')
     conan_build_info_path = os.path.join(source_folder, "conans/build_info/command.py")
-    hidden = "--hidden-import=glob --hidden-import=pylint.reporters.text"
+    hidden = "--hidden-import=glob"
     if platform.system() != "Windows":
         hidden += " --hidden-import=setuptools.msvc"
         win_ver = ""


Test patch:
diff --git a/conans/test/build_helpers/cpp_std_flags_test.py b/conans/test/build_helpers/cpp_std_flags_test.py
index a8448b6c2..b110bb3f4 100644
--- a/conans/test/build_helpers/cpp_std_flags_test.py
+++ b/conans/test/build_helpers/cpp_std_flags_test.py
@@ -78,26 +78,26 @@ class CompilerFlagsTest(unittest.TestCase):
         self.assertEquals(cppstd_flag("clang", "5", "11"), '-std=c++11')
         self.assertEquals(cppstd_flag("clang", "5", "14"), '-std=c++14')
         self.assertEquals(cppstd_flag("clang", "5", "gnu14"), '-std=gnu++14')
-        self.assertEquals(cppstd_flag("clang", "5", "17"), '-std=c++1z')
+        self.assertEquals(cppstd_flag("clang", "5", "17"), '-std=c++17')
 
         self.assertEquals(cppstd_flag("clang", "5.1", "11"), '-std=c++11')
         self.assertEquals(cppstd_flag("clang", "5.1", "14"), '-std=c++14')
-        self.assertEquals(cppstd_flag("clang", "5.1", "17"), '-std=c++1z')
+        self.assertEquals(cppstd_flag("clang", "5.1", "17"), '-std=c++17')
 
         self.assertEquals(cppstd_flag("clang", "7", "11"), '-std=c++11')
         self.assertEquals(cppstd_flag("clang", "7", "14"), '-std=c++14')
-        self.assertEquals(cppstd_flag("clang", "7", "17"), '-std=c++1z')
+        self.assertEquals(cppstd_flag("clang", "7", "17"), '-std=c++17')
 
     def test_clang_cppstd_defaults(self):
-        self.assertEquals(cppstd_default("clang", "2"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "2.1"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "3.0"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "3.1"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "3.4"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "3.5"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "5"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "5.1"), "gnu++98")
-        self.assertEquals(cppstd_default("clang", "7"), "gnu++98")
+        self.assertEquals(cppstd_default("clang", "2"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "2.1"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "3.0"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "3.1"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "3.4"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "3.5"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "5"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "5.1"), "gnu98")
+        self.assertEquals(cppstd_default("clang", "7"), "gnu14")
 
     def test_apple_clang_cppstd_flags(self):
         self.assertEquals(cppstd_flag("apple-clang", "3.9", "98"), None)
@@ -137,14 +137,14 @@ class CompilerFlagsTest(unittest.TestCase):
         self.assertEquals(cppstd_flag("apple-clang", "9", "17"), "-std=c++1z")
 
     def test_apple_clang_cppstd_defaults(self):
-        self.assertEquals(cppstd_default("apple-clang", "2"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "3"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "4"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "5"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "6"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "7"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "8"), "gnu++98")
-        self.assertEquals(cppstd_default("apple-clang", "9"), "gnu++98")
+        self.assertEquals(cppstd_default("apple-clang", "2"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "3"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "4"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "5"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "6"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "7"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "8"), "gnu98")
+        self.assertEquals(cppstd_default("apple-clang", "9"), "gnu98")
 
     def test_visual_cppstd_flags(self):
         self.assertEquals(cppstd_flag("Visual Studio", "12", "11"), None)
diff --git a/conans/test/command/export_linter_test.py b/conans/test/command/export_linter_test.py
index 3fd7b0da6..54b320739 100644
--- a/conans/test/command/export_linter_test.py
+++ b/conans/test/command/export_linter_test.py
@@ -3,6 +3,7 @@ from conans.paths import CONANFILE
 from conans.test.utils.tools import TestClient
 import six
 import os
+from conans import tools
 
 
 conanfile = """
@@ -19,6 +20,14 @@ class TestConan(ConanFile):
 
 class ExportLinterTest(unittest.TestCase):
 
+    def setUp(self):
+        self.old_env = dict(os.environ)
+        os.environ["CONAN_RECIPE_LINTER"] = "True"
+
+    def tearDown(self):
+        os.environ.clear()
+        os.environ.update(self.old_env)
+
     def test_basic(self):
         client = TestClient()
         client.save({CONANFILE: conanfile})
@@ -36,10 +45,10 @@ class ExportLinterTest(unittest.TestCase):
     def test_disable_linter(self):
         client = TestClient()
         client.save({CONANFILE: conanfile})
-        client.run("config set general.recipe_linter=False")
-        client.run("export . lasote/stable")
-        self.assertNotIn("ERROR: Py3 incompatibility", client.user_io.out)
-        self.assertNotIn("WARN: Linter", client.user_io.out)
+        with tools.environment_append({"CONAN_RECIPE_LINTER": "False"}):
+            client.run("export . lasote/stable")
+            self.assertNotIn("ERROR: Py3 incompatibility", client.user_io.out)
+            self.assertNotIn("WARN: Linter", client.user_io.out)
 
     def test_custom_rc_linter(self):
         client = TestClient()
@@ -127,7 +136,6 @@ class BaseConan(ConanFile):
                       client.user_io.out)
 
     def export_deploy_test(self):
-
         conanfile = """
 from conans import ConanFile
 class BaseConan(ConanFile):
diff --git a/conans/test/command/install_test.py b/conans/test/command/install_test.py
index 9cc8437e9..543241c62 100644
--- a/conans/test/command/install_test.py
+++ b/conans/test/command/install_test.py
@@ -453,3 +453,42 @@ class Pkg(ConanFile):
         self.assertTrue(error)
         self.assertIn("ERROR: Unable to find 'Hello/0.1@lasote/stable' in remotes",
                       client.out)
+
+    def install_argument_order_test(self):
+        # https://github.com/conan-io/conan/issues/2520
+
+        conanfile_boost = """from conans import ConanFile
+class BoostConan(ConanFile):
+    name = "boost"
+    version = "0.1"
+    options = {"shared": [True, False]}
+    default_options = "shared=True"
+"""
+        conanfile = """from conans import ConanFile
+class TestConan(ConanFile):
+    name = "Hello"
+    version = "0.1"
+    requires = "boost/0.1@conan/stable"
+"""
+        client = TestClient()
+        client.save({"conanfile.py": conanfile,
+                     "conanfile_boost.py": conanfile_boost})
+        client.run("create conanfile_boost.py conan/stable")
+        client.run("install . -o boost:shared=True --build=missing")
+        output_0 = "%s" % client.out
+        client.run("install . -o boost:shared=True --build missing")
+        output_1 = "%s" % client.out
+        client.run("install -o boost:shared=True . --build missing")
+        output_2 = "%s" % client.out
+        client.run("install -o boost:shared=True --build missing .")
+        output_3 = "%s" % client.out
+        self.assertNotIn("ERROR", output_3)
+        self.assertEqual(output_0, output_1)
+        self.assertEqual(output_1, output_2)
+        self.assertEqual(output_2, output_3)
+
+        client.run("install -o boost:shared=True --build boost . --build missing")
+        output_4 = "%s" % client.out
+        client.run("install -o boost:shared=True --build missing --build boost .")
+        output_5 = "%s" % client.out
+        self.assertEqual(output_4, output_5)
diff --git a/conans/test/command/user_test.py b/conans/test/command/user_test.py
index a0a6a8a60..c247b910f 100644
--- a/conans/test/command/user_test.py
+++ b/conans/test/command/user_test.py
@@ -4,13 +4,44 @@ from conans.test.utils.tools import TestClient, TestServer
 
 class UserTest(unittest.TestCase):
 
-    def test_command_user(self):
-        """ Test that the user can be shown and changed, and it is reflected in the
-        user cache localdb
+    def test_command_user_no_remotes(self):
+        """ Test that proper error is reported when no remotes are defined and conan user is executed
         """
         client = TestClient()
-        client.run('user')
-        self.assertIn("ERROR: No remotes defined", client.user_io.out)
+        with self.assertRaises(Exception):
+            client.run("user")
+        self.assertIn("ERROR: No default remote defined", client.user_io.out)
+
+        with self.assertRaises(Exception):
+            client.run("user -r wrong_remote")
+        self.assertIn("ERROR: No remote 'wrong_remote' defined", client.user_io.out)
+
+    def test_command_user_list(self):
+        """ Test list of user is reported for all remotes or queried remote
+        """
+        servers = {
+            "default": TestServer(),
+            "test_remote_1": TestServer(),
+        }
+        client = TestClient(servers=servers)
+
+        # Test with wrong remote right error is reported
+        with self.assertRaises(Exception):
+            client.run("user -r Test_Wrong_Remote")
+        self.assertIn("ERROR: No remote 'Test_Wrong_Remote' defined", client.user_io.out)
+
+        # Test user list for requested remote reported
+        client.run("user -r test_remote_1")
+        self.assertIn("Current 'test_remote_1' user: None (anonymous)", client.user_io.out)
+        self.assertNotIn("Current 'default' user: None (anonymous)", client.user_io.out)
+
+        # Test user list for all remotes is reported
+        client.run("user")
+        self.assertIn(
+            ("Current 'default' user: None (anonymous)\n"
+             "Current 'test_remote_1' user: None (anonymous)"),
+            client.user_io.out
+        )
 
     def test_with_remote_no_connect(self):
         test_server = TestServer()