Only rerun bindgen when its dependencies change

This took a bit of wrangling to get the depfiles working, but I
eventually figured it out. ninja -d explain is very useful.

Fixed: 597
Change-Id: I909a4c9418e9dc954e3d328da8f3a825e62544e4
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59005
Reviewed-by: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a1fb153..aac5f0d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,7 @@
 
 include(sources.cmake)
 include(cmake/go.cmake)
+include(cmake/paths.cmake)
 include(cmake/perlasm.cmake)
 
 enable_language(C)
diff --git a/cmake/go.cmake b/cmake/go.cmake
index 61a01f5..966ad32 100644
--- a/cmake/go.cmake
+++ b/cmake/go.cmake
@@ -34,10 +34,7 @@
   else()
     # Ninja expects the target in the depfile to match the output. This is a
     # relative path from the build directory.
-    string(LENGTH "${CMAKE_BINARY_DIR}" root_dir_length)
-    math(EXPR root_dir_length "${root_dir_length} + 1")
-    string(SUBSTRING "${CMAKE_CURRENT_BINARY_DIR}" ${root_dir_length} -1 target)
-    set(target "${target}/${dest}")
+    binary_dir_relative_path(${dest} target)
 
     set(depfile "${CMAKE_CURRENT_BINARY_DIR}/${dest}.d")
     add_custom_command(OUTPUT ${dest}
diff --git a/cmake/paths.cmake b/cmake/paths.cmake
new file mode 100644
index 0000000..43ebc40
--- /dev/null
+++ b/cmake/paths.cmake
@@ -0,0 +1,11 @@
+# binary_dir_relative_path sets outvar to
+# ${CMAKE_CURRENT_BINARY_DIR}/${cur_bin_dir_relative}, but expressed relative to
+# ${CMAKE_BINARY_DIR}.
+#
+# TODO(davidben): When we require CMake 3.20 or later, this can be replaced with
+# the built-in cmake_path(RELATIVE_PATH) function.
+function(binary_dir_relative_path cur_bin_dir_relative outvar)
+  string(LENGTH "${CMAKE_BINARY_DIR}/" root_dir_length)
+  string(SUBSTRING "${CMAKE_CURRENT_BINARY_DIR}/${cur_bin_dir_relative}" ${root_dir_length} -1 result)
+  set(${outvar} ${result} PARENT_SCOPE)
+endfunction()
diff --git a/rust/bssl-sys/CMakeLists.txt b/rust/bssl-sys/CMakeLists.txt
index c11510b..5e5f446 100644
--- a/rust/bssl-sys/CMakeLists.txt
+++ b/rust/bssl-sys/CMakeLists.txt
@@ -2,48 +2,51 @@
 add_library(rust_wrapper STATIC rust_wrapper.c)
 target_link_libraries(rust_wrapper crypto)
 
+# Generate architecture-specific wrappers. bindgen must be called from
+# ${CMAKE_BINARY_DIR}, with the output path as a relative path. bindgen writes
+# the depfile using the same syntax as the command-line argument, and ninja
+# requires a path relative to the top-level build directory.
+set(wrapper src/wrapper_${RUST_BINDINGS}.rs)
+binary_dir_relative_path(${wrapper} wrapper_relative)
+binary_dir_relative_path(${wrapper}.d depfile_relative)
 
-# Generate architecture-specific wrappers.
-set(WRAPPER_TARGET ${CMAKE_BINARY_DIR}/rust/bssl-sys/src/wrapper_${RUST_BINDINGS}.rs)
-set(COMMAND ${BINDGEN_EXECUTABLE} wrapper.h
-            -o ${WRAPPER_TARGET}
-            --no-derive-default
-            --enable-function-attribute-detection
-            --use-core
-            --default-macro-constant-type=signed
-            --rustified-enum=point_conversion_form_t
-            # These regexes need to accept both / and \ to handle Windows file
-            # path differences, due a bindgen issue. See
-            # https://crbug.com/boringssl/595. Ideally, we would write [/\\],
-            # but there are many layers of escaping here. First, CMake
-            # interprets backslashes. Then CMake generates a Ninja or Make file.
-            # That, in turn, launches passes inputs to the shell on POSIX, and
-            # does something else on Windows.
-            #
-            # It is unlikely that every layer here has sufficiently well-defined
-            # escaping and correctly handled the next layer's escaping. On top
-            # of that, we'd likely need to detect Windows vs POSIX hosts and
-            # change the input. Instead, just use [[:punct:]] which is more
-            # permissive than necessary, but we only need to exclude unwanted
-            # libc heaaders.
-            #
-            # If bindgen ever supports some file-based config (see
-            # https://github.com/rust-lang/rust-bindgen/issues/2508), we can
-            # switch to that.
-            --allowlist-file=".*[[:punct:]]include[[:punct:]]openssl[[:punct:]].*\\.h"
-            --allowlist-file=".*[[:punct:]]rust_wrapper\\.h"
-            -- # these are LLVM arg passthroughs
-            -I../../include
-            # https://doc.rust-lang.org/nightly/rustc/platform-support.html
-            --target=${RUST_BINDINGS})
-
-add_custom_target(
-  bindgen_rust_${RUST_BINDINGS}
-  ALL
-  ${COMMAND}
-  BYPRODUCTS ${WRAPPER_TARGET}
-  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+add_custom_command(
+  OUTPUT ${wrapper}
+  COMMAND ${BINDGEN_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.h
+          -o ${wrapper_relative}
+          --depfile=${depfile_relative}
+          --no-derive-default
+          --enable-function-attribute-detection
+          --use-core
+          --default-macro-constant-type=signed
+          --rustified-enum=point_conversion_form_t
+          # These regexes need to accept both / and \ to handle Windows file
+          # path differences, due a bindgen issue. See
+          # https://crbug.com/boringssl/595. Ideally, we would write [/\\], but
+          # there are many layers of escaping here. First, CMake interprets
+          # backslashes. Then CMake generates a Ninja or Make file. That, in
+          # turn, uses the shell on POSIX, and does something else on Windows.
+          #
+          # It is unlikely that every layer here has sufficiently well-defined
+          # escaping and correctly handled the next layer's escaping. On top of
+          # that, we'd likely need to detect Windows vs POSIX hosts and change
+          # the input. Instead, just use [[:punct:]] which is more permissive
+          # than necessary, but we only need to exclude unwanted libc headers.
+          #
+          # If bindgen ever supports some file-based config (see
+          # https://github.com/rust-lang/rust-bindgen/issues/2508), we can
+          # switch to that.
+          --allowlist-file=".*[[:punct:]]include[[:punct:]]openssl[[:punct:]].*\\.h"
+          --allowlist-file=".*[[:punct:]]rust_wrapper\\.h"
+          -- # these are LLVM arg passthroughs
+          -I${PROJECT_SOURCE_DIR}/include
+          # https://doc.rust-lang.org/nightly/rustc/platform-support.html
+          --target=${RUST_BINDINGS}
+  DEPENDS wrapper.h
+  DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/${wrapper}.d
+  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
 )
+add_custom_target(bssl_sys ALL DEPENDS ${wrapper})
 
 # move files into build directory
 configure_file("src/lib.rs" "src/lib.rs")