Repository: conan-io/conan

Issue:
Add C++2a standard
GCC 8 was released a few days ago and added some useful C++2a features such as `std::endian`. I really want to migrate all my Conan packages to `-std=c++2a`.

I think `2a` and `gnu2a` should be added to `cppstd` and then be renamed to `20` and `gnu20` after the publication. This would require support for aliases so packages' hashes won't be changed but we still have several years to develop this feature.


Discussion:

Patch:
diff --git a/conans/client/build/cppstd_flags.py b/conans/client/build/cppstd_flags.py
index 5435d63e6..7711a442f 100644
--- a/conans/client/build/cppstd_flags.py
+++ b/conans/client/build/cppstd_flags.py
@@ -32,11 +32,12 @@ def cppstd_default(compiler, compiler_version):
 
 
 def _clang_cppstd_default(compiler_version):
-    return "gnu98" if Version(compiler_version) < "6.0" else "gnu14"
+    # Official docs are wrong, in 6.0 the default is gnu14 to follow gcc's choice
+    return "gnu98" if Version(compiler_version) < "6" else "gnu14"
 
 
 def _gcc_cppstd_default(compiler_version):
-    return "gnu98" if Version(compiler_version) < "6.1" else "gnu14"
+    return "gnu98" if Version(compiler_version) < "6" else "gnu14"
 
 
 def _visual_cppstd_default(compiler_version):
@@ -46,17 +47,19 @@ def _visual_cppstd_default(compiler_version):
 
 
 def _cppstd_visualstudio(visual_version, cppstd):
-
+    # https://docs.microsoft.com/en-us/cpp/build/reference/std-specify-language-standard-version
     v14 = None
     v17 = None
+    v20 = None
 
     if Version(visual_version) >= "14":
         v14 = "c++14"
         v17 = "c++latest"
     if Version(visual_version) >= "15":
         v17 = "c++17"
+        v20 = "c++latest"
 
-    flag = {"14": v14, "17": v17}.get(str(cppstd), None)
+    flag = {"14": v14, "17": v17, "20": v20}.get(str(cppstd), None)
     return "/std:%s" % flag if flag else None
 
 
@@ -66,7 +69,7 @@ def _cppstd_apple_clang(clang_version, cppstd):
     https://github.com/Kitware/CMake/blob/master/Modules/Compiler/AppleClang-CXX.cmake
     """
 
-    v98 = vgnu98 = v11 = vgnu11 = v14 = vgnu14 = v17 = vgnu17 = None
+    v98 = vgnu98 = v11 = vgnu11 = v14 = vgnu14 = v17 = vgnu17 = v20 = vgnu20 = None
 
     if Version(clang_version) >= "4.0":
         v98 = "c++98"
@@ -93,7 +96,8 @@ def _cppstd_apple_clang(clang_version, cppstd):
     flag = {"98": v98, "gnu98": vgnu98,
             "11": v11, "gnu11": vgnu11,
             "14": v14, "gnu14": vgnu14,
-            "17": v17, "gnu17": vgnu17}.get(cppstd, None)
+            "17": v17, "gnu17": vgnu17,
+            "20": v20, "gnu20": vgnu20}.get(cppstd, None)
 
     return "-std=%s" % flag if flag else None
 
@@ -103,8 +107,10 @@ def _cppstd_clang(clang_version, cppstd):
     Inspired in:
     https://github.com/Kitware/CMake/blob/
     1fe2dc5ef2a1f262b125a2ba6a85f624ce150dd2/Modules/Compiler/Clang-CXX.cmake
+
+    https://clang.llvm.org/cxx_status.html
     """
-    v98 = vgnu98 = v11 = vgnu11 = v14 = vgnu14 = v17 = vgnu17 = None
+    v98 = vgnu98 = v11 = vgnu11 = v14 = vgnu14 = v17 = vgnu17 = v20 = vgnu20 = None
 
     if Version(clang_version) >= "2.1":
         v98 = "c++98"
@@ -131,17 +137,22 @@ def _cppstd_clang(clang_version, cppstd):
         v17 = "c++1z"
         vgnu17 = "gnu++1z"
 
+    if Version(clang_version) >= "6":
+        v20 = "c++2a"
+        vgnu20 = "gnu++2a"
+
     flag = {"98": v98, "gnu98": vgnu98,
             "11": v11, "gnu11": vgnu11,
             "14": v14, "gnu14": vgnu14,
-            "17": v17, "gnu17": vgnu17}.get(cppstd, None)
+            "17": v17, "gnu17": vgnu17,
+            "20": v20, "gnu20": vgnu20}.get(cppstd, None)
     return "-std=%s" % flag if flag else None
 
 
 def _cppstd_gcc(gcc_version, cppstd):
     """https://github.com/Kitware/CMake/blob/master/Modules/Compiler/GNU-CXX.cmake"""
-    # https://gcc.gnu.org/projects/cxx-status.html#cxx98
-    v98 = vgnu98 = v11 = vgnu11 = v14 = vgnu14 = v17 = vgnu17 = None
+    # https://gcc.gnu.org/projects/cxx-status.html
+    v98 = vgnu98 = v11 = vgnu11 = v14 = vgnu14 = v17 = vgnu17 = v20 = vgnu20 = None
 
     if Version(gcc_version) >= "3.4":
         v98 = "c++98"
@@ -165,8 +176,17 @@ def _cppstd_gcc(gcc_version, cppstd):
         v17 = "c++1z"
         vgnu17 = "gnu++1z"
 
+    if Version(gcc_version) >= "5.2":  # Not sure if even in 5.1 gnu17 is valid, but gnu1z is
+        v17 = "c++17"
+        vgnu17 = "gnu++17"
+
+    if Version(gcc_version) >= "8":
+        v20 = "c++2a"
+        vgnu20 = "gnu++2a"
+
     flag = {"98": v98, "gnu98": vgnu98,
             "11": v11, "gnu11": vgnu11,
             "14": v14, "gnu14": vgnu14,
-            "17": v17, "gnu17": vgnu17}.get(cppstd)
+            "17": v17, "gnu17": vgnu17,
+            "20": v20, "gnu20": vgnu20}.get(cppstd)
     return "-std=%s" % flag if flag else None
diff --git a/conans/client/build/msbuild.py b/conans/client/build/msbuild.py
index 9477fb964..d15bf4b57 100644
--- a/conans/client/build/msbuild.py
+++ b/conans/client/build/msbuild.py
@@ -117,14 +117,21 @@ class MSBuild(object):
             flags = vs_build_type_flags(self._settings)
             flags.append(vs_std_cpp(self._settings))
 
+        flags_str = " ".join(list(filter(None, flags))) # Removes empty and None elements
+        additional_node = "<AdditionalOptions>" \
+                          "{} %(AdditionalOptions)" \
+                          "</AdditionalOptions>".format(flags_str) if flags_str else ""
+        runtime_node = "<RuntimeLibrary>" \
+                       "{}" \
+                       "</RuntimeLibrary>".format(runtime_library) if runtime_library else ""
         template = """<?xml version="1.0" encoding="utf-8"?>
-    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-      <ItemDefinitionGroup>
-        <ClCompile>
-          <RuntimeLibrary>{runtime}</RuntimeLibrary>
-          <AdditionalOptions>{compiler_flags} %(AdditionalOptions)</AdditionalOptions>
-        </ClCompile>
-      </ItemDefinitionGroup>
-    </Project>""".format(**{"runtime": runtime_library,
-                            "compiler_flags": " ".join([flag for flag in flags if flag])})
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemDefinitionGroup>
+    <ClCompile>
+      {runtime_node}
+      {additional_node}
+    </ClCompile>
+  </ItemDefinitionGroup>
+</Project>""".format(**{"runtime_node": runtime_node,
+                        "additional_node": additional_node})
         return template
diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py
index dfb96b9e6..2a760693c 100644
--- a/conans/client/conf/__init__.py
+++ b/conans/client/conf/__init__.py
@@ -53,7 +53,8 @@ compiler:
         version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
                   "5", "5.1", "5.2", "5.3", "5.4", "5.5",
                   "6", "6.1", "6.2", "6.3", "6.4",
-                  "7", "7.1", "7.2", "7.3"]
+                  "7", "7.1", "7.2", "7.3",
+                  "8", "8.1"]
         libcxx: [libstdc++, libstdc++11]
         threads: [None, posix, win32] #  Windows MinGW
         exception: [None, dwarf2, sjlj, seh] # Windows MinGW
@@ -62,7 +63,7 @@ compiler:
         version: ["8", "9", "10", "11", "12", "14", "15"]
         toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp, v140, v140_xp, v140_clang_c2, LLVM-vs2014, LLVM-vs2014_xp, v141, v141_xp, v141_clang_c2]
     clang:
-        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0", "5.0", "6.0"]
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0", "5.0", "6.0", "7.0"]
         libcxx: [libstdc++, libstdc++11, libc++]
     apple-clang:
         version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1"]
diff --git a/conans/client/migrations.py b/conans/client/migrations.py
index 27a86af1f..bbb121bcf 100644
--- a/conans/client/migrations.py
+++ b/conans/client/migrations.py
@@ -41,17 +41,15 @@ class ClientMigrator(Migrator):
         # VERSION 0.1
         if old_version is None:
             return
-        if old_version < Version("1.2.1"):
+        if old_version < Version("1.4.0"):
             old_settings = """
 # Only for cross building, 'os_build/arch_build' is the system that runs Conan
 os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
 arch_build: [x86, x86_64, ppc64le, ppc64, armv6, armv7, armv7hf, armv8, sparc, sparcv9, mips, mips64, avr, armv7s, armv7k]
-
 # Only for building cross compilation tools, 'os_target/arch_target' is the system for
 # which the tools generate code
 os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
 arch_target: [x86, x86_64, ppc64le, ppc64, armv6, armv7, armv7hf, armv8, sparc, sparcv9, mips, mips64, avr, armv7s, armv7k]
-
 # Rest of the settings are "host" settings:
 # - For native building/cross building: Where the library/program will run.
 # - For building cross compilation tools: Where the cross compiler will run.
@@ -96,11 +94,10 @@ compiler:
         version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0", "5.0", "6.0"]
         libcxx: [libstdc++, libstdc++11, libc++]
     apple-clang:
-        version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0"]
+        version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1"]
         libcxx: [libstdc++, libc++]
-
-build_type: [None, Debug, Release]
-cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17]
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
 """
             self._update_settings_yml(old_settings)
 
diff --git a/conans/client/tools/win.py b/conans/client/tools/win.py
index d025ec4dd..bdfa9cc67 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -300,12 +300,18 @@ def vcvars_dict(settings, arch=None, compiler_version=None, force=False, filter_
     new_env = {}
     start_reached = False
     for line in ret.splitlines():
+        line = line.strip()
         if not start_reached:
             if "__BEGINS__" in line:
                 start_reached = True
             continue
-        name_var, value = line.split("=", 1)
-        new_env[name_var] = value
+        if line == "\n" or not line:
+            continue
+        try:
+            name_var, value = line.split("=", 1)
+            new_env[name_var] = value
+        except ValueError:
+            pass
 
     if filter_known_paths:
         def relevant_path(path):


Test patch:
diff --git a/conans/test/build_helpers/autotools_configure_test.py b/conans/test/build_helpers/autotools_configure_test.py
index 4134d9bee..4b93b7529 100644
--- a/conans/test/build_helpers/autotools_configure_test.py
+++ b/conans/test/build_helpers/autotools_configure_test.py
@@ -57,7 +57,7 @@ class AutoToolsConfigureTest(unittest.TestCase):
         conanfile = MockConanfile(settings, options)
         be = AutoToolsBuildEnvironment(conanfile)
         expected = be.vars["CXXFLAGS"]
-        self.assertIn("-std=c++1z", expected)
+        self.assertIn("-std=c++17", expected)
 
         # Invalid one for GCC
         settings = MockSettings({"build_type": "Release",
diff --git a/conans/test/build_helpers/cpp_std_flags_test.py b/conans/test/build_helpers/cpp_std_flags_test.py
index 7e7c1c5e4..6d80880d2 100644
--- a/conans/test/build_helpers/cpp_std_flags_test.py
+++ b/conans/test/build_helpers/cpp_std_flags_test.py
@@ -41,14 +41,20 @@ class CompilerFlagsTest(unittest.TestCase):
 
         self.assertEquals(cppstd_flag("gcc", "7", "11"), '-std=c++11')
         self.assertEquals(cppstd_flag("gcc", "7", "14"), '-std=c++14')
-        self.assertEquals(cppstd_flag("gcc", "7", "17"), '-std=c++1z')
+        self.assertEquals(cppstd_flag("gcc", "7", "17"), '-std=c++17')
+
+        self.assertEquals(cppstd_flag("gcc", "8", "11"), '-std=c++11')
+        self.assertEquals(cppstd_flag("gcc", "8", "14"), '-std=c++14')
+        self.assertEquals(cppstd_flag("gcc", "8", "17"), '-std=c++17')
+        self.assertEquals(cppstd_flag("gcc", "8", "20"), '-std=c++2a')
 
     def test_gcc_cppstd_defaults(self):
         self.assertEquals(cppstd_default("gcc", "4"), "gnu98")
         self.assertEquals(cppstd_default("gcc", "5"), "gnu98")
-        self.assertEquals(cppstd_default("gcc", "6"), "gnu98")
+        self.assertEquals(cppstd_default("gcc", "6"), "gnu14")
         self.assertEquals(cppstd_default("gcc", "6.1"), "gnu14")
         self.assertEquals(cppstd_default("gcc", "7.3"), "gnu14")
+        self.assertEquals(cppstd_default("gcc", "8.1"), "gnu14")
 
     def test_clang_cppstd_flags(self):
         self.assertEquals(cppstd_flag("clang", "2.0", "98"), None)
@@ -84,9 +90,10 @@ class CompilerFlagsTest(unittest.TestCase):
         self.assertEquals(cppstd_flag("clang", "5.1", "14"), '-std=c++14')
         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++17')
+        self.assertEquals(cppstd_flag("clang", "6", "11"), '-std=c++11')
+        self.assertEquals(cppstd_flag("clang", "6", "14"), '-std=c++14')
+        self.assertEquals(cppstd_flag("clang", "6", "17"), '-std=c++17')
+        self.assertEquals(cppstd_flag("clang", "6", "20"), '-std=c++2a')
 
     def test_clang_cppstd_defaults(self):
         self.assertEquals(cppstd_default("clang", "2"), "gnu98")
@@ -97,6 +104,7 @@ class CompilerFlagsTest(unittest.TestCase):
         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", "6"), "gnu14")
         self.assertEquals(cppstd_default("clang", "7"), "gnu14")
 
     def test_apple_clang_cppstd_flags(self):
@@ -162,6 +170,7 @@ class CompilerFlagsTest(unittest.TestCase):
         self.assertEquals(cppstd_flag("Visual Studio", "17", "11"), None)
         self.assertEquals(cppstd_flag("Visual Studio", "17", "14"), '/std:c++14')
         self.assertEquals(cppstd_flag("Visual Studio", "17", "17"), '/std:c++17')
+        self.assertEquals(cppstd_flag("Visual Studio", "17", "20"), '/std:c++latest')
 
     def test_visual_cppstd_defaults(self):
         self.assertEquals(cppstd_default("Visual Studio", "11"), None)
diff --git a/conans/test/build_helpers/msbuild_test.py b/conans/test/build_helpers/msbuild_test.py
index 7294136d0..d9792d0f2 100644
--- a/conans/test/build_helpers/msbuild_test.py
+++ b/conans/test/build_helpers/msbuild_test.py
@@ -16,7 +16,8 @@ class MSBuildTest(unittest.TestCase):
     def dont_mess_with_build_type_test(self):
         settings = MockSettings({"build_type": "Debug",
                                  "compiler": "Visual Studio",
-                                 "arch": "x86_64"})
+                                 "arch": "x86_64",
+                                 "compiler.runtime": "MDd"})
         conanfile = MockConanfile(settings)
         msbuild = MSBuild(conanfile)
         self.assertEquals(msbuild.build_env.flags, ["-Zi", "-Ob0", "-Od"])
@@ -30,6 +31,16 @@ class MSBuildTest(unittest.TestCase):
 
         self.assertNotIn("-Ob0", template)
         self.assertNotIn("-Od", template)
+        self.assertIn("<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>", template)
+
+    def without_runtime_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64"})
+        conanfile = MockConanfile(settings)
+        msbuild = MSBuild(conanfile)
+        template = msbuild._get_props_file_contents()
+        self.assertNotIn("<RuntimeLibrary>", template)
 
     @attr('slow')
     def build_vs_project_test(self):
diff --git a/conans/test/generators/compiler_args_test.py b/conans/test/generators/compiler_args_test.py
index 7a6c1415c..96d14ffbf 100644
--- a/conans/test/generators/compiler_args_test.py
+++ b/conans/test/generators/compiler_args_test.py
@@ -46,7 +46,7 @@ class CompilerArgsTest(unittest.TestCase):
         gcc = GCCGenerator(conan_file)
         self.assertEquals('-Dmydefine1 -Ipath/to/include1 cxx_flag1 c_flag1 -m32 -O3 -s -DNDEBUG '
                           '-Wl,-rpath="path/to/lib1" '
-                          '-Lpath/to/lib1 -lmylib -std=gnu++1z', gcc.content)
+                          '-Lpath/to/lib1 -lmylib -std=gnu++17', gcc.content)
 
         settings.arch = "x86_64"
         settings.build_type = "Debug"
@@ -55,14 +55,14 @@ class CompilerArgsTest(unittest.TestCase):
         gcc = GCCGenerator(conan_file)
         self.assertEquals('-Dmydefine1 -Ipath/to/include1 cxx_flag1 c_flag1 -m64 -g '
                           '-Wl,-rpath="path/to/lib1" -Lpath/to/lib1 -lmylib '
-                          '-D_GLIBCXX_USE_CXX11_ABI=1 -std=gnu++1z',
+                          '-D_GLIBCXX_USE_CXX11_ABI=1 -std=gnu++17',
                           gcc.content)
 
         settings.compiler.libcxx = "libstdc++"
         gcc = GCCGenerator(conan_file)
         self.assertEquals('-Dmydefine1 -Ipath/to/include1 cxx_flag1 c_flag1 -m64 -g '
                           '-Wl,-rpath="path/to/lib1" -Lpath/to/lib1 -lmylib '
-                          '-D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++1z',
+                          '-D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++17',
                           gcc.content)
 
         settings.os = "Windows"
diff --git a/conans/test/util/tools_test.py b/conans/test/util/tools_test.py
index a8984e93d..1334edce9 100644
--- a/conans/test/util/tools_test.py
+++ b/conans/test/util/tools_test.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 
+import mock
 import os
 import platform
 import unittest
@@ -625,6 +626,43 @@ class MyConan(ConanFile):
             client.run("create . conan/testing")
             self.assertIn("VCINSTALLDIR set to: None", client.out)
 
+    def vcvars_dict_test(self):
+        # https://github.com/conan-io/conan/issues/2904
+        output_with_newline_and_spaces = """__BEGINS__
+     PROCESSOR_ARCHITECTURE=AMD64
+
+PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 158 Stepping 9, GenuineIntel
+
+
+ PROCESSOR_LEVEL=6 
+
+PROCESSOR_REVISION=9e09    
+
+                         
+set nl=^
+env_var=
+without_equals_sign
+
+ProgramFiles(x86)=C:\Program Files (x86)
+       
+""".encode("utf-8")
+
+        def vcvars_command_mock(settings, arch, compiler_version, force):  # @UnusedVariable
+            return "unused command"
+
+        def subprocess_check_output_mock(cmd, shell):
+            self.assertIn("unused command", cmd)
+            return output_with_newline_and_spaces
+
+        with mock.patch('conans.client.tools.win.vcvars_command', new=vcvars_command_mock):
+            with mock.patch('subprocess.check_output', new=subprocess_check_output_mock):
+                vars = tools.vcvars_dict(None)
+                self.assertEqual(vars["PROCESSOR_ARCHITECTURE"], "AMD64")
+                self.assertEqual(vars["PROCESSOR_IDENTIFIER"], "Intel64 Family 6 Model 158 Stepping 9, GenuineIntel")
+                self.assertEqual(vars["PROCESSOR_LEVEL"], "6")
+                self.assertEqual(vars["PROCESSOR_REVISION"], "9e09")
+                self.assertEqual(vars["ProgramFiles(x86)"], "C:\Program Files (x86)")
+
     def run_in_bash_test(self):
         if platform.system() != "Windows":
             return