# PHAS0100 - Week 1 (13 January 2023) :::info ## :tada: Welcome to the first day! ### Today Today we will - introduce the course - make sure we all can use the tools we need, - understand how to manage C++ projects effectively - practice using the git version control system, and - learn to build C++ projects using CMake ::: Note: Mandatory pre-reading to be completed before in-session class ### Course and tools #### Tools - [Introduction to HackMD](/KHGuLhyARXOAn6MmgjhoUA) - We will also use [GitHub](https://github.com) - create an account now if you've not done it already. - Get [your computer ready](https://moodle.ucl.ac.uk/mod/page/view.php?id=2714313) - We recommend to use [VSCode with the Remote-containers extension](https://code.visualstudio.com/docs/remote/containers-tutorial) - Or can load the same devcontainer environment on GitHub codespaces. - Or can install the required tools yourself (g++ >9.4.0, cmake > 3.21, git 2.x etc). ### Timetable <details> | Start | End | Topic | | -------- | -------- | -------- | | 14:00 | 14:15 | Introduction to PHAS0100 & Quiz| | 14:15 | 14:55 | Coding session 1: development environment setup and Git | | 14:55 | 15:05 | Break | --- | | 15:05 | 15:20 | Instructor-led CMake live demo for building a simple C++ project | | 15:20 | 15:55 | Coding session 2: CMake exercises 3-5 | | 15:55 | 16:05 | Break | --- | | 16:15 | 16:55 | Coding session 3: CMake exercises 6-9 | </details> ### Pre-class Exercise: Setting up SSH keys and authenticating on GitHub <details> Before the in-class session, you shall have set up an SSH keypair and activated it on GitHub to facilitate syncing of local repositories with their remote counterparts. Follow the [*Configuring secure remote connections with SSH*](https://github-pages.ucl.ac.uk/research-computing-with-cpp/01projects/sec01Git.html) section of the lecture notes to complete this pre-class exercise. </details> ## Coding session 1: Development environment setup and Git (40 minutes) ### Class Exercise 0: Development environment setup This first breakout/class coding session is a chance for you to check you are setup with your VS Code and docker development environment and ask questions if you are stuck at any point. >Please use the contents of the `.devcontainer/` folder from https://github.com/UCL/rc-cpp-vscode-dev-container to set up and test your development container in VS Code. If you are facing difficulties with setting up the Docker-based dev environment on VS Code (e.g. unable to install Docker on your laptop), the instructors and teaching assistants shall guide you on how to use [GitHub codespaces](https://github.com/features/codespaces) to perform the exercises in a web browser making use of an indentical development environment through the same devcontainer file. ### Class Exercise 1: Using git for version control Follow the notes given in [the Git section of the course notes](https://github-pages.ucl.ac.uk/research-computing-with-cpp/01projects/sec01Git.html) for detailed instructions on completing this exercise ##### 1a. Verifying successful install of `git` After a development environment is set up, try opening a terminal and run: ``` $ git --version ``` (exlude the `$` sign, which is just a mere indicator/placeholder for your current shell prompt). It should return a version string `2.xx.y` (2.39.0) if you are using the supplied devcontainer on VSCode or GitHub codespaces ##### 1b. Configure git Follow the instructions provided in the *configuration* section of the lecture notes for week1 to configure your git environment ##### 1c. Download starter files Download and extract the files from the archive available [here](https://liveuclac-my.sharepoint.com/:u:/g/personal/uccagop_ucl_ac_uk/ESoVCHLdn4ZKiQEVxE_vVTwBqP9r6aOgcusKsPii7nN50w?e=wyyt8M) onto your local file system on your computer. ##### 1d. Initialise a git tracked repository ##### 1e. Verify the tracking status of files in the project (`git status`) ##### 1f. Track `hello.cpp` in the project (`git add` followed by `git commit`). Query the tracked status after every action. ##### 1g. Track all files in the project (`git add .` followed by `git commit`). Query the tracked status after every action. ##### 1h. Edit the `README.md` file. Check the tracked status and view the differences with respect to the latest commit ##### 1i. View the project's version history (and try out its shortened form) ### Class Exercise 2: Push commits from your local repository to a remote repository on GitHub Refer to the section titled *Remote repositories and Github* in the [lecture notes](https://github-pages.ucl.ac.uk/research-computing-with-cpp/01projects/sec01Git.html) which provides detailed instructions on setting up a remote repository on GitHub and pushing all local commits. ## Breakout session 2: CMake for building C++ projects (35 minutes) ### Prep: Obtain the exercise files The exercise files are available in [this](https://github.com/UCL-PHAS0100-22-23/week1_cmake_exercises) GitHub repository. We clone them with the `--recurse-submodules` flag to bring in the VSCode devcontainer configuration. ```shell $ git clone --recurse-submodules https://github.com/UCL-PHAS0100-22-23/week1_cmake_exercises ``` Navigate into the cloned directory i.e. `week1_cmake_exercises` either from a GUI file browser or in your terminal application and open VSCode from this top-level directory. If using the command-line, assuming that the Visual Studio Code (VSCode) text editor is in the search path for your user account on your computer, you may run ```sh $ code . ``` to open up the contents of the folder in VSCode. If a security dialog pops up, it is safe to indicate that you trust the author and files within the top-level folder and all subfolders therein. Ignore the error that triggers a popup question: > CMakeLists.txt was not found in the root of the folder "local_exercise_repo_cmake". How would you like to proceed? by clicking on `Don't show again`. If the development containers extension is installed, then VSCode shall prompt you to reopen your session in a remote container. After answering `yes` in this dialog box, you may view the progress of the container being opened and configured with all necessary extensions by clicking on `show log`. This will take a while for the very first time, since the container has to be built from the Dockerfile with the settings defined in our `devcontainer.json` specification. However, subsequent attempts to reopen the folder in a container shall be much faster. After reopening the folder in a container, open a new terminal within VSCode, and navigate to each exercise's subdirectory to work on it. Exercise 0 is a minimal CMake example, which will be a live demo led by the instructor, which you may follow along. Subsequent exercises need to be attempted independently, however the teaching assistants and instructors shall be available to help. ::: info For each exercise folder, open and explore the C++ source, header and CMakeLists.txt files. Each of these projects do not currently build in their current state, and requires the user to add/edit relevant lines in various `CMakeLists.txt` files in order to configure and build the project successfully. ::: ### Class Exercise 3: `1_hierarchical_cmake_helloworld` We have modified the minimal CMake hello world demo project to use a hierarchical project structure as is common in professional projects. ``` 1_hierarchical_cmake_helloworld/ ├── CMakeLists.txt ├── LICENSE.txt ├── README.md └── src ├── CMakeLists.txt └── hello.cpp ``` Notice that there are two `CMakeLists.txt` now. In the top-level `CMakeLists.txt` at the project root, we have set certain high-level description of the project, but have not provided instructions on building the "hello_world" executable program from the source! This is intentional. The task of describing the build of each logical/modular project unit is delegated to a nested/leaf `CMakeLists.txt` that closely reflect's the project's file hierarchy. In this case, we wish to 'hand-off' control to `src/CMakeLists.txt` for building the `hello_world` executable. This is achieved by using the `add_subdirectory()` command. :::info > Write the command for CMake to process the `CMakeLists.txt` file located within in the `src/` subdirectory See: https://cmake.org/cmake/help/latest/command/add_subdirectory.html ::: Within the 'leaf' `CMakeLists.txt` file located in `src/`, we describe how the `hello_world` executable is to be built. This is considered best practice, i.e. to have a `CMakeLists.txt` as close to the relevant source files for each modular functionality within the project. This file can see and use all the properties, variables, functions and macros defined at any `CMakeLists.txt` in its parent scope (in this case, all definitions from the top-level `CMakeLists.txt` file are visible here i.e. they are in the 'scope' of this leaf file). Configure, build and run the output executable. ### Class Exercise 4: `2_hierarchical_cmake_bindir_helloworld` You must have observed that in the previous exercise, the output executable program `hello_world` was placed under the `src/` directory of the build tree. This is non-idiomatic since we expect such executables to be present under the bin tree. This is because CMake reflects the source tree hierarchy in its build folder. Our aim is to now to instruct CMake to place all the binary executables within a `bin/` subdirectory within the build directory by editing the top-level `CMakeLists.txt` file. ::: info Use the `set()` command, and the pre-defined variables CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_BINARY_DIR See: - https://cmake.org/cmake/help/latest/command/set.html - https://cmake.org/cmake/help/latest/variable/CMAKE_RUNTIME_OUTPUT_DIRECTORY.html - https://cmake.org/cmake/help/latest/variable/CMAKE_BINARY_DIR.html ::: Configure, build and run the project, and verify that the output executable program has been placed inside the `bin/` directory. ### Class Exercise 5: `3_cpp17_standards` So far, our `hello.cpp` C++ source code had just a single `cout` statement. This C++ code can be compiled by very old compilers, even those defaulting to the truly ancient C++03 standards. The C++ standard has evolved a lot since then, and has gained a lot of convienience features over the years. Several different ISO standards of the C++ language specification has been published (C++11, 14, 17, 20). Unless we specify otherwise, CMake does not pass compiler flags to require any particular standard. Thus, we are able to compile the default standard for the particular version of the compiler we use. In the supplied VS Code devcontainer, the compiler is `g++ 9.4.0` which defaults to the C++14 standard, but is capable of compiling C++17 code. However, if we want to compile more modern C++ code, then we need to indicate this to CMake (which shall pass the appropriate OS/compiler dependent flags). Open and inspect the modified source code in `hello.cpp`. We see the usage of `std::string_view` which is a fast string-like datatype first introduced in the C++17 language spec. At the top-level `CMakeLists.txt` file, :::info 1. We wish to ensure that the `hello_world` target conforms to the C++17 standard. One way to achieve this in CMake is through the `target_compile_features()` command which can be used to set some pre-defined properties/behaviour/characteristics to follow for compiling targets. See: - https://cmake.org/cmake/help/latest/command/target_compile_features.html - `cxx_std_17` in https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html 2. While `target_compile_features()` encourages the use of C++17 standards if possible, it does not enforce this. The built-in boolean variable [`CXX_STANDARD_REQUIRED`](https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD_REQUIRED.html) property can be set to `True/ON` to enforce this. Another useful thing to do to ensure wide platform and target portability of our project is to disable compiler extensions. Various C++ compilers take liberties to enable compiler-specific extensions by default, unless explicitly directed not to do so. The built-in boolean property [`CXX_EXTENSIONS`](https://cmake.org/cmake/help/latest/prop_tgt/CXX_EXTENSIONS.html) can be set to `False/Off` to disable these compiler extensions. The `set_target_properties()` command allows us to set multiple properties on targets within a single invocation like so: See: - set_target_properties() command https://cmake.org/cmake/help/latest/command/set_target_properties.html, on how to set CXX_STANDARD_REQUIRED and CXX_EXTENSIONS boolean flags for our executable ::: Configure, build and run the output executable. ## Coding session 3: CMake for building C++ projects (50 minutes) ### Class Exercise 6: `4_modified_src` First, configure, build and run the output executable for the project. Next, modify the C++ source file `hello.cpp`, e.g. add a new line of code to print your favourite London landmark. :::info Do you need to run the CMake configure stage again (i.e. `cmake -S . -B build_dir`)? ::: The CMake configure stage can be skipped over for any subsequent edits of the source code. As long as the dependency relationship has not changed (i.e. the executable target depends directly on the same source file), we may directly proceed to the build stage (i.e. `cmake --build build_dir`) to recompile and get the new output executable. Run the rebuilt executable and verify that it produces the output that corresponds to your modified source code. ### Class Exercise 7: `5_modified_src_and_cmakelists` First, configure, build and run the output executable. Next, rename the C++ source file `hello.cpp` to `greet.cpp` :::info Suitably edit the `src/CMakeLists.txt` file to indicate the updated filename in the dependency list for the executable target. Do you need to run the CMake configure stage again (i.e. `cmake -S . -B build_dir`)? ::: In this case, the CMake configure stage can be skipped. This is because during the first configuration run, CMake implicitly marks every `CMakeLists.txt` it encounters as a dependency in the generated build system (e.g. Makefiles on Unix-like systems). Hence, we may directly proceed to the build stage (i.e. `cmake --build build_dir`) to recompile and get the new output executable. Run the updated executable and verify that it produces the expected output. ### Class Exercise 8: `6_header_inclusion` For this example, we look at how to include C++ header files in our project. A few classical approaches (i.e. without CMake), that developers use are: - hard-code the relative path to the header locations - add the header locations to the include paths of the compiler whilst omitting the need to specify relative paths in source code - populate environment variables that modify the include paths Each of these have drawbacks. Hard-coding paths to include directories is a brittle approach that can lead to breakages when the filesystem organisational hierarchy of the project is to be changed. It becomes tedious to update the new paths, and is therefore, error prone. Adding a header location manually to the include paths of the compiler implies that the build system files need to be suitably updated when the filesystem hierarchy changes. While this can sometimes be manageable for compiling on a single platform, it quickly becomes infeasible for compilaing a large refactoring of a real-world project across multiple platforms. Environment variables are compiler-specific. Environment variables that are set for gcc or clang may not be applicable in Microsoft or Intel compilers! Thus, this is not a portable solution. CMake alleviates the need to manually set such compiler-specific include flags by providing high-level directives. ::: info Explore the `hello.cpp` file. It contains a conditional pre-processor directive block `#ifdef...#endif` to detect if a header file was successfuly processed, and print it to the screen suitably. Study the included header file `common_header.h`. Note the use of header guards, which is considered a best practice in C++ programming. Try configuring and builing the project with the usual CMake steps. Does it work? ::: To successfuly configure and build the project, we need to: ::: info Inform CMake that, to build our target `hello_world`, we need to tell the compiler the location of the header file (which in this case, is located in the `include` sub-directory) using the `target_include_directories()` command. see: https://cmake.org/cmake/help/latest/command/target_include_directories.html ::: Try configuring and building the project. Does it work now? Verify that the header was indeed processed by running the output executable program and looking at the output. ### Class Exercise 9: `7_basic_lib` Now, we look at modularising our code by separating distinct functionality of code blocks into reusable pieces, that can be leveraged in multiple places. Such pre-packaged functionality that can be 'linked' to our application can be broadly considered as a *library*. Explore the updated project files. Note the detailed structural hierarchy of file organisation. ``` . ├── CMakeLists.txt ├── LICENSE.txt ├── README.md └── src ├── CMakeLists.txt ├── greeting │   ├── CMakeLists.txt │   ├── greeting.cpp │   └── include │   └── greeting.h ├── hello.cpp └── include └── common_header.h ``` The project consists of a main application `say_hello` that is built from a main source code `hello.cpp`, which includes functionality from a library `greeting` (which is located in its own sub-directory in the source tree). The `greeting` library consists of a source file `greeting.cpp` and a header file `greeting.h` (which is located within the `include` folder inside `greeting`). Furthermore, the `hello.cpp` file includes both a `common_header.h` (located in `src/include`) as well as `greeting.h` (located in `src/greeting/include`). Such topological organisation and complex dependencies are a characterstic of most large-scale C++ research software. :::info 1. At the top-level `CMakeLists.txt`, - As a good housekeeping practice, just as we instructed CMake to place the output executables under the `bin` sub-directory under the build tree, we ask CMake to place the generated library files to be placed under the `lib` sub-directory under the build tree. Such libraries built by default by CMake on Unix-like systems end with the extension ".a", which in unix-parlance stand for called "archive libraries". Their location can be influenced by setting a variable called `CMAKE_ARCHIVE_OUTPUT_DIRECTORY`. Use set() to configure the pre-defined variable [CMAKE_ARCHIVE_OUTPUT_DIRECTORY](https://cmake.org/cmake/help/latest/prop_tgt/ARCHIVE_OUTPUT_DIRECTORY.html) accordingly. 2. Edit `src/CMakeLists.txt` to - process the list file from the `greeting` subdirectory. - After the `greeting` library has been built, it is required to inform CMake that our `say_hello` executable depends on it. Add the instruction to achieve this i.e. link the built `greeting` library to the main executable `say_hello`. See: https://cmake.org/cmake/help/latest/command/target_link_libraries.html 3. Edit `src/greeting/CMakeLists.txt` - The `add_library()` instruction informs CMake that we are adding a library target (not a standalone executable target) See: https://cmake.org/cmake/help/latest/command/add_library.html - Now, indicate to CMake, the C++ source code dependencies of this library target. If the source files are not used directly anywhere, but are only used in building the library, they can be made PRIVATE See: https://cmake.org/cmake/help/latest/command/target_sources.html - The header file (greeting.h) included in the library's C++ source code (greeting.cpp) is placed within the `include` sub-directory relative to the current CMakeLists.txt. Indicate to CMake this include dependency. If the header file is used both by the library target, and also directly included within our executable target (which consumes the library target), then it needs to be declared as PUBLIC. See: https://cmake.org/cmake/help/latest/command/target_include_directories.html :::