Step 7: Adding System Introspection

Let us consider adding some code to our project that depends on features the target platform may not have. For this example, we will add some code that depends on whether or not the target platform has the log and exp functions. Of course almost every platform has these functions but for this tutorial assume that they are not common.

Exercise 1 - Assessing Dependency Availability

Goal

Change implementation based on available system dependencies.

Helpful Resources

Files to Edit

  • MathFunctions/CMakeLists.txt

  • MathFunctions/mysqrt.cxx

Getting Started

The starting source code is provided in the Step7 directory. In this exercise, complete TODO 1 through TODO 5.

Start by editing MathFunctions/CMakeLists.txt. Include the CheckCXXSourceCompiles module. Then, use check_cxx_source_compiles to determine whether log and exp are available from cmath. If they are available, use target_compile_definitions() to specify HAVE_LOG and HAVE_EXP as compile definitions.

In the MathFunctions/mysqrt.cxx, include cmath. Then, if the system has log and exp, use them to compute the square root.

Build and Run

Make a new directory called Step7_build. Run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool and run the Tutorial executable.

This can look like the following:

mkdir Step7_build
cd Step7_build
cmake ../Step7
cmake --build .

Which function gives better results now, sqrt or mysqrt?

Solution

In this exercise we will use functions from the CheckCXXSourceCompiles module so first we must include it in MathFunctions/CMakeLists.txt.

TODO 1: Click to show/hide answer
TODO 1: MathFunctions/CMakeLists.txt
  include(CheckCXXSourceCompiles)

Then test for the availability of log and exp using check_cxx_compiles_source. This function lets us try compiling simple code with the required dependency prior to the true source code compilation. The resulting variables HAVE_LOG and HAVE_EXP represent whether those dependencies are available.

TODO 2: Click to show/hide answer
TODO 2: MathFunctions/CMakeLists.txt
  check_cxx_source_compiles("
    #include <cmath>
    int main() {
      std::log(1.0);
      return 0;
    }
  " HAVE_LOG)
  check_cxx_source_compiles("
    #include <cmath>
    int main() {
      std::exp(1.0);
      return 0;
    }
  " HAVE_EXP)

Next, we need to pass these CMake variables to our source code. This way, our source code can tell what resources are available. If both log and exp are available, use target_compile_definitions() to specify HAVE_LOG and HAVE_EXP as PRIVATE compile definitions.

TODO 3: Click to show/hide answer
TODO 3: MathFunctions/CMakeLists.txt
  if(HAVE_LOG AND HAVE_EXP)
    target_compile_definitions(SqrtLibrary
                               PRIVATE "HAVE_LOG" "HAVE_EXP"
                               )
  endif()

  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()

Since we may be using log and exp, we need to modify mysqrt.cxx to include cmath.

TODO 4: Click to show/hide answer
TODO 4: MathFunctions/mysqrt.cxx
#include <cmath>

If log and exp are available on the system, then use them to compute the square root in the mysqrt function. The mysqrt function in MathFunctions/mysqrt.cxx will look as follows:

TODO 5: Click to show/hide answer
TODO 5: MathFunctions/mysqrt.cxx
#if defined(HAVE_LOG) && defined(HAVE_EXP)
  double result = std::exp(std::log(x) * 0.5);
  std::cout << "Computing sqrt of " << x << " to be " << result
            << " using log and exp" << std::endl;
#else
  double result = x;

  // do ten iterations
  for (int i = 0; i < 10; ++i) {
    if (result <= 0) {
      result = 0.1;
    }
    double delta = x - (result * result);
    result = result + 0.5 * delta / result;
    std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
  }
#endif