SCons / scons

SCons - a software construction tool
http://scons.org
MIT License
2.07k stars 316 forks source link

C/C++ scanner: add __has_include #4517

Open mwichmann opened 5 months ago

mwichmann commented 5 months ago

A preprocessor identifier __has_include was added to the C standard in the 2023 edition; the same identifier has appeared in the C++ standard since the 2017 edition (one of the justifications for adding to C was to keep the preprocessors relatively aligned). The identifier allows coding conditional inclusion of a header file, and thus affects SCons dependency scanning. Since most C compilers are also C++ compilers, this identifier is widely implemented already.

From the standard, here is example usage:

#if __has_include(<optional.h>)
  #include <optional.h>
  #define have_optional 1
#elif __has_include(<experimental/optional.h>)
  #include <experimental/optional.h>
  #define have_optional 1
  #define have_experimental_optional 1
#endif
#ifndef have_optional
  #define have_optional 0
#endif

As a shorter write-up than going through the entire standard, here is the accepted proposal which added it to C:

https://open-std.org/JTC1/SC22/WG14/www/docs/n2799.pdf

mwichmann commented 5 months ago

For reading enjoyment, here's a snazzy C++ example from cppreference.com using __has_include:

#if __has_include(<optional>)
#  include <optional>
#  define has_optional 1
   template<class T> using optional_t = std::optional
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>
#  define has_optional -1
   template<class T> using optional_t = std::experimental::optional
#else
#  define has_optional 0
#  include <utility>

template<class V>
class optional_t
{
    V v_{}; bool has_{false};
public:
    optional_t() = default;
    optional_t(V&& v) : v_(v), has_{true} {}
    V value_or(V&& alt) const& { return has_ ? v_ : alt; }
    /*...*/
};
#endif

Don't have any clear idea how to teach the scanner to handle this case, where a system header is used if it exists, if not an experimental header is used, and if neither, some inline code is used. We don't usually generate dependencies for "system headers", but the algorithm is unsophisticated - if it's not found, assume it's a system header and don't generate the dep, which has the potential to miss headers which will be generated by the build. There's of course no rule that says this check-for-include-file functionality has to be limited to system headers.

bdbaddog commented 5 months ago

Does the default scanner do ifdefs? or is it just the optional scanner which does?

mwichmann commented 5 months ago

They use the same base class which does recognize ifdef and friends, but doesn't evaluate them; the conditional scanner is "smarter" and adds extra stuff.