cmake可见性和AppArmor示例

This commit is contained in:
NoDistanceY 2026-09-03 10:39:21 +08:00
commit 4b4b933e3d
19 changed files with 661 additions and 0 deletions

View file

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.15)
project(visibility_demo CXX)
set(CMAKE_CXX_STANDARD 17)
# ============ color ============
add_library(color
color/src/color.cpp
color/src/rgb_convert.cpp
)
target_include_directories(color
PUBLIC color/public # 对外头目录:用户由此 #include <color/color.h>
PRIVATE color/include # 内部头目录rgb_convert.h 只有 color 自己可见
)
# 宏出现在公开头文件的 #ifdef 里,决定 API 形状。
# 若只标 PRIVATEcolor.cpp 编译出了 brightColorCode
# 但用户看到的头文件里该声明被预处理掉 → 库和用户对 API 的理解不一致。
target_compile_definitions(color PUBLIC COLOR_ENABLE_BRIGHT)
# ============ timeutil ============
add_library(timeutil timeutil/timeutil.cpp)
target_include_directories(timeutil PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/timeutil)
# ============ printer ============
add_library(printer printer/src/printer.cpp)
target_include_directories(printer PUBLIC printer/public)
target_link_libraries(printer
PUBLIC color # printer.h 引用了 color/color.h → 契约的一部分
PRIVATE timeutil # 只有 printer.cpp 用 → 实现细节
)
# 时间格式只影响 printer.cpp 的编译 → PRIVATE不泄漏、改格式不触发下游重编
target_compile_definitions(printer PRIVATE "PRINTER_TIME_FORMAT=\"%H:%M:%S\"")
# ============ app ============
add_executable(app app/main.cpp)
target_link_libraries(app PRIVATE printer)

View file

@ -0,0 +1,64 @@
# CMake target 可见性 Demovisibility
> 来自博客 `docs/编程/20-接口可见性.md` §5.2「完整 Demo」。
> 演示 `target_include_directories` / `target_link_libraries` /
> `target_compile_definitions` 上 PUBLIC / PRIVATE / INTERFACE 的传播语义。
## 运行
```bash
cd cmake/visibility
cmake -B build && cmake --build build
./build/app
```
预期输出(终端里是彩色的):
```
10:37:06 hello (绿色)
bright red (高亮红 —— PUBLIC 宏 COLOR_ENABLE_BRIGHT 传播到 app 的证据)
(没有 "leaked!" —— printer 的 PRIVATE 宏 PRINTER_TIME_FORMAT 被隔离的证据)
```
## 目录结构
```
visibility/
├── CMakeLists.txt
├── app/
│ └── main.cpp # 最终用户,只认识 printer
├── color/
│ ├── public/color/color.h # 对外 API 头(含 PUBLIC 宏开关 COLOR_ENABLE_BRIGHT
│ ├── include/rgb_convert.h # 内部中间件头,仅库内 .cpp 共享PRIVATE 目录)
│ └── src/{color,rgb_convert}.cpp
├── timeutil/
│ └── {timeutil.h,timeutil.cpp} # printer 的私有依赖
└── printer/
├── public/printer/printer.h # 对外 API 头(引用了 color → color 是 PUBLIC 依赖)
└── src/printer.cpp
```
依赖与可见性一图流:
```
app ──PRIVATE──> printer ──PUBLIC──> color ✓ app 能 include color.h、感知 COLOR_ENABLE_BRIGHT
│ └─(内部) rgb_convert.h ✗ 外部看不见include/ 是 PRIVATE 目录)
└────PRIVATE──> timeutil ✗ app 看不见 timeutil.h
└────PRIVATE 宏 PRINTER_TIME_FORMAT ✗ 不泄漏给 app
```
## 验证实验§5.3
| # | 操作 | 实测结果 |
|---|------|---------|
| 1 | 直接编译运行 | ✅ 输出 bright red不输出 leaked! |
| 2 | main.cpp 加 `#include "rgb_convert.h"` | ✅ `fatal error: rgb_convert.h: No such file or directory` |
| 3 | main.cpp 加 `#include "timeutil.h"` | ✅ `fatal error: timeutil.h: No such file or directory` |
| 4 | printer 的 `PUBLIC color``PRIVATE color` | 未跑printer 自身编译过app 找不到 color/color.h 报错 |
## 判断口诀
打开自己的公开头文件看一眼:
- 头文件里 `#include` 了谁 → 那个库 **PUBLIC**
- 头文件里 `#ifdef` 了哪个宏(宏影响 API 形状)→ 那个宏 **PUBLIC**
- 只在 .cpp 里出现的库/宏/路径 → **PRIVATE**

View file

@ -0,0 +1,19 @@
#include <printer/printer.h>
#include <iostream>
int main() {
Printer p;
p.print("hello", Color::Green);
// COLOR_ENABLE_BRIGHT 是 color 的 PUBLIC 宏,
// 顺着 app → printer → color 传播到这里,本分支会被编译进去:
#ifdef COLOR_ENABLE_BRIGHT
std::cout << brightColorCode(Color::Red) << "bright red" << "\033[0m\n";
#endif
// PRINTER_TIME_FORMAT 是 printer 的 PRIVATE 宏,这里看不见:
#ifdef PRINTER_TIME_FORMAT
std::cout << "leaked!\n"; // 永远不会被编译进去
#endif
return 0;
}

View file

@ -0,0 +1,4 @@
#pragma once
// 内部工具:把 RGB 分量拼成 ANSI 真彩色转义码。
// 供 color 库内部多个 .cpp 共享,不属于对外 API。
const char* rgbToAnsi(int r, int g, int b);

View file

@ -0,0 +1,11 @@
#pragma once
enum class Color { Red, Green, Blue };
const char* colorCode(Color c);
// 这个宏出现在公开头文件里,直接决定 API 的形状(多不多一个函数)。
// 因此该宏必须对"用 color 的人"同样可见 → CMake 里标 PUBLIC。
#ifdef COLOR_ENABLE_BRIGHT
const char* brightColorCode(Color c); // 高亮色版本
#endif

View file

@ -0,0 +1,22 @@
#include "color/color.h"
#include "rgb_convert.h" // 内部头:能找到它,靠的是 PRIVATE 的 include/ 搜索路径
const char* colorCode(Color c) {
switch (c) {
case Color::Red: return rgbToAnsi(205, 49, 49);
case Color::Green: return rgbToAnsi(13, 188, 121);
case Color::Blue: return rgbToAnsi(36, 114, 200);
}
return "";
}
#ifdef COLOR_ENABLE_BRIGHT
const char* brightColorCode(Color c) {
switch (c) {
case Color::Red: return rgbToAnsi(241, 76, 76);
case Color::Green: return rgbToAnsi(35, 209, 139);
case Color::Blue: return rgbToAnsi(59, 142, 234);
}
return "";
}
#endif

View file

@ -0,0 +1,8 @@
#include "rgb_convert.h"
#include <cstdio>
const char* rgbToAnsi(int r, int g, int b) {
static char buf[32]; // demo 从简,非线程安全
std::snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", r, g, b);
return buf;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <string>
#include <color/color.h> // 公开头引用了 color → color 必须是 PUBLIC 依赖
// 注意:这里没有 timeutil.h也没有 PRINTER_TIME_FORMAT
class Printer {
public:
void print(const std::string& msg, Color c);
};

View file

@ -0,0 +1,12 @@
#include "printer/printer.h"
#include "timeutil.h" // 只在实现里用 → timeutil 是 PRIVATE 依赖
#include <iostream>
#ifndef PRINTER_TIME_FORMAT // 只在本 .cpp 里用的宏 → PRIVATE
#define PRINTER_TIME_FORMAT "%H:%M:%S" // 没从 CMake 传进来时的兜底
#endif
void Printer::print(const std::string& msg, Color c) {
std::cout << timestamp(PRINTER_TIME_FORMAT) << " "
<< colorCode(c) << msg << "\033[0m" << std::endl;
}

View file

@ -0,0 +1,9 @@
#include "timeutil.h"
#include <ctime>
std::string timestamp(const char* fmt) {
char buf[32];
std::time_t t = std::time(nullptr);
std::strftime(buf, sizeof(buf), fmt, std::localtime(&t));
return buf;
}

View file

@ -0,0 +1,3 @@
#pragma once
#include <string>
std::string timestamp(const char* fmt); // fmt 形如 "%H:%M:%S"