diff --git a/wpimath/drake-dare-qrn.patch b/wpimath/drake-dare-qrn.patch new file mode 100644 index 0000000000..8033d2dbc8 --- /dev/null +++ b/wpimath/drake-dare-qrn.patch @@ -0,0 +1,55 @@ +diff --git a/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp b/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp +index 9585c4dae..d53fbdddf 100644 +--- a/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp ++++ b/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp +@@ -453,5 +453,18 @@ Eigen::MatrixXd DiscreteAlgebraicRiccatiEquation( + return X; + } + ++Eigen::MatrixXd DiscreteAlgebraicRiccatiEquation( ++ const Eigen::Ref& A, ++ const Eigen::Ref& B, ++ const Eigen::Ref& Q, ++ const Eigen::Ref& R, ++ const Eigen::Ref& N) { ++ DRAKE_DEMAND(N.rows() == B.rows() && N.cols() == B.cols()); ++ ++ Eigen::MatrixXd scrA = A - B * R.llt().solve(N.transpose()); ++ Eigen::MatrixXd scrQ = Q - N * R.llt().solve(N.transpose()); ++ return DiscreteAlgebraicRiccatiEquation(scrA, B, scrQ, R); ++} ++ + } // namespace math + } // namespace drake +diff --git a/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h b/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h +index e3fea30c5..9b9fe97ec 100644 +--- a/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h ++++ b/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h +@@ -27,6 +27,27 @@ Eigen::MatrixXd DiscreteAlgebraicRiccatiEquation( + const Eigen::Ref& B, + const Eigen::Ref& Q, + const Eigen::Ref& R); ++ ++/// DiscreteAlgebraicRiccatiEquation function ++/// computes the unique stabilizing solution X to the discrete-time algebraic ++/// Riccati equation: ++/// \f[ ++/// A'XA - X - (A'XB + N)(B'XB + R)^{-1}(B'XA + N') + Q = 0 ++/// \f] ++/// ++/// See ++/// https://en.wikipedia.org/wiki/Linear%E2%80%93quadratic_regulator#Infinite-horizon,_discrete-time_LQR ++/// for the change of variables used here. ++/// ++/// @throws std::runtime_error if Q is not positive semi-definite. ++/// @throws std::runtime_error if R is not positive definite. ++/// ++Eigen::MatrixXd DiscreteAlgebraicRiccatiEquation( ++ const Eigen::Ref& A, ++ const Eigen::Ref& B, ++ const Eigen::Ref& Q, ++ const Eigen::Ref& R, ++ const Eigen::Ref& N); + } // namespace math + } // namespace drake + diff --git a/wpimath/drake-replace-dense-with-core.patch b/wpimath/drake-replace-dense-with-core.patch new file mode 100644 index 0000000000..0294a0e2a3 --- /dev/null +++ b/wpimath/drake-replace-dense-with-core.patch @@ -0,0 +1,40 @@ +diff --git b/wpimath/src/main/native/include/drake/common/is_approx_equal_abstol.h a/wpimath/src/main/native/include/drake/common/is_approx_equal_abstol.h +index 9af0c4525..b3f369ca0 100644 +--- b/wpimath/src/main/native/include/drake/common/is_approx_equal_abstol.h ++++ a/wpimath/src/main/native/include/drake/common/is_approx_equal_abstol.h +@@ -2,7 +2,7 @@ + + #include + +-#include ++#include + + namespace drake { + +diff --git a/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp b/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp +index 9585c4dae..49c2fefe7 100644 +--- a/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp ++++ b/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp +@@ -1,5 +1,8 @@ + #include "drake/math/discrete_algebraic_riccati_equation.h" + ++#include ++#include ++ + #include "drake/common/drake_assert.h" + #include "drake/common/drake_throw.h" + #include "drake/common/is_approx_equal_abstol.h" +diff --git b/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h a/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h +index b64bfe75e..fc5efb313 100644 +--- b/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h ++++ a/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h +@@ -3,7 +3,7 @@ + #include + #include + +-#include ++#include + + namespace drake { + namespace math { + diff --git a/wpimath/drake-replace-noreturn-attributes.patch b/wpimath/drake-replace-noreturn-attributes.patch new file mode 100644 index 0000000000..20fd8b9d80 --- /dev/null +++ b/wpimath/drake-replace-noreturn-attributes.patch @@ -0,0 +1,30 @@ +diff --git b/wpimath/src/main/native/include/drake/common/drake_assert.h a/wpimath/src/main/native/include/drake/common/drake_assert.h +index acc1298fe..21e7bd100 100644 +--- b/wpimath/src/main/native/include/drake/common/drake_assert.h ++++ a/wpimath/src/main/native/include/drake/common/drake_assert.h +@@ -83,10 +83,10 @@ + namespace drake { + namespace internal { + // Abort the program with an error message. +-__attribute__((noreturn)) /* gcc is ok with [[noreturn]]; clang is not. */ ++[[noreturn]] + void Abort(const char* condition, const char* func, const char* file, int line); + // Report an assertion failure; will either Abort(...) or throw. +-__attribute__((noreturn)) /* gcc is ok with [[noreturn]]; clang is not. */ ++[[noreturn]] + void AssertionFailed( + const char* condition, const char* func, const char* file, int line); + } // namespace internal +diff --git b/wpimath/src/main/native/include/drake/common/drake_throw.h a/wpimath/src/main/native/include/drake/common/drake_throw.h +index ffa617c25..d19e4efb7 100644 +--- b/wpimath/src/main/native/include/drake/common/drake_throw.h ++++ a/wpimath/src/main/native/include/drake/common/drake_throw.h +@@ -12,7 +12,7 @@ + namespace drake { + namespace internal { + // Throw an error message. +-__attribute__((noreturn)) /* gcc is ok with [[noreturn]]; clang is not. */ ++[[noreturn]] + void Throw(const char* condition, const char* func, const char* file, int line); + } // namespace internal + } // namespace drake diff --git a/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp b/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp index cf5267f423..268de596af 100644 --- a/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp +++ b/wpimath/src/main/native/cpp/drake/math/discrete_algebraic_riccati_equation.cpp @@ -1,12 +1,12 @@ #include "drake/math/discrete_algebraic_riccati_equation.h" +#include +#include + #include "drake/common/drake_assert.h" #include "drake/common/drake_throw.h" #include "drake/common/is_approx_equal_abstol.h" -#include -#include - namespace drake { namespace math { namespace { diff --git a/wpimath/src/main/native/include/drake/common/drake_assert.h b/wpimath/src/main/native/include/drake/common/drake_assert.h index 21e7bd100c..6263259c6d 100644 --- a/wpimath/src/main/native/include/drake/common/drake_assert.h +++ b/wpimath/src/main/native/include/drake/common/drake_assert.h @@ -98,7 +98,7 @@ namespace assert { // require special handling. template struct ConditionTraits { - static constexpr bool is_valid = std::is_convertible::value; + static constexpr bool is_valid = std::is_convertible_v; static bool Evaluate(const Condition& value) { return value; } @@ -113,7 +113,7 @@ struct ConditionTraits { #define DRAKE_DEMAND(condition) \ do { \ typedef ::drake::assert::ConditionTraits< \ - typename std::remove_cv::type> Trait; \ + typename std::remove_cv_t> Trait; \ static_assert(Trait::is_valid, "Condition should be bool-convertible."); \ if (!Trait::Evaluate(condition)) { \ ::drake::internal::AssertionFailed( \ @@ -130,7 +130,7 @@ constexpr bool kDrakeAssertIsDisarmed = false; # define DRAKE_ASSERT(condition) DRAKE_DEMAND(condition) # define DRAKE_ASSERT_VOID(expression) do { \ static_assert( \ - std::is_convertible::value, \ + std::is_convertible_v, \ "Expression should be void."); \ expression; \ } while (0) @@ -142,10 +142,10 @@ constexpr bool kDrakeAssertIsDisarmed = true; } // namespace drake # define DRAKE_ASSERT(condition) static_assert( \ ::drake::assert::ConditionTraits< \ - typename std::remove_cv::type>::is_valid, \ + typename std::remove_cv_t>::is_valid, \ "Condition should be bool-convertible."); # define DRAKE_ASSERT_VOID(expression) static_assert( \ - std::is_convertible::value, \ + std::is_convertible_v, \ "Expression should be void.") #endif diff --git a/wpimath/src/main/native/include/drake/common/drake_assertion_error.h b/wpimath/src/main/native/include/drake/common/drake_assertion_error.h index 541b118ecf..b42847453e 100644 --- a/wpimath/src/main/native/include/drake/common/drake_assertion_error.h +++ b/wpimath/src/main/native/include/drake/common/drake_assertion_error.h @@ -6,8 +6,8 @@ namespace drake { namespace internal { -/// This is what DRAKE_ASSERT and DRAKE_DEMAND throw when our assertions are -/// configured to throw. +// This is what DRAKE_ASSERT and DRAKE_DEMAND throw when our assertions are +// configured to throw. class assertion_error : public std::runtime_error { public: explicit assertion_error(const std::string& what_arg) diff --git a/wpimath/src/main/native/include/drake/common/drake_copyable.h b/wpimath/src/main/native/include/drake/common/drake_copyable.h index 086f1f78ee..f8949cc46b 100644 --- a/wpimath/src/main/native/include/drake/common/drake_copyable.h +++ b/wpimath/src/main/native/include/drake/common/drake_copyable.h @@ -64,49 +64,3 @@ class Foo { (void) static_cast(&Classname::operator=); \ } - -/** DRAKE_DECLARE_COPY_AND_MOVE_AND_ASSIGN declares (but does not define) the -special member functions for copy-construction, copy-assignment, -move-construction, and move-assignment. Drake's Doxygen is customized to -render the declarations with appropriate comments. - -This is useful when paired with DRAKE_DEFINE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN_T -to work around https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57728 whereby the -declaration and definition must be split. Once Drake no longer supports GCC -versions prior to 6.3, this macro could be removed. - -Invoke this this macro in the public section of the class declaration, e.g.: -
-template 
-class Foo {
- public:
-  DRAKE_DECLARE_COPY_AND_MOVE_AND_ASSIGN(Foo)
-
-  // ...
-};
-DRAKE_DEFINE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN_T(Foo)
-
-*/ -#define DRAKE_DECLARE_COPY_AND_MOVE_AND_ASSIGN(Classname) \ - Classname(const Classname&); \ - Classname& operator=(const Classname&); \ - Classname(Classname&&); \ - Classname& operator=(Classname&&); \ - /* Fails at compile-time if default-copy doesn't work. */ \ - static void DRAKE_COPYABLE_DEMAND_COPY_CAN_COMPILE() { \ - (void) static_cast(&Classname::operator=); \ - } - -/** Helper for DRAKE_DECLARE_COPY_AND_MOVE_AND_ASSIGN. Provides defaulted -definitions for the four special member functions of a templated class. -*/ -#define DRAKE_DEFINE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN_T(Classname) \ - template \ - Classname::Classname(const Classname&) = default; \ - template \ - Classname& Classname::operator=(const Classname&) = default; \ - template \ - Classname::Classname(Classname&&) = default; \ - template \ - Classname& Classname::operator=(Classname&&) = default; diff --git a/wpimath/src/main/native/include/drake/common/drake_throw.h b/wpimath/src/main/native/include/drake/common/drake_throw.h index d19e4efb70..aa992a16c7 100644 --- a/wpimath/src/main/native/include/drake/common/drake_throw.h +++ b/wpimath/src/main/native/include/drake/common/drake_throw.h @@ -7,7 +7,7 @@ /// @file /// Provides a convenient wrapper to throw an exception when a condition is /// unmet. This is similar to an assertion, but uses exceptions instead of -/// std::abort(), and cannot be disabled. +/// ::abort(), and cannot be disabled. namespace drake { namespace internal { @@ -23,7 +23,7 @@ void Throw(const char* condition, const char* func, const char* file, int line); #define DRAKE_THROW_UNLESS(condition) \ do { \ typedef ::drake::assert::ConditionTraits< \ - typename std::remove_cv::type> Trait; \ + typename std::remove_cv_t> Trait; \ static_assert(Trait::is_valid, "Condition should be bool-convertible."); \ if (!Trait::Evaluate(condition)) { \ ::drake::internal::Throw(#condition, __func__, __FILE__, __LINE__); \ diff --git a/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h b/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h index fc5efb313f..0f2a2d78ec 100644 --- a/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h +++ b/wpimath/src/main/native/include/drake/math/discrete_algebraic_riccati_equation.h @@ -5,7 +5,8 @@ #include -namespace drake::math { +namespace drake { +namespace math { /// Computes the unique stabilizing solution X to the discrete-time algebraic /// Riccati equation: @@ -47,4 +48,6 @@ Eigen::MatrixXd DiscreteAlgebraicRiccatiEquation( const Eigen::Ref& Q, const Eigen::Ref& R, const Eigen::Ref& N); -} // namespace drake::math +} // namespace math +} // namespace drake + diff --git a/wpimath/update_drake.py b/wpimath/update_drake.py new file mode 100755 index 0000000000..199def0e1b --- /dev/null +++ b/wpimath/update_drake.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +import os +import re +import subprocess + +from upstream_utils import clone_repo, comment_out_invalid_includes, copy_to, walk_if + + +def drake_cpp_inclusions(dp, f): + """Returns true if the given source file in the drake git repo should be + copied into allwpilib + + Keyword arguments: + dp -- directory path + f -- filename + """ + return f in [ + "drake_assert_and_throw.cc", "discrete_algebraic_riccati_equation.cc" + ] + + +def drake_h_inclusions(dp, f): + """Returns true if the given header file in the drake git repo should be + copied into allwpilib + + Keyword arguments: + dp -- directory path + f -- filename + """ + return f in [ + "drake_assert.h", "drake_assertion_error.h", "is_approx_equal_abstol.h", + "never_destroyed.h", "drake_copyable.h", "drake_throw.h", + "discrete_algebraic_riccati_equation.h" + ] + + +def main(): + wpimath = os.getcwd() + clone_repo("https://github.com/RobotLocomotion/drake", "v0.31.0") + + # Copy drake source files into allwpilib + files = walk_if(".", drake_cpp_inclusions) + dest = os.path.join(wpimath, "src/main/native/cpp/drake") + copy_to(files, dest) + + for i in range(len(files)): + # Files moved to dest + files[i] = os.path.join(dest, files[i]) + + # copy_to() renames .cc files to .cpp + if files[i].endswith(".cc"): + files[i] = os.path.splitext(files[i])[0] + ".cpp" + + for f in files: + comment_out_invalid_includes( + f, os.path.join(wpimath, "src/main/native/include")) + + # Copy drake header files into allwpilib + files = walk_if(".", drake_h_inclusions) + dest = os.path.join(wpimath, "src/main/native/include/drake") + copy_to(files, dest) + + # Files moved to dest + for i in range(len(files)): + files[i] = os.path.join(dest, files[i]) + + for f in files: + comment_out_invalid_includes( + f, os.path.join(wpimath, "src/main/native/include")) + + # Apply patches + os.chdir(wpimath) + subprocess.check_output(["git", "apply", "drake-dare-qrn.patch"]) + subprocess.check_output( + ["git", "apply", "drake-replace-noreturn-attributes.patch"]) + subprocess.check_output( + ["git", "apply", "drake-replace-dense-with-core.patch"]) + + +if __name__ == "__main__": + main() diff --git a/wpimath/update_eigen.py b/wpimath/update_eigen.py index ebf7eae854..3470ad22fb 100755 --- a/wpimath/update_eigen.py +++ b/wpimath/update_eigen.py @@ -2,45 +2,9 @@ import os import re -import shutil import subprocess -import tempfile - -def clone_repo(url, treeish): - """Clones a git repo at the given URL and checks out the given tree-ish - (either branch or tag). - - Keyword argument: - url -- The URL of the git repo - treeish -- The tree-ish to check out (branch or tag) - """ - repo = os.path.basename(url) - dest = os.path.join(os.getcwd(), repo).rstrip(".git") - - # Clone Git repository into current directory or update it - if not os.path.exists(dest): - subprocess.run(["git", "clone", url, dest]) - os.chdir(dest) - else: - os.chdir(dest) - subprocess.run(["git", "fetch", "origin", treeish]) - - # Get list of heads - # Example output of "git ls-remote --heads": - # From https://gitlab.com/libeigen/eigen.git - # 77c66e368c7e355f8be299659f57b0ffcaedb505 refs/heads/3.4 - # 3e006bfd31e4389e8c5718c30409cddb65a73b04 refs/heads/master - ls_out = subprocess.check_output(["git", "ls-remote", - "--heads"]).decode().rstrip() - heads = [x.split()[1] for x in ls_out.split("\n")[1:]] - - if f"refs/heads/{treeish}" in heads: - # Checking out the remote branch avoids needing to handle syncing a - # preexisting local one - subprocess.run(["git", "checkout", f"origin/{treeish}"]) - else: - subprocess.run(["git", "checkout", treeish]) +from upstream_utils import clone_repo, comment_out_invalid_includes, copy_to, walk_if def eigen_inclusions(dp, f): @@ -120,82 +84,41 @@ def unsupported_inclusions(dp, f): return "AutoDiff" in abspath or "MatrixFunctions" in abspath -def comment_out_invalid_includes(filename): - """Comment out #include directives that include a nonexistent file - - Keyword arguments: - filename -- file to search for includes - """ - # Read header - with open(filename) as f: - old_contents = f.read() - - new_contents = "" - pos = 0 - for match in re.finditer(r"#include \"([^\"]+)\"", old_contents): - include = match.group(1) - - # Write contents from before this match - new_contents += old_contents[pos:match.span()[0]] - - # Comment out #include if the file doesn't exist - if not os.path.exists(os.path.join(os.path.dirname(filename), include)): - new_contents += "// " - - new_contents += match.group() - pos = match.span()[1] - - # Write rest of file if it wasn't all processed - if pos < len(old_contents): - new_contents += old_contents[pos:] - - # Write modified file back out - if old_contents != new_contents: - with open(filename, "w") as f: - f.write(new_contents) - - def main(): - cwd = os.getcwd() - os.chdir(tempfile.gettempdir()) + wpimath = os.getcwd() clone_repo("https://gitlab.com/libeigen/eigen.git", "3.3.9") + repo = os.getcwd() - # Get list of Eigen headers to copy - files = [ - os.path.join(dp, f) - for dp, dn, fn in os.walk("Eigen") - for f in fn - if eigen_inclusions(dp, f) - ] - files += [ - os.path.join(dp, f) - for dp, dn, fn in os.walk("unsupported") - for f in fn - if unsupported_inclusions(dp, f) - ] + # Copy Eigen headers into allwpilib + os.chdir(os.path.join(repo, "Eigen")) + files = walk_if(".", eigen_inclusions) + dest = os.path.join(wpimath, "src/main/native/eigeninclude/Eigen") + copy_to(files, dest) - # Delete old Eigen install - include_root = os.path.join(cwd, "src/main/native/eigeninclude") - for folder in ["Eigen", "unsupported"]: - shutil.rmtree(os.path.join(include_root, folder), ignore_errors=True) + # Files moved to dest + for i in range(len(files)): + files[i] = os.path.join(dest, files[i]) - # Copy new Eigen into allwpilib for f in files: - dest_file = os.path.join(include_root, f) - dest_dir = os.path.dirname(dest_file) - if not os.path.exists(dest_dir): - os.makedirs(dest_dir) - shutil.copyfile(f, dest_file) + comment_out_invalid_includes(f, dest) + + # Copy unsupported headers into allwpilib + os.chdir(os.path.join(repo, "unsupported")) + files = walk_if(".", unsupported_inclusions) + dest = os.path.join(wpimath, "src/main/native/eigeninclude/unsupported") + copy_to(files, dest) + + # Files moved to dest + for i in range(len(files)): + files[i] = os.path.join(dest, files[i]) + + for f in files: + comment_out_invalid_includes(f, dest) # Apply patches - os.chdir(cwd) + os.chdir(wpimath) subprocess.check_output(["git", "apply", "eigen-maybe-uninitialized.patch"]) - # Comment out missing headers - for f in files: - dest_file = os.path.join(include_root, f) - comment_out_invalid_includes(dest_file) - if __name__ == "__main__": main() diff --git a/wpimath/upstream_utils.py b/wpimath/upstream_utils.py new file mode 100644 index 0000000000..da0df212a9 --- /dev/null +++ b/wpimath/upstream_utils.py @@ -0,0 +1,128 @@ +import os +import re +import shutil +import subprocess +import tempfile + + +def clone_repo(url, treeish): + """Clones a git repo at the given URL into a temp folder and checks out the + given tree-ish (either branch or tag). + + The current working directory will be set to the repository folder. + + Keyword argument: + url -- The URL of the git repo + treeish -- The tree-ish to check out (branch or tag) + """ + os.chdir(tempfile.gettempdir()) + + repo = os.path.basename(url) + dest = os.path.join(os.getcwd(), repo).rstrip(".git") + + # Clone Git repository into current directory or update it + if not os.path.exists(dest): + subprocess.run(["git", "clone", url, dest]) + os.chdir(dest) + else: + os.chdir(dest) + subprocess.run(["git", "fetch", "origin", treeish]) + + # Get list of heads + # Example output of "git ls-remote --heads": + # From https://gitlab.com/libeigen/eigen.git + # 77c66e368c7e355f8be299659f57b0ffcaedb505 refs/heads/3.4 + # 3e006bfd31e4389e8c5718c30409cddb65a73b04 refs/heads/master + ls_out = subprocess.check_output(["git", "ls-remote", + "--heads"]).decode().rstrip() + heads = [x.split()[1] for x in ls_out.split("\n")[1:]] + + if f"refs/heads/{treeish}" in heads: + # Checking out the remote branch avoids needing to handle syncing a + # preexisting local one + subprocess.run(["git", "checkout", f"origin/{treeish}"]) + else: + subprocess.run(["git", "checkout", treeish]) + + +def walk_if(top, pred): + """Walks the current directory, then returns a list of files for which the + given predicate is true. + + Keyword arguments: + top -- the top directory to walk + pred -- a function that takes a directory path and a filename, then returns + True if the file should be included in the output list + """ + return [ + os.path.join(dp, f) + for dp, dn, fn in os.walk(top) + for f in fn + if pred(dp, f) + ] + + +def copy_to(files, root): + if os.path.exists(root): + # Delete old install + for filename in os.listdir(root): + filepath = os.path.join(root, filename) + if os.path.isfile(filepath) or os.path.islink(filepath): + os.unlink(filepath) + elif os.path.isdir(filepath): + shutil.rmtree(filepath) + else: + os.makedirs(root) + + for f in files: + dest_file = os.path.join(root, f) + + # Rename .cc file to .cpp + if dest_file.endswith(".cc"): + dest_file = os.path.splitext(dest_file)[0] + ".cpp" + + # Make leading directory + dest_dir = os.path.dirname(dest_file) + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + + shutil.copyfile(f, dest_file) + + +def comment_out_invalid_includes(filename, include_root): + """Comment out #include directives that include a nonexistent file + + Keyword arguments: + filename -- file to search for includes + include_root -- the search path for includes + """ + # Read header + with open(filename) as f: + old_contents = f.read() + + new_contents = "" + pos = 0 + for match in re.finditer(r"#include \"([^\"]+)\"", old_contents): + include = match.group(1) + + # Write contents from before this match + new_contents += old_contents[pos:match.span()[0]] + + # Comment out #include if the file doesn't exist in current directory or + # include root + if not os.path.exists(os.path.join( + os.path.dirname(filename), include)) and not os.path.exists( + os.path.join(include_root, include)): + new_contents += "// " + + new_contents += match.group() + pos = match.span()[1] + + # Write rest of file if it wasn't all processed + if pos < len(old_contents): + new_contents += old_contents[pos:] + + # Write modified file back out + if old_contents != new_contents: + with open(filename, "w") as f: + f.write(new_contents)