Back to the topic starter question, this is a strategie I used in a project.
1. In CubeMx set everything up, mcu, clock config, IO, ADC etc.
2. Save the project in a temporary folder.
3. Generate code, using LL drivers (and not HAL), as a Makefile project.
4. Create the folder structure for the project, mine is like this:

- In .vscode there is a config to work easy with VSCode. The C++ extension is setup, Cortex Dug configured for debugging, some tasks for clean, build etc.
- The build folder is generated by make, and removed by make clean
- doc contains the reference guide, datasheet etc. from the mcu
- Drives is copied 1 to 1 from the CubeMx project, containing all mcu specific stuff, drivers etc.
- src contains my source files, every file in this folder is auto compiled by make.
- Makefile is my generic Makefile to build C++ projects, with some changes to work easy with STM32 stuff
- startup_*.s is copied from the CubeMx project
- the svd file comes from the internet (forgot source, just google). This gives a nice view of the registers of the mcu during debug.
- the .ld (linker file) is copied from the CubeMx project
The source folder looks like this:

No explanation needed (I think?)
The C++ encapsulates the calls the the ST LL libraries, and the classes are small and very readable. Porting to a different mcu should be fairly simple.
This is what my main looks like:
char msg[64];
unsigned int cnt = 0;
int main()
{
DigitalOut LED(PA4);
Adc adc(ADC1);
AnalogIn CH0(adc, PA0);
AnalogIn CH1(adc, PA1);
AnalogIn CH2(adc, PA2);
VRef VRef(adc);
Temp Temp(adc);
Usart SerialPort(PA9, PA10, 115200);
while (1)
{
LED = On;
uint16_t in0 = CH0.read();
uint16_t vref = VRef.read();
uint16_t temp = Temp.read();
int len = sprintf(msg, "values [%d]: %d %d %d %d %d\r\n", ++cnt, in0, CH1.read(), CH2.read(), temp, vref);
SerialPort.send(msg, len);
LED = Off;
Delay(1000);
}
}
Again, very readable, maintainable and portable. No need to explain what it does, the code tells the story.
Building with all debug info in the firmware gives these sizes:
arm-none-eabi-size build/firmware.elf
text data bss dec hex filename
6744 116 1904 8764 223c build/firmware.elf
Not bad, considering most of it comes out of the ST LL libraries.