Yes, often you had to set a macro, e.g. telling which MCU board it is, also which board revision etc.
Yes, it helps to "toggle" between different boards: same code source for different boards, but the macro selects which real board is used.
I use often to distinguish between two different revisions: a newer revision of a board can have different GPIO pins to use.
But to write a source code, with macros, which should run on completely different MCU boards - very challenging (but possible).
If you want to toggle with FW source code between an STMF768I-DISCO and a Portenata H7, or a NUCLEO-STM32H745ZI-Q - your project needs also other Driver LIB, other HAL files, other BSP files etc.
This can cause to toggle also LIB files, much more project settings, e.g. the MCU type, instruction set, HW FPU used or not.
I use macros to differentiate between different revisions of a board, but not really to differentiate between different MCU types and platforms. Pretty tough to know and handle the differences.
And: the code becomes really messy when you mix two boards with different macros set.
Often, one macro is not properly maintained: you flip the macro and it does not work, even on the correct board: to support two versions of the same board with a different macro is often lazy and an update not done for the other flavor.
BTW:
You can "ask" the MCU to tell you which MCU type, revision it is (not the board revision but if it is a STM32H7 vs. STM32F7). You can use this register value on the fly to adapt in your code what to do when you this or the other MCU (not a macro, a run-time "if" depending on what MCU found).
The revision of a base board - you cannot figure out, you have to know. Even not possible to realize if you use a breakout board, or a VisionShield etc.
The Raspberry Pi is a bit smarter: it has an I2C flash on board from where you can read which HW version and config it is.
You can also use I2C flash for "system config" - I use:
My FW has a lot of configurable parameters, used during startup. It looks into an I2C flash memory and reads important values there.
These values can be: UART baud rate, GPIO pins usable, HW features, also max. MCU clock, SPI mode, SPI clock etc.
It configures the MCU FW on startup with these values.
When I change now to a different board - I change this "system config" in I2C flash and the FW "adapts itself" to the modified and correct setting.
My own mainboard has an I2C flash: and putting a different MCU module on my main board - it reads on start/reset the I2C flash which tells my FW what main board it is (and which features it has).
So, macros are "cool" to handle slightly different changes between different versions.
But a "runtime system config" to read (Linux, Raspberry Pi ... they use a "device tree") makes it even more flexible but needs a lot of coding to support.
The drawback with macros: changing a board, type etc. needs a new compile of your source code, flash code etc.
But a FW "reading" the board config can follow automatically what it will find (e.g. which main board used, which revision ...).