-
Notifications
You must be signed in to change notification settings - Fork 9
Development
The project is structured to allow primary development on multiple platforms/OSes. Hence, CMake is used as the build system generator. The source tree is organized into OS-dependent and OS-independent sub-trees. See the project structure and composition section below for complete details.
Project dependencies are managed by vcpkg using manifest mode and listed in the project vcpkg manifest file vcpkg.json. All dependencies must be resolved using vcpkg such that the project can be (easily) used in the Windows build system.
To support maximal testability and code re-use across all supported platforms, each major software component is divided into two (2) main parts, the core, os independent/common part, and the detail, os dependent/specific part. The core part includes the interface, logic, and flow-control and must not be aware of any operating system or platform specific dependencies. The detail part includes the implementation details which are necessarily aware of and make use of operating system or platform specific dependencies.
From a code composition point of view, OS-independent parts are collected into static libraries. OS-dependent parts are collected into final link-targets such as binaries (.exe
) and shared object libraries (.so
, .dll
) and link to the OS-independent static libraries. In this way, code duplication is kept to a bare minimum. To achive this, all OS-independent code should be placed in the src/common
project tree, and all OS-dependent code should be placed in a separate project tree under src
with the corresponding OS name, for example, src/linux
and src/windows
.
The below diagram shows an example of the netremote-server
component composition:
A CMake target naming convention for major components is used where the OS-independent static library is named with the component name, and the corresponding OS-dependent targets are named with the component name suffixed with the OS name, separated by a dash. Eg.
CMake OS-indepdendent static library target name = <component-name>
CMake OS-dependent binary/shared library target name = <component-name>-<os-name>
For example, for the component that produces the gRPC server executable, the following CMake targets are defined:
Target Name | Target Type | Source Location |
---|---|---|
netremote-server | static library | src/common/server |
netremote-server-linux | executable | src/common/linux |
netremote-server-windows | executable | src/windows/server |
CMake's OUTPUT_NAME
function is used to remove the OS-suffix from the final produced artifact name. So in the example above, the executable binary produced by the netremote-server-linux
CMake target is netremote-server
instead of netremote-server-linux
.
Directory | Purpose | Contents |
---|---|---|
.devcontainer | Build Infrastructure | Development container collateral |
.docker | Build Infrastructure | Docker-based development resources |
.github | Build Infrastructure | GitHub resources (actions, templates, workflows) |
cmake | Build Infrastructure | Custom CMake build scripts |
doc | Documentation | Documentation and resources |
packaging | Packaging | Packaging scripts and tooling (eg. .deb creation) |
protocol | API, Protocol | API definitions and client bindings |
src | Source Code | Source sub-directories |
tests | Source Code | Source code for tests |
vcpkg | Source Code | git sub-module for vcpkg dependency management |
Directory | Contents |
---|---|
common | Source that is common to all platforms |
linux | Source that is specific to Linux |
windows | Source that is specific to Windows |
The components in this tree typically define the majority of the core logic needed to implement the component. Thus they are commonly built as static library targets via CMake add_library(<name> STATIC)
and then linked into their corresponding component in the OS-dependedent trees.
For example, the server
component here defines a static library target netremote-server
which is then listed as a link library via CMake target_link_libraries
of its corresponding Linux component which defines the executable target via CMake add_executable()
under src/linux/server
.
Directory | Contents |
---|---|
client | Client/consumer related code. |
dotnet | Microsoft .NET projects, mostly for non-C++ sanity checks |
server | Server code that exposes the gRPC API services externally |
service | Service code that implementes the gRPC API |
shared | Shared utility code for use throughout the project |
tools | Project tools like command-line and dev helper programs |
wifi | Shared Wi-Fi related utility and helper code |
These source trees closely mirror that of the common
tree and typically define the final output artifact for a particular component, and link corresponding common, OS-independent components.
Directory | Contents |
---|---|
deb | Scripts to create Linux debian-style package .deb . |
vcpkg | Scripts to create netremote vcpkg port |
Directory | Contents |
---|---|
protos | Protocol buffer definitions for the API (.proto ) |
include | Headers defining common protocol properties such as ip, port, etc. |
Where possible, C++ Standard Library primitives should be used for interoperability between common and OS-dependent code. The use of OS-specific primitives and libraries is reserved for scenarios where they are strictly needed (eg. calling an OS/System API), or where the highest possible performance is required and only the OS implementation can provide this.
The coding style is dictated by both .clang-format
and .clang-tidy
files at the root of the project. Please configure your editor to format and lint sources accordingly. Above all, the coding style should be kept as consistent as possible. The exact style used is not overly important.
To help keep the code consistent, please follow these general guidelines:
- DO use spaces instead of tabs.
- DON'T prefix variable names to describe their type, scope, or visibility.
-
DO use
std::filesystem
for storage and UNIX path separators (/
) where possible. -
DO use
std::format
for string formatting. - DO use complete words for variable and function names.
- DO use the following table for variable naming:
Block | Style | Example |
---|---|---|
Types | PascalCase | struct AccessPoint {}; |
Functions | PascalCase | AccessPoint GetAccessPointById(std::string_view id) |
Variables | camelCase | AccessPoint accessPoint{}; |
Parameters | camelCase | void registerEventCallback(AccessPointEventCallback& eventCallback) |
Namespaces | Uppercase | namespace Microsoft::Net::Remote |
Public Members | PascalCase | struct AccessPoint { std::string Id; } |
Private Members | camelCase with m_ prefix |
class AccessPoint { uint64_t m_sessionId; } |
Directory Names | lowercase | include/microsoft/net/wifi |
File Names | PascalCase | Ieee80211.hxx |
#include Paths |
lowercase | #include <microsoft/net/wifi/Ieee80211.hxx> |
Pre-requisities:
- C++ 23 Compiler
- CMake version >= 3.25
Both a compiler and standard C++ library supporting C++23 are required. The C++ reference pages for C++23 core language features and C++23 library features provide complete details about current compiler and library support.
CMake may be installed in any form, as long as the version meets the minimum. One popular way of installing it on Windows is to use Chocolately with choco install -y cmake
. On Linux, all standard package managers provide a cmake package (eg. apt-get install -y cmake
, yum install -y cmake
, etc.).
To bootstrap the build environment, instruct CMake to generate the build files. It is strongly recommended to do this in a directory that is separate from the source; this allows one to easily destroy and recreate the build environment without affecting the checked-out source and changes in progress. Typically, a new directory called build
at the top-level project tree is used for this purpose:
git clone [email protected]:microsoft/netremote.git
cd netremote
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -Bbuild
cmake --build build
Alternatively, Microsoft provides a CMake Tools Visual Studio Code extension that automates this process. After installing the extension and cloning the repository in VSCode, hit Ctrl + Shift + P or F1, then find the command CMake: Delete Cache and Reconfigure. This will generate the build configuration in a build
folder at the top-level of the source tree. Once done, you can build the ALL
target (default) with the CMake: Build command again (Ctrl + Shift + P or F1, type cmake, find the command).
In general, you set a build target and variant, then use the CMake: Build command to build incrementally. All build targets can be found in the CMake: Project Outline activity bar, but a list of them will also be shown when invoking actions that involve targets.
You may need to enable unsupported presets versions. To do this, press File > Preferences then search for 'presets' and enable the option CMake: Allow Unsupported Presets Versions.
As described above, a C++ 23 compiler and CMake are required. So, any distribution satisfying these requirements may be used. A known working environment for Linux is Ubuntu 24.04 (noble numbat) with a few development packages. Instructions for setting up this environment are provided below.
-
Install Ubuntu 24.04 (noble)
If development on Windows is desired, Ubuntu may be installed in WSL using a rootfs image. To install WSL, on newer versions of Windows 11, use the following command:
wsl --install --no-distribution
. For complete instructions, refer to https://learn.microsoft.com/en-us/windows/wsl/install. Then follow these steps to install Ubuntu 24.04 noble for WSL:-
Choose a location to store the WSL Ubuntu root filesystem (rootfs) image and switch to this directory. For optimal performance, use a non-system drive and create a top-level folder for all WSL content such as
d:\wsl
:md d:\wsl cd d:\wsl
-
Download the Ubuntu 24.04 wsl rootfs archive. The development environment has been tested and confirmed to work with the 20240627 image. More recent images may work as well, however, no guarantees are made for such images.
- Stable: https://cloud-images.ubuntu.com/wsl/noble/20240627/ubuntu-noble-wsl-amd64-24.04lts.rootfs.tar.gz
- Current: https://cloud-images.ubuntu.com/wsl/noble/current/ubuntu-noble-wsl-amd64-24.04lts.rootfs.tar.gz
Invoke-WebRequest -Uri https://cloud-images.ubuntu.com/wsl/noble/20240627/ubuntu-noble-wsl-amd64-24.04lts.rootfs.tar.gz -OutFile .\
-
Import the downloaded rootfs image to create a new WSL distribution for Ubuntu 24.04 noble:
md d:\wsl\noble wsl --import noble d:\wsl\noble .\ubuntu-noble-wsl-amd64-24.04lts.rootfs.tar.gz wsl -d noble
-
You are now running in a root shell on a fresh Ubuntu 24.04 installation. The rootfs image has no suitable non-root user accounts, so create one, adding them to the
sudo
androot
groups to enable privileged execution:adduser dev --comment dev usermod dev -aG root,sudo
-
Create a
/etc/wsl.conf
file.-
Add a
[boot]
section with asystemd=true
setting to enable systemd. -
Add a
[user]
section with adefault=username
setting to specify the default logon user to be the one you just created (eg.dev
).
[boot] systemd=true [user] default=dev
For example, you can create the file with those contents using
echo
in execution mode:echo -e '[boot]\nsystemd=true\n\n[user]\ndefault=dev' > /etc/wsl.conf
-
-
Exit Ubuntu 24.04 noble:
exit
-
Shutdown WSL so it can pick up the new default user changes, and re-run Ubuntu 24.04 noble:
wsl --shutdown wsl -d noble
You should now be logged on as the
dev
user. -
-
Install Linux Development Dependencies
Execute the following commands in a shell to install all the tools required to compile, lint, and debug the project:
-
Update the package cache and upgrade to latest packages:
sudo apt-get update -y && sudo apt-get upgrade -y
-
Install core build tools and dependencies:
sudo apt-get install -y autoconf automake autopoint build-essential ca-certificates cmake curl git gnupg libltdl-dev libmount-dev libtool linux-libc-dev libstdc++-14-dev ninja-build pkg-config python3-jinja2 tar unzip zip
-
Install LLVM 18 toolchain dependencies:
sudo apt-get install -y libllvm-18-ocaml-dev libllvm18 llvm-18 llvm-18-dev llvm-18-doc llvm-18-examples llvm-18-runtime clang-18 clang-tools-18 clang-18-doc libclang-common-18-dev libclang-18-dev libclang1-18 clang-format-18 python3-clang-18 clangd-18 clang-tidy-18 libclang-rt-18-dev libpolly-18-dev libfuzzer-18-dev lldb-18 libc++-18-dev libc++abi-18-dev libomp-18-dev libclc-18-dev libunwind-18-dev libmlir-18-dev mlir-18-tools libbolt-18-dev bolt-18 flang-18 libclang-rt-18-dev-wasm32 libclang-rt-18-dev-wasm64 libc++-18-dev-wasm32 libc++abi-18-dev-wasm32 libclang-rt-18-dev-wasm32 libclang-rt-18-dev-wasm64 libllvmlibc-18-dev
-
Install other development dependencies and helpful tools:
sudo apt install -y --no-install-recommends bc bison dwarves flex libelf-dev dos2unix file gnupg2 iproute2 mtools neofetch rsync ssh sudo gdb kmod nano policycoreutils-python-utils python-is-python3 vim debconf-utils iw
-
Install hostapd and wpa_supplicant development dependencies:
sudo apt-get install -y libnl-3-200-dbg libnl-3-dev libssl-dev libnl-genl-3-dev libdbus-c++-dev libnl-route-3-dev
[!NOTE] The source of truth for installing development dependencies is found in the GitHub build-with-host host action `Install Linux build dependencies' step. The instructions there always supercede the instructions here as that action is used to build official release packages.
-
-
Install Docker on Windows (optional)
Download and install docker from https://docs.docker.com/desktop/install/windows-install (amd64). During the installation, ensure the
Use WSL 2 instead of Hyper-V (recommended)
box is checked. -
Configure commit-signing (optional)
-
Install GNU Privacy Guard (GnuPG):
sudo apt install -y --no-install-recommends gnupg2
-
Follow the common instructions here.
-
Visual Studio 2022 generally satisfies the requirements, however, the full integrated development environment (IDE) is not needed. A much leaner alternative for those using other editors such as Visual Studio Code can instead install Visual Studio Build Tools.
As described above, a C++ 23 compiler and CMake are required. A known, minimal working environment for Windows is using Visual Studio 2022 Build Tools which provides all of the necessary development requirements. Note that the build tools do not provide the Visual Studio IDE, so if this is desired, the Visual Studio product of choice may be installed instead. Instructions for setting up this environment are provided below:
-
Install the build tools
Download the latest version of Visual Studio 2022 Build Tools or the full Visual Studio IDE and run the installer. Select the following 'Workloads':
- ASP.NET and web development
- Desktop development with C++
- .NET desktop development
- Windows Universal Platform development
Select the 'Individual Components' tab at the top, and select the following required components:
- C++ CMake tools for Windows
- MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)
- MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)
- Windows 11 SDK (10.0.22621.0)
- .NET 7.0 Runtime
Additionally if you are using the Visual Studio 2022 Build Tools install the followng under 'Individual Components'
- C++ Build Tools core features
- C++ core features
The following components are optional, but strongly recommended for a more complete development experience:
- C++ AddressSanitizer
- C++ Clang Compiler for Windows (15.0.1)
- C++ Clang-cl for v143 build tools (x64/x86)
Once installed, the project can be built using CMake as described here.
-
Configure git commit-signing (optional)
Prior to following the generic instructions for configuring a git signing key here, the gpg tools must be installed:
-
Download the latest version of gpg4win from https://www.gpg4win.org/.
-
Run the installer, deselect all optional components, leaving only
GnuPG
selected. Accept all other defaults and complete the installation. -
Configure git with the location of the Windows gpg tools:
git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"
-
Follow the common instructions here.
-
While not required, it is strongly recommended to configure git with a GNU Privacy Guard (GPG) signing key. This allows GitHub to verify commits were pushed by a specific user and will show a green Verified
status beside each verified commit. Follow these steps to configure a signing key for commit verification:
- Install the gpg tools for the target operating system (Linux, Windows).
- Generate a gpg key
- Add the gpg key to your github account
-
Configure git to use the gpg key. This link will also tell you how to use gpg signing with an ssh key. Additionally, if you wish tell git to use a type any type of signing by default, be it gpg, ssh or X.509, you need to run the following command
git config --global commit.gpgsign true
A Docker image is available that provides a known working Linux development environment with all of the tools necessary pre-installed:
- abeltrano/netremote-dev: A container image for development of the project, including hostapd and building modules for the WSL2 kernel.
To use the Docker container image, install Docker on your development machine, then start an interactive instance, which will bring you to a bash shell:
docker run -it abeltrano/netremote-dev
This provides the full development environment but does not sync source code. So instead of using the docker image directly, it is recommended to use a development container described in the Dev Container section below.
The docker images are also configured for use with dev containers. Many IDEs have built-in support for development containers, including but not limited to VSCode. This method is the easiest and fastest way to get a working development environment, especially if you're already using VSCode.
Use of dev containers in VSCode is the recommended and officially supported development environment. To use this, install Docker on your development machine and install the Dev Containers VSCode extension. The Dev Containers extension has two (2) primary ways to use it:
-
[Recommended] Cloning directly into a named volume in the container.
Clone the repository with the dev containers command to use a named volume: press Ctrl + Shift + P or F1 and select the Dev Containers: Clone Repository in Named Container Volume dev container extension command. A named volume is a persistent filesystem that is mounted in the container and used to store the repository source code. This allows the source code to survive across container stops, removals, and deletions. As part of the command, you will be prompted to select the named volume to use. On first run, no volumes will have been created so select Create a new volume and give it any name you want (eg
source
). In subsequent command invocations to clone the repository, the source volume will exist and you can select it from the list. -
Cloning locally and sharing source workspace with the container.
Clone the repository as normal. With this method, VSCode will detect a dev container specification and prompt you to open it in a container:
Alternatively, you can open the project in the container at any time by invoking the Dev Containers: Open Folder in Container dev container extension. Press Ctrl + Shift + P or F1 and select Reopen in Container.
A prompt will ask to select a
devcontainer.json
file:Select NetRemoteDev-Stateless, which is configured to mount the workspace directory in a bind mount instead of a volume. The NetRemoteDev configuration will not work.
Warning
A major drawback of this method is that the source tree is mounted in the container using a bind mount, which has extremely poor performance to the point where git operations are essentially unusable. Hence, this method typically requires two (2) VSCode windows: one for the repository on the host where git operations are carried out, and one for the container where the source is modified and built. Therefore, this method is strongly discouraged.