False dichotomy; simply don't make abundant use of #ifdefs.
Most large, portable projects don't use #ifdefs across the codebase. They abstract the core concepts out into interfaces that can be implemented atop different APIs, and then maintain distinct implementations where necessary.
The advantage is that this also insulates you from changes to the underlying system APIs, creates testable code, and provides clean well-defined points of interaction between subsystems.