Skip to content

Commit 15d442a

Browse files
committed
docs(cmakeFetchContentVersusGitSubmodule): explain the FetchContent flow
The README states up front that LVGL is not a submodule, that CMake fetches it during configure into build/_deps, and that CI proves it. It covers the prerequisites, the exact clone/configure/build/run commands, how FetchContent_Declare and FetchContent_MakeAvailable work here, and why LVGL's build switches are cache entries. Switching the tag is documented with the reconfigure command and the real log lines of the checkout moving, as is cache behavior: -D persists, -U LVGL_GIT_TAG drops the override, and deleting build/ is a reset rather than the update path. A short comparison explains when a submodule is still the better model, and the CI section lists what each workflow step proves. The screenshot is the demo window captured from a local run. The tested versions include the green ubuntu-latest run of the new workflow.
1 parent ff0dabb commit 15d442a

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2026 Marcel Petrick
3+
4+
SPDX-License-Identifier: GPL-3.0-or-later
5+
-->
6+
7+
# CMake FetchContent instead of a Git submodule: a minimal LVGL demo
8+
9+
[![cmakeFetchContentVersusGitSubmodule](https://github.com/marcelpetrick/codingWithGPT/actions/workflows/cmakeFetchContentVersusGitSubmodule.yml/badge.svg)](https://github.com/marcelpetrick/codingWithGPT/actions/workflows/cmakeFetchContentVersusGitSubmodule.yml)
10+
11+
This is a minimal LVGL/CMake demonstration of one idea:
12+
13+
- **LVGL is not a Git submodule.** There is no `.gitmodules` file and no copy of LVGL in this repository.
14+
- **CMake fetches LVGL during configure**, with the plain `FetchContent` module that ships with CMake.
15+
- **The fetched source lives in the build tree** (`build/_deps/lvgl-src`), not in the source tree.
16+
- **GitHub Actions proves the same behavior in CI**: a clean fetch and build, then a tag switch in the same build directory.
17+
18+
The demo program opens a 480×320 window with one centered label. The window is only there to prove that LVGL was not just downloaded: it was configured, compiled, linked and used.
19+
20+
![The demo window: one centered "Hello LVGL" label](media/hello_lvgl.png)
21+
22+
> Clone one normal GitHub repository, run CMake, and CMake obtains LVGL itself. Change the requested LVGL tag and rerun CMake; CMake updates the dependency checkout. No Git submodule is needed.
23+
24+
| File | Role |
25+
| --- | --- |
26+
| [`CMakeLists.txt`](CMakeLists.txt) | The whole dependency story. `FetchContent_Declare()` is in this file, not in a helper module. |
27+
| [`lv_conf.h`](lv_conf.h) | LVGL configuration: 32-bit color and the SDL display driver, nothing else. |
28+
| [`src/main.cpp`](src/main.cpp) | The demo: `lv_init()`, one SDL window, one label, the LVGL timer loop. |
29+
| [`../.github/workflows/cmakeFetchContentVersusGitSubmodule.yml`](../.github/workflows/cmakeFetchContentVersusGitSubmodule.yml) | CI. It sits at the repository root because GitHub only runs workflows from there. |
30+
| [`fetchcontent_lvgl_project_spec.md`](fetchcontent_lvgl_project_spec.md) | The original vision and requirements for this project. |
31+
32+
## Prerequisites
33+
34+
- Git
35+
- CMake 3.28 or newer
36+
- A C and C++ compiler (C++17)
37+
- The SDL2 development package, which provides the desktop window
38+
- Ninja (recommended; any CMake generator works)
39+
- Network access for the first configure, which clones LVGL from GitHub
40+
41+
LVGL itself is not a prerequisite. CMake fetches it.
42+
43+
On Ubuntu:
44+
45+
```bash
46+
sudo apt-get update
47+
sudo apt-get install -y build-essential cmake ninja-build libsdl2-dev
48+
```
49+
50+
## Clone, configure, build, run
51+
52+
This project lives in the `codingWithGPT` collection repository. A plain clone is enough; no `--recursive`, no `git submodule update`.
53+
54+
```bash
55+
git clone https://github.com/marcelpetrick/codingWithGPT.git
56+
cd codingWithGPT/cmakeFetchContentVersusGitSubmodule
57+
cmake -S . -B build -G Ninja
58+
cmake --build build
59+
./build/hello_lvgl
60+
```
61+
62+
The first `cmake -S . -B build` is where the dependency arrives. `FetchContent` clones LVGL at the requested tag into CMake's dependency area in the build tree:
63+
64+
```text
65+
build/_deps/lvgl-src the LVGL checkout (a normal Git clone, owned by CMake)
66+
build/_deps/lvgl-build LVGL's build output
67+
build/_deps/lvgl-subbuild the helper project CMake uses to run the clone
68+
```
69+
70+
`build/` is ignored by Git, so none of this ever lands in the repository. Closing the window ends the program.
71+
72+
To watch the clone happen, configure with `-DFETCHCONTENT_QUIET=OFF`. The log then contains the Git output, including:
73+
74+
```text
75+
-- LVGL FetchContent revision: v9.5.0
76+
-- Populating lvgl
77+
Cloning into 'lvgl-src'...
78+
HEAD is now at 85aa60d18 chore: release v9.5.0 (#9753)
79+
```
80+
81+
## How the CMake part works
82+
83+
The dependency section of [`CMakeLists.txt`](CMakeLists.txt) is the core of the project:
84+
85+
```cmake
86+
include(FetchContent)
87+
88+
if(NOT DEFINED LVGL_GIT_TAG)
89+
set(LVGL_GIT_TAG "v9.5.0")
90+
endif()
91+
92+
FetchContent_Declare(
93+
lvgl
94+
GIT_REPOSITORY https://github.com/lvgl/lvgl.git
95+
GIT_TAG ${LVGL_GIT_TAG}
96+
GIT_PROGRESS TRUE
97+
)
98+
99+
message(STATUS "LVGL FetchContent revision: ${LVGL_GIT_TAG}")
100+
FetchContent_MakeAvailable(lvgl)
101+
```
102+
103+
- **`FetchContent_Declare()`** only records where LVGL comes from and which revision is wanted.
104+
- **`FetchContent_MakeAvailable()`** does the work during configure: it clones LVGL on the first run, moves the checkout to another tag when `LVGL_GIT_TAG` changes, and then adds LVGL's own `CMakeLists.txt` with `add_subdirectory()`. From then on LVGL's targets are part of this build.
105+
- **The application links `lvgl::lvgl`**, the target LVGL's CMake exports. No LVGL sources are listed by hand and no include path is hard-coded; the target carries them.
106+
- **SDL2 comes from the system** through `find_package(SDL2)`. It is linked to the `lvgl` target, because LVGL's SDL driver is compiled inside it. SDL2 is a host prerequisite, not a second fetched dependency.
107+
- **LVGL's build switches are set as cache entries** before `FetchContent_MakeAvailable()`: the path to `lv_conf.h`, and examples, demos and the bundled ThorVG turned off. LVGL declares these as cache options under an older CMake policy level, which discards a plain variable of the same name on the very first configure. With plain `set()` calls the first configure ignored the given `lv_conf.h` path and enabled all three extras.
108+
109+
The defaults are deliberate:
110+
111+
- **A release tag, not a branch.** `master` would change under your feet. A tag is readable and makes switching releases easy to show. Tags can in principle be moved upstream; a production project that needs strict reproducibility may prefer a full commit hash in `GIT_TAG`.
112+
- **No `GIT_SHALLOW`.** A full clone has every tag locally, so switching between releases is a plain checkout.
113+
- **No `FETCHCONTENT_UPDATES_DISCONNECTED` or `FETCHCONTENT_FULLY_DISCONNECTED`.** Either would stop CMake from updating the checkout, and updating it is what this project shows.
114+
115+
## Changing the LVGL tag
116+
117+
Rerun configure in the **same** build directory with another tag, then build:
118+
119+
```bash
120+
cmake -S . -B build -DLVGL_GIT_TAG=v9.4.0
121+
cmake --build build
122+
```
123+
124+
What happens:
125+
126+
1. This reruns the configure step in the existing build directory.
127+
2. CMake sees that the requested revision of the declared dependency changed.
128+
3. `FetchContent` updates the existing checkout in `build/_deps/lvgl-src` to the new tag. It is not cloned again.
129+
4. The build recompiles LVGL and relinks the demo.
130+
131+
No `git submodule update`, and no Git command of your own, is involved. With `-DFETCHCONTENT_QUIET=OFF` the switch is visible in the log:
132+
133+
```text
134+
-- LVGL FetchContent revision: v9.4.0
135+
Previous HEAD position was 85aa60d18 chore: release v9.5.0 (#9753)
136+
HEAD is now at c016f72d4 chore: release v9.4.0 (#9075)
137+
```
138+
139+
Switch back the same way:
140+
141+
```bash
142+
cmake -S . -B build -DLVGL_GIT_TAG=v9.5.0
143+
cmake --build build
144+
```
145+
146+
To see which revision CMake checked out, a read-only Git query is enough:
147+
148+
```bash
149+
git -C build/_deps/lvgl-src describe --tags
150+
```
151+
152+
## CMake cache behavior: `-D` persists, `-U` removes
153+
154+
A value given with `-DLVGL_GIT_TAG=...` is stored in `build/CMakeCache.txt`. It stays there for every later configure of that build directory and keeps overriding the default in `CMakeLists.txt` until you change or remove it.
155+
156+
| You want to... | Run |
157+
| --- | --- |
158+
| Use another release | `cmake -S . -B build -DLVGL_GIT_TAG=v9.4.0` |
159+
| Drop the override and go back to the default in `CMakeLists.txt` | `cmake -S . -B build -U LVGL_GIT_TAG` |
160+
| Pick up a new default you edited in `CMakeLists.txt` (no `-D` override cached) | `cmake -S . -B build` |
161+
162+
If you edit the default in `CMakeLists.txt` and nothing changes, a cached override is still active: `grep LVGL_GIT_TAG build/CMakeCache.txt` shows it, and `-U LVGL_GIT_TAG` removes it.
163+
164+
Deleting the build directory is a valid clean reset when something is broken, but it is **not** how the dependency gets updated. The commands above are.
165+
166+
```bash
167+
rm -rf build
168+
cmake -S . -B build -G Ninja
169+
```
170+
171+
To build without network access, point `FetchContent` at an existing LVGL checkout; it is then used as is, without cloning or updating:
172+
173+
```bash
174+
cmake -S . -B build -G Ninja -DFETCHCONTENT_SOURCE_DIR_LVGL=/path/to/lvgl
175+
```
176+
177+
## Why this is not a submodule
178+
179+
```text
180+
Git submodule:
181+
Git owns dependency checkout state in the source tree.
182+
183+
FetchContent:
184+
CMake owns dependency population for the build tree.
185+
```
186+
187+
With a submodule, the superproject records a commit of the dependency, the checkout lives in the source tree, and every clone needs `--recursive` or `git submodule update --init`. Updating means a Git operation and a commit in the superproject.
188+
189+
With `FetchContent`, the revision is a line in `CMakeLists.txt`, the checkout lives in the build tree, and a normal clone is enough. Updating means changing a tag and rerunning configure.
190+
191+
This project picks the second model because LVGL is a pure build dependency here and the goal is that a normal clone is sufficient. Neither model is better in general. A submodule is the right choice when you want to edit the dependency inside your source tree, commit against it, or have the source already present after cloning with no network at configure time. `FetchContent` costs network access on the first configure (or a local checkout passed with `FETCHCONTENT_SOURCE_DIR_LVGL`), and each build directory holds its own copy.
192+
193+
## What CI proves
194+
195+
[The workflow](../.github/workflows/cmakeFetchContentVersusGitSubmodule.yml) runs on `ubuntu-latest` for every push and pull request that touches this project:
196+
197+
1. Checks out the repository with `submodules: false` and confirms there is no `.gitmodules` and no tracked LVGL source.
198+
2. Installs only `libsdl2-dev` and `ninja-build`. LVGL is not installed.
199+
3. **Configures a clean build directory with the default tag.** This is where CMake fetches LVGL.
200+
4. Verifies with a read-only `git rev-parse` that `build/_deps/lvgl-src` is exactly at the default tag.
201+
5. Builds the demo against the fetched LVGL.
202+
6. **Reconfigures the same build directory with `-DLVGL_GIT_TAG=v9.4.0`** and verifies that `FetchContent` moved the checkout to that tag.
203+
7. Builds the demo against the switched release.
204+
8. **Reconfigures with `-U LVGL_GIT_TAG`** and verifies the checkout is back on the default tag.
205+
206+
Git is never used to clone, fetch or check out LVGL; CMake does all of that. No `build/` or `_deps/` cache is kept between runs, because a warm cache would hide the first fetch. The job summary lists the requested tag and the checked-out commit after each configure. The runner has no display, so CI stops at building; the window above is the visible proof.
207+
208+
## Tested with
209+
210+
Both LVGL tags, v9.5.0 (default) and v9.4.0, build without a single compiler or CMake warning on:
211+
212+
| Environment | CMake | Compiler | SDL2 |
213+
| --- | --- | --- | --- |
214+
| Manjaro Linux (local, window checked on screen) | 4.4.3 | GCC 16.2.1 | 2.32.72 (sdl2-compat) |
215+
| GitHub `ubuntu-latest` runner (Ubuntu 24.04) | 3.31.6 | GCC 13.3.0 | 2.30.0 |
216+
217+
The whole CI job, clone included, takes about a minute and a half. The minimum stated in `CMakeLists.txt` is CMake 3.28.
218+
219+
## Scope
220+
221+
On purpose, this is not a production LVGL application, an embedded port, an LVGL feature tour, or a package-manager comparison. The demo stays at one window and one label so that the dependency handling is the only thing worth reading.
222+
223+
## License
224+
225+
GPL-3.0-or-later, see the repository's [`LICENSE`](../LICENSE). LVGL is fetched at configure time and is licensed separately under the MIT license.
991 Bytes
Loading

0 commit comments

Comments
 (0)