{"id":5765,"date":"2020-08-03T07:00:06","date_gmt":"2020-08-03T14:00:06","guid":{"rendered":"http:\/\/writeasync.net\/?p=5765"},"modified":"2020-08-01T15:38:05","modified_gmt":"2020-08-01T22:38:05","slug":"cross-platform-without-complexity","status":"publish","type":"post","link":"https:\/\/writeasync.net\/?p=5765","title":{"rendered":"Cross-platform without complexity: VS IDE"},"content":{"rendered":"<p>Let&#8217;s say you want to start a <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/cpp\/welcome-back-to-cpp-modern-cpp?view=vs-2019\">modern C++<\/a> project. However, you have some requirements in mind that may be hard to achieve. First, the project should support development in the Visual Studio IDE but also provide a simple command line build experience. Since you want to be able to check your work, a low-friction unit testing environment is a must. Finally, all of this needs to be supported cross-platform &#8212; at least Windows and Linux to start with.<\/p>\n<p>Certainly you can&#8217;t have all these at once, can you? Think again! With <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/cmake-projects-in-visual-studio?view=vs-2019\">native support in Visual Studio for CMake<\/a> (&#8220;<a href=\"https:\/\/stackoverflow.com\/questions\/41664151\/what-does-the-c-in-cmake-stand-for\">Cross-platform Make<\/a>&#8220;) including <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/linux\/cmake-linux-project?view=vs-2019\">Linux targeting<\/a>, cross-platform development has never been simpler.<\/p>\n<p>To illustrate how to get started, let&#8217;s introduce a sample project structure:<\/p>\n<ul>\n<li><strong>inc<\/strong> <em>(public header file root)<\/em>\n<ul>\n<li><strong>core<\/strong> <em>(header files for `core` lib)<\/em><\/li>\n<\/ul>\n<\/li>\n<li><strong>src<\/strong> <em>(source code root)<\/em>\n<ul>\n<li><strong>app<\/strong> <em>(executable target)<\/em><\/li>\n<li><strong>core<\/strong> <em>(static lib target)<\/em><\/li>\n<\/ul>\n<\/li>\n<li><strong>test<\/strong> <em>(test code root)<\/em><\/li>\n<\/ul>\n<p>Let&#8217;s create the public header file first, <code>inc\\core\\Sample.h<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#ifndef SAMPLE_INC_SAMPLE_H\r\n#define SAMPLE_INC_SAMPLE_H\r\n\r\n#include &lt;string&gt;\r\n\r\nnamespace sample\r\n{\r\nnamespace core\r\n{\r\n\r\nclass Sample\r\n{\r\n  public:\r\n    Sample(const std::string &amp;name);\r\n\r\n    const std::string &amp;get_name() const;\r\n\r\n  private:\r\n    std::string name_;\r\n};\r\n\r\n} \/\/ namespace core\r\n} \/\/ namespace sample\r\n\r\n#endif\r\n<\/pre>\n<p>Remember that we&#8217;re cross-platform so for maximum portability we are using a <a href=\"https:\/\/github.com\/isocpp\/CppCoreGuidelines\/blob\/master\/CppCoreGuidelines.md#sf8-use-include-guards-for-all-h-files\">compiler-agnostic include guard<\/a> instead of the slightly more Windows-y <a href=\"https:\/\/en.wikipedia.org\/wiki\/Pragma_once\"><code>#pragma once<\/code><\/a>.<\/p>\n<p>Let&#8217;s continue with the implementation file, <code>src\\core\\Sample.cpp<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;Sample.h&gt;\r\n\r\nnamespace sample\r\n{\r\nnamespace core\r\n{\r\n\r\nSample::Sample(const std::string &amp;name) : name_(name)\r\n{\r\n}\r\n\r\nconst std::string &amp;Sample::get_name() const\r\n{\r\n    return name_;\r\n}\r\n\r\n} \/\/ namespace core\r\n} \/\/ namespace sample\r\n<\/pre>\n<p>Of course, we would be remiss without a corresponding unit test for this (admittedly embarrassingly basic) class. In order to fulfill our cross-platform promise, we can pick one of these: <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/test\/how-to-use-google-test-for-cpp?view=vs-2019\">Google Test<\/a> or <a href=\"https:\/\/docs.microsoft.com\/en-us\/visualstudio\/test\/how-to-use-boost-test-for-cpp?view=vs-2019\">Boost.Test<\/a>. Since <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/module\/GoogleTest.html\">Google Test is well-integrated with CMake<\/a>, we will declare it the winner for today.<\/p>\n<p>Here is <code>test\\SampleTest.cpp<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;Sample.h&gt;\r\n#include &lt;gtest\/gtest.h&gt;\r\n\r\nnamespace sample\r\n{\r\nnamespace core\r\n{\r\n\r\nTEST(SampleTest, GetName)\r\n{\r\n    Sample hello(&quot;hello&quot;);\r\n\r\n    ASSERT_EQ(&quot;hello&quot;, hello.get_name());\r\n}\r\n\r\n} \/\/ namespace core\r\n} \/\/ namespace sample\r\n<\/pre>\n<p>To complete the example, we&#8217;ll build an executable entry point in <code>src\\app\\main.cpp<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;iostream&gt;\r\n#include &lt;Sample.h&gt;\r\n\r\nusing sample::core::Sample;\r\nusing std::cout;\r\nusing std::endl;\r\n\r\nint main(int argc, const char *argv&#x5B;])\r\n{\r\n    if (argc != 2)\r\n    {\r\n        cout &lt;&lt; &quot;Please pass a string!&quot; &lt;&lt; endl;\r\n        return 1;\r\n    }\r\n\r\n    Sample world(argv&#x5B;1]);\r\n    cout &lt;&lt; &quot;Hello, &quot; &lt;&lt; world.get_name() &lt;&lt; &quot;!&quot; &lt;&lt; endl;\r\n    return 1;\r\n}\r\n<\/pre>\n<p>This is all great, but so far we have a bunch of files on disk with nothing to stitch them together. If we were doing straight Windows development, this is where we would create some <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/reference\/vcxproj-file-structure?view=vs-2019\">.vcxproj files<\/a>. But not today &#8212; we are going all-in with CMake!<\/p>\n<p>Following from our project structure above, we need three <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/manual\/cmake-buildsystem.7.html#binary-targets\">binary targets<\/a>: a static library (<code>src\\core<\/code>) and two executables (<code>src\\app<\/code> for production code, <code>test<\/code> for test code). We would like our test executable to integrate with <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/module\/CTest.html\">CMake&#8217;s test system, CTest<\/a>, along with smoke tests of the product for good measure. To achieve this, we could write the following <code>CMakeLists.txt<\/code> in our project root:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ncmake_minimum_required(VERSION 3.17)\r\nproject(sample LANGUAGES CXX)\r\n\r\ninclude(GoogleTest)\r\nfind_package(GTest)\r\n\r\nadd_library(sample-core\r\n    src\/core\/Sample.cpp\r\n)\r\n\r\ntarget_include_directories(sample-core\r\n    PUBLIC\r\n        ${CMAKE_CURRENT_SOURCE_DIR}\/inc\/core\r\n)\r\n\r\nadd_executable(sample-app\r\n    src\/app\/main.cpp\r\n)\r\n\r\ntarget_link_libraries(sample-app\r\n    sample-core\r\n)\r\n\r\nadd_executable(sample-test\r\n    test\/SampleTest.cpp\r\n)\r\n\r\ntarget_link_libraries(sample-test\r\n    sample-core\r\n    GTest::GTest\r\n    GTest::Main\r\n)\r\n\r\nenable_testing()\r\n\r\ngtest_discover_tests(sample-test)\r\n\r\nadd_test(sample-app-UsageError\r\n    sample-app\r\n)\r\n\r\nset_tests_properties(sample-app-UsageError PROPERTIES\r\n    PASS_REGULAR_EXPRESSION &quot;Please pass a string!&quot;\r\n)\r\n\r\nadd_test(sample-app-HelloWorld\r\n    sample-app world\r\n)\r\n\r\nset_tests_properties(sample-app-HelloWorld PROPERTIES\r\n    PASS_REGULAR_EXPRESSION &quot;Hello, world!&quot;\r\n)\r\n<\/pre>\n<p>As promised, we have targets <code>sample-core<\/code> for the lib, <code>sample-app<\/code> for the executable, and <code>sample-test<\/code> for the tests. In addition, we&#8217;ve defined two <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/command\/add_test.html\">command line tests with <code>add_test<\/code><\/a> which verify that the input matches an expected output expression.<\/p>\n<p>We&#8217;ve glossed over the fact that Google Test is an external dependency. How are we supposed to get it on Windows in a way that will be seamless for VS \/ CMake? This is where <a href=\"https:\/\/github.com\/microsoft\/vcpkg#quick-start-windows\">vcpkg<\/a> comes in. With one simple command <code>vcpkg install gtest --triplet x64-windows<\/code> we will be able to use Google Test in any 64-bit Windows CMake target.<\/p>\n<p>So far, so good. But we haven&#8217;t even cracked open Visual Studio yet. Since we have no solution or project files, we need to <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/cmake-projects-in-visual-studio?view=vs-2019#ide-integration\">tell VS to open our project folder instead<\/a>. We can then <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/customize-cmake-settings?view=vs-2019\">create two configurations<\/a>, <code>x64-Debug<\/code> and <code>x64-Release<\/code>, to support building debug or release binaries respectively. In this case, the provided defaults are fine so we can just save the generated <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/build\/cmakesettings-reference?view=vs-2019\">CMakeSettings.json<\/a> file and move on:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;configurations&quot;: &#x5B;\r\n    {\r\n      &quot;name&quot;: &quot;x64-Debug&quot;,\r\n      &quot;generator&quot;: &quot;Ninja&quot;,\r\n      &quot;configurationType&quot;: &quot;Debug&quot;,\r\n      &quot;inheritEnvironments&quot;: &#x5B; &quot;msvc_x64_x64&quot; ],\r\n      &quot;buildRoot&quot;: &quot;${projectDir}\\\\out\\\\build\\\\${name}&quot;,\r\n      &quot;installRoot&quot;: &quot;${projectDir}\\\\out\\\\install\\\\${name}&quot;,\r\n      &quot;cmakeCommandArgs&quot;: &quot;&quot;,\r\n      &quot;buildCommandArgs&quot;: &quot;&quot;,\r\n      &quot;ctestCommandArgs&quot;: &quot;&quot;,\r\n      &quot;variables&quot;: &#x5B;]\r\n    },\r\n    {\r\n      &quot;name&quot;: &quot;x64-Release&quot;,\r\n      &quot;generator&quot;: &quot;Ninja&quot;,\r\n      &quot;configurationType&quot;: &quot;RelWithDebInfo&quot;,\r\n      &quot;buildRoot&quot;: &quot;${projectDir}\\\\out\\\\build\\\\${name}&quot;,\r\n      &quot;installRoot&quot;: &quot;${projectDir}\\\\out\\\\install\\\\${name}&quot;,\r\n      &quot;cmakeCommandArgs&quot;: &quot;&quot;,\r\n      &quot;buildCommandArgs&quot;: &quot;&quot;,\r\n      &quot;ctestCommandArgs&quot;: &quot;&quot;,\r\n      &quot;inheritEnvironments&quot;: &#x5B; &quot;msvc_x64_x64&quot; ],\r\n      &quot;variables&quot;: &#x5B;]\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>After building either one of the configurations, the Test Explorer should populate with the Google Test suite and the two command line tests in CMake. (Note that the CMake tests still have <a href=\"https:\/\/developercommunity.visualstudio.com\/idea\/793209\/cmake-weird-test-name-prefix.html\">an unfriendly generated group name<\/a> as of this writing.) Assuming everything was done correctly up to this point, the tests will pass!<br \/>\n<a href=\"http:\/\/writeasync.net\/wp-content\/uploads\/2020\/08\/CMakeTests-VS.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/writeasync.net\/wp-content\/uploads\/2020\/08\/CMakeTests-VS-251x300.png\" alt=\"VS 2019 Test Explorer showing discovered CMake tests\" width=\"251\" height=\"300\" class=\"alignnone size-medium wp-image-5769\" srcset=\"https:\/\/writeasync.net\/wp-content\/uploads\/2020\/08\/CMakeTests-VS-251x300.png 251w, https:\/\/writeasync.net\/wp-content\/uploads\/2020\/08\/CMakeTests-VS.png 285w\" sizes=\"auto, (max-width: 251px) 100vw, 251px\" \/><\/a><\/p>\n<p>Here is the GitHub repo with all the above files: <a href=\"https:\/\/github.com\/bobbymcr\/CMakeSampleVS\/tree\/248daff4873bc9ca8c238034fa9469e3a8173361\">CMakeSampleVS<\/a><\/p>\n<p>At this point we have the beginnings of a cross-platform project, though we&#8217;ve only proven that it works in Visual Studio on Windows. Also, didn&#8217;t we say we want command line build capabilities as well? We&#8217;ll tackle these issues in later posts.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s say you want to start a modern C++ project. However, you have some requirements in mind that may be hard to achieve. First, the project should support development in the Visual Studio IDE but also provide a simple command&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[107,101,41],"tags":[],"class_list":["post-5765","post","type-post","status-publish","format-standard","hentry","category-cross-platform","category-native","category-tdd"],"_links":{"self":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5765","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5765"}],"version-history":[{"count":7,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5765\/revisions"}],"predecessor-version":[{"id":5775,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5765\/revisions\/5775"}],"wp:attachment":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5765"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5765"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5765"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}