
# HelloWorld CPP

本章将以一个简单的 Demo 来介绍如何建立一个最基本的 AimRT CPP 工程。

本 Demo 将演示以下几项基本功能：
- 基于 CMake FetchContent 通过源码引用 AimRT；
- 编写一个基础的基于 AimRT CPP 接口的`Module`；
- 使用基础的日志功能；
- 使用基础的配置功能；
- 以 App 模式集成`Module`；
- 编译项目，并运行进程以执行`Module`中的逻辑。


请注意：本文档演示的 APP 模式，仅能在源码引用 AimRT 这种方式下构建，无法在二进制安装、find_package 引用方式下构建。


## STEP1: 确保本地环境符合要求

请先确保本地的编译环境、网络环境符合要求，具体请参考[引用与安装（CPP）](installation_cpp.md)中的要求。


注意，示例本身是跨平台的，但本文档基于 linux 进行演示。


## STEP2: 创建目录结构，添加基本文件

参照以下目录结构创建文件：
```
├── CMakeLists.txt
├── cmake
│   └── GetAimRT.cmake
└── src
    ├── CMakeLists.txt
    ├── install
    │   └── cfg
    │       └── helloworld_cfg.yaml
    ├── module
    │   └── helloworld_module
    │       ├── CMakeLists.txt
    │       ├── helloworld_module.cc
    │       └── helloworld_module.h
    └── app
        └── helloworld_app
            ├── CMakeLists.txt
            └── main.cc
```

请注意，此处仅是一个供参考的路径结构，并非强制要求。但推荐您在搭建自己的工程时，为以下几个领域单独建立文件夹：
- install：存放部署时的一些配置、启动脚本等；
- module：存放业务逻辑代码；
- app：app 模式下，main 函数存放处，在 main 函数中注册业务 module；
- pkg：pkg 模式下，pkg 动态库入口方法存放处，在 pkg 中注册业务 module；


### File 1 : /CMakeLists.txt
根 CMake ，用于构建工程。
```cmake
cmake_minimum_required(VERSION 3.24)

project(helloworld LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

include(cmake/GetAimRT.cmake)

add_subdirectory(src)
```

### File 2 : /cmake/GetAimRT.cmake
此文件用于获取 AimRT，注意需要将`GIT_TAG`版本改为你想引用的版本：

```cmake
include(FetchContent)

FetchContent_Declare(
  aimrt
  GIT_REPOSITORY https://github.com/AimRT/aimrt.git
  GIT_TAG v1.x.x)

FetchContent_GetProperties(aimrt)

if(NOT aimrt_POPULATED)
  FetchContent_MakeAvailable(aimrt)
endif()
```

### File 3 : /src/CMakeLists.txt
引用 src 下的各个子目录。
```cmake
add_subdirectory(module/helloworld_module)
add_subdirectory(app/helloworld_app)
```

### File 4 : /src/module/helloworld_module/CMakeLists.txt
创建`helloworld_module`静态库。
```cmake
file(GLOB_RECURSE src ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)

add_library(helloworld_module STATIC)
add_library(helloworld::helloworld_module ALIAS helloworld_module)

target_sources(helloworld_module PRIVATE ${src})

target_include_directories(
  helloworld_module
  PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)

# 引用aimrt_module_cpp_interface
target_link_libraries(
  helloworld_module
  PRIVATE yaml-cpp::yaml-cpp
  PUBLIC aimrt::interface::aimrt_module_cpp_interface)
```

### File 5 : /src/app/helloworld_app/CMakeLists.txt
创建`helloworld_app`可执行文件。
```cmake
file(GLOB_RECURSE src ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)

add_executable(helloworld_app)

target_sources(helloworld_app PRIVATE ${src})

target_include_directories(
  helloworld_app
  PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

target_link_libraries(
  helloworld_app
  PRIVATE aimrt::runtime::core
          helloworld::helloworld_module)
```

## STEP3: 编写业务代码

业务逻辑主要通过 Module 来承载，参考以下代码实现一个简单的 Module，解析传入的配置文件并打印一些简单的日志。

### File 6 : /src/module/helloworld_module/helloworld_module.h
```cpp
#pragma once

#include "aimrt_module_cpp_interface/module_base.h"

class HelloWorldModule : public aimrt::ModuleBase {
public:
  HelloWorldModule() = default;
  ~HelloWorldModule() override = default;

  aimrt::ModuleInfo Info() const override {
    return aimrt::ModuleInfo{.name = "HelloWorldModule"};
  }

  bool Initialize(aimrt::CoreRef core) override;
  bool Start() override;
  void Shutdown() override;

private:
  aimrt::CoreRef core_;
};
```

### File 7 : /src/module/helloworld_module/helloworld_module.cc
```cpp
#include "helloworld_module/helloworld_module.h"

#include "yaml-cpp/yaml.h"

bool HelloWorldModule::Initialize(aimrt::CoreRef core) {
  // Save aimrt framework handle
  core_ = core;

  // Log
  AIMRT_HL_INFO(core_.GetLogger(), "Init.");

  try {
    // Read cfg
  auto file_path = core_.GetConfigurator().GetConfigFilePath();
  if (!file_path.empty()) {
    YAML::Node cfg_node = YAML::LoadFile(file_path.data());
    for (const auto& itr : cfg_node) {
      std::string k = itr.first.as<std::string>();
      std::string v = itr.second.as<std::string>();
      AIMRT_HL_INFO(core_.GetLogger(), "cfg [{} : {}]", k, v);
    }
  }

  } catch (const std::exception& e) {
    AIMRT_HL_ERROR(core_.GetLogger(), "Init failed, {}", e.what());
    return false;
  }

  AIMRT_HL_INFO(core_.GetLogger(), "Init succeeded.");

  return true;
}

bool HelloWorldModule::Start() {
  AIMRT_HL_INFO(core_.GetLogger(), "Start succeeded.");
  return true;
}

void HelloWorldModule::Shutdown() {
  AIMRT_HL_INFO(core_.GetLogger(), "Shutdown succeeded.");
}
```

## STEP4: 确定部署方案和配置

我们使用 App 模式，手动编写 Main 函数，将 HelloWorldModule 通过硬编码的方式注册到 AimRT 框架中。然后编写一份配置，以确定一些运行时细节。

### File 8 : /src/app/helloworld_app/main.cc
在以下示例 main 函数中，我们捕获了 kill 信号，以完成优雅退出。
```cpp
#include <csignal>
#include <iostream>

#include "core/aimrt_core.h"
#include "helloworld_module/helloworld_module.h"

using namespace aimrt::runtime::core;

AimRTCore *global_core_ptr_ = nullptr;

void SignalHandler(int sig) {
  if (global_core_ptr_ && (sig == SIGINT || sig == SIGTERM)) {
    global_core_ptr_->Shutdown();
    return;
  }
  raise(sig);
};

int32_t main(int32_t argc, char **argv) {
  signal(SIGINT, SignalHandler);
  signal(SIGTERM, SignalHandler);

  std::cout << "AimRT start." << std::endl;

  try {
    AimRTCore core;
    global_core_ptr_ = &core;

    // register module
    HelloWorldModule helloworld_module;
    core.GetModuleManager().RegisterModule(helloworld_module.NativeHandle());

    AimRTCore::Options options;
    options.cfg_file_path = argv[1];
    core.Initialize(options);

    core.Start();

    core.Shutdown();

    global_core_ptr_ = nullptr;
  } catch (const std::exception &e) {
    std::cout << "AimRT run with exception and exit. " << e.what() << std::endl;
    return -1;
  }

  std::cout << "AimRT exit." << std::endl;
  return 0;
}
```

### File 9 : /src/install/cfg/helloworld_cfg.yaml
以下是一个简单的示例配置文件。这个配置文件中的其他内容将在后续章节中介绍，这里关注两个地方：
- `aimrt.log`节点：此处指定了日志的一些细节。
- `HelloWorldModule`节点：此处为`HelloWorldModule`的配置，可以在模块中读取到。

```yaml
aimrt:
  log: # log配置
    core_lvl: INFO # 内核日志等级，可选项：Trace/Debug/Info/Warn/Error/Fatal/Off，不区分大小写
    backends: # 日志后端
      - type: console # 控制台日志

# 模块自定义配置，框架会为每个模块生成临时配置文件，开发者通过Configurator接口获取该配置文件路径
HelloWorldModule:
  key1: val1
  key2: val2
```

## STEP5: 启动并测试

完善代码之后，在 linux 上执行以下命令完成编译：
```shell
# cd to root path of project
cmake -B build
cd build
make -j
```

编译完成后，将生成的可执行文件`helloworld_app`和配置文件`helloworld_cfg.yaml`拷贝到一个目录下，然后执行以下命令运行进程，观察打印出来的日志：
```shell
./helloworld_app ./helloworld_cfg.yaml
```
