@@ -53,6 +53,25 @@ namespace attributes {
5353 std::string path () const { return path_; }
5454 bool exists () const { return exists_; }
5555 time_t lastModified () const { return lastModified_; }
56+
57+ bool operator <(const FileInfo& other) const {
58+ return path_ < other.path_ ;
59+ };
60+
61+ bool operator ==(const FileInfo& other) const {
62+ return path_ == other.path_ &&
63+ exists_ == other.exists_ &&
64+ lastModified_ == other.lastModified_ ;
65+ };
66+
67+ bool operator !=(const FileInfo& other) const {
68+ return ! (*this == other);
69+ };
70+
71+ std::ostream& operator <<(std::ostream& os) const {
72+ os << path_;
73+ return os;
74+ }
5675
5776 private:
5877 std::string path_;
@@ -355,7 +374,8 @@ namespace attributes {
355374 // Class used to parse and return attribute information from a source file
356375 class SourceFileAttributesParser : public SourceFileAttributes {
357376 public:
358- explicit SourceFileAttributesParser (const std::string& sourceFile);
377+ explicit SourceFileAttributesParser (const std::string& sourceFile,
378+ bool localIncludes);
359379
360380 private:
361381 // prohibit copying
@@ -405,6 +425,11 @@ namespace attributes {
405425 const std::vector<std::string>& embeddedR () const {
406426 return embeddedR_;
407427 }
428+
429+ // Get local includes
430+ const std::vector<FileInfo>& localIncludes () const {
431+ return localIncludes_;
432+ };
408433
409434 private:
410435
@@ -437,6 +462,7 @@ namespace attributes {
437462 FunctionMap functionMap_ ;
438463 std::vector<std::string> modules_;
439464 std::vector<std::string> embeddedR_;
465+ std::vector<FileInfo> localIncludes_;
440466 std::vector<std::vector<std::string> > roxygenChunks_;
441467 std::vector<std::string> roxygenBuffer_;
442468 };
@@ -690,6 +716,102 @@ namespace attributes {
690716 return matches;
691717 }
692718
719+ template <typename Stream>
720+ void readFile (const std::string& file, Stream& os) {
721+ std::ifstream ifs (file.c_str ());
722+ if (ifs.fail ())
723+ throw Rcpp::file_io_error (file);
724+ os << ifs.rdbuf ();
725+ ifs.close ();
726+ }
727+
728+ template <typename Collection>
729+ void readLines (std::istream& is, Collection* pLines) {
730+ pLines->clear ();
731+ std::string line;
732+ while (std::getline (is, line)) {
733+ // strip \r (for the case of windows line terminators on posix)
734+ if (line.length () > 0 && *line.rbegin () == ' \r ' )
735+ line.erase (line.length ()-1 , 1 );
736+ stripTrailingLineComments (&line);
737+ pLines->push_back (line);
738+ }
739+ }
740+
741+ // parse the local includes from the passed lines
742+ std::vector<FileInfo> parseLocalIncludes (
743+ const std::string& sourceFile) {
744+
745+ // import R functions
746+ Rcpp::Environment baseEnv = Rcpp::Environment::base_env ();
747+ Rcpp::Function dirname = baseEnv[" dirname" ];
748+ Rcpp::Function filepath = baseEnv[" file.path" ];
749+ Rcpp::Function normalizePath = baseEnv[" normalizePath" ];
750+ Rcpp::Function fileExists = baseEnv[" file.exists" ];
751+
752+ // get the path to the source file's directory
753+ Rcpp::CharacterVector sourceDir = dirname (sourceFile);
754+
755+ // read the source file into a buffer
756+ std::stringstream buffer;
757+ readFile (sourceFile, buffer);
758+
759+ // Now read into a list of strings (which we can pass to regexec)
760+ // First read into a std::deque (which will handle lots of append
761+ // operations efficiently) then copy into an R chracter vector
762+ std::deque<std::string> lines;
763+ readLines (buffer, &lines);
764+ Rcpp::CharacterVector linesVector = Rcpp::wrap (lines);
765+
766+ // look for local includes
767+ Rcpp::List matches = regexMatches (
768+ linesVector, " ^\\ s*#include\\ s*\" ([^\" ]+)\"\\ s*$" );
769+
770+ // accumulate local includes (skip commented sections)
771+ CommentState commentState;
772+ std::vector<FileInfo> localIncludes;
773+ for (int i = 0 ; i<matches.size (); i++) {
774+ std::string line = lines[i];
775+ commentState.submitLine (line);
776+ if (!commentState.inComment ()) {
777+ // get the match
778+ const Rcpp::CharacterVector match = matches[i];
779+ if (match.size () == 2 ) {
780+ // compose a full file path for the match
781+ Rcpp::CharacterVector include =
782+ filepath (sourceDir, std::string (match[1 ]));
783+ // if it exists then normalize and add to our list
784+ if (fileExists (include)) {
785+ include = normalizePath (include);
786+ localIncludes.push_back (
787+ FileInfo (Rcpp::as<std::string>(include)));
788+ }
789+ }
790+ }
791+ }
792+
793+ // look for local includes recursively (make a copy of the local
794+ // includes first so we can mutate it during iteration)
795+ std::vector<FileInfo> localIncludesCopy = localIncludes;
796+ for (size_t i = 0 ; i<localIncludesCopy.size (); i++) {
797+ FileInfo include = localIncludesCopy[i];
798+ std::vector<FileInfo> includes = parseLocalIncludes (
799+ include.path ());
800+ std::copy (includes.begin (),
801+ includes.end (),
802+ std::back_inserter (localIncludes));
803+ }
804+
805+ // remove duplicates
806+ std::sort (localIncludes.begin (), localIncludes.end ());
807+ std::vector<FileInfo>::iterator end =
808+ std::unique (localIncludes.begin (), localIncludes.end ());
809+ localIncludes.erase (end, localIncludes.end ());
810+
811+ // return includes
812+ return localIncludes;
813+ }
814+
693815 // Parse embedded R code chunks from a file (receives the lines of the
694816 // file as a CharcterVector for using with regexec and as a standard
695817 // stl vector for traversal/insepection)
@@ -860,18 +982,16 @@ namespace attributes {
860982 }
861983
862984 // Parse the attributes from a source file
863- SourceFileAttributesParser::SourceFileAttributesParser
864- (const std::string& sourceFile)
985+ SourceFileAttributesParser::SourceFileAttributesParser (
986+ const std::string& sourceFile,
987+ bool localIncludes)
865988 : sourceFile_(sourceFile)
866989 {
867990 // First read the entire file into a std::stringstream so we can check
868991 // it for attributes (we don't want to do any of our more expensive
869992 // processing steps if there are no attributes to parse)
870- std::ifstream ifs (sourceFile_.c_str ());
871- if (ifs.fail ())
872- throw Rcpp::file_io_error (sourceFile_);
873993 std::stringstream buffer;
874- buffer << ifs. rdbuf ( );
994+ readFile (sourceFile_, buffer );
875995 std::string contents = buffer.str ();
876996
877997 // Check for attribute signature
@@ -881,15 +1001,8 @@ namespace attributes {
8811001 // Now read into a list of strings (which we can pass to regexec)
8821002 // First read into a std::deque (which will handle lots of append
8831003 // operations efficiently) then copy into an R chracter vector
884- std::string line;
8851004 std::deque<std::string> lines;
886- while (std::getline (buffer, line)) {
887- // strip \r (for the case of windows line terminators on posix)
888- if (line.length () > 0 && *line.rbegin () == ' \r ' )
889- line.erase (line.length ()-1 , 1 );
890- stripTrailingLineComments (&line);
891- lines.push_back (line);
892- }
1005+ readLines (buffer, &lines);
8931006 lines_ = Rcpp::wrap (lines);
8941007
8951008 // Scan for attributes
@@ -965,6 +1078,10 @@ namespace attributes {
9651078
9661079 // Parse embedded R
9671080 embeddedR_ = parseEmbeddedR (lines_, lines);
1081+
1082+ // Recursively parse local includes
1083+ if (localIncludes)
1084+ localIncludes_ = parseLocalIncludes (sourceFile);
9681085 }
9691086 }
9701087
@@ -2587,6 +2704,12 @@ namespace {
25872704 if (!FileInfo (dynlibPath ()).exists ())
25882705 return true ;
25892706
2707+ // variation in local includes means we're dirty
2708+ std::vector<FileInfo> localIncludes = parseLocalIncludes (
2709+ cppSourcePath_);
2710+ if (localIncludes != localIncludes_)
2711+ return true ;
2712+
25902713 // not dirty
25912714 return false ;
25922715 }
@@ -2602,7 +2725,7 @@ namespace {
26022725 filecopy (cppSourcePath_, generatedCppSourcePath (), true );
26032726
26042727 // parse attributes
2605- SourceFileAttributesParser sourceAttributes (cppSourcePath_);
2728+ SourceFileAttributesParser sourceAttributes (cppSourcePath_, true );
26062729
26072730 // generate cpp for attributes and append them
26082731 std::ostringstream ostr;
@@ -2664,6 +2787,9 @@ namespace {
26642787
26652788 // capture embededded R
26662789 embeddedR_ = sourceAttributes.embeddedR ();
2790+
2791+ // capture local includes
2792+ localIncludes_ = sourceAttributes.localIncludes ();
26672793 }
26682794
26692795 const std::string& contextId () const {
@@ -2793,6 +2919,7 @@ namespace {
27932919 std::vector<std::string> depends_;
27942920 std::vector<std::string> plugins_;
27952921 std::vector<std::string> embeddedR_;
2922+ std::vector<FileInfo> localIncludes_;
27962923 };
27972924
27982925 // Dynlib cache that allows lookup by either file path or code contents
@@ -2989,7 +3116,7 @@ BEGIN_RCPP
29893116
29903117 // parse file (continue if there is no generator output)
29913118 std::string cppFile = cppFiles[i];
2992- SourceFileAttributesParser attributes (cppFile);
3119+ SourceFileAttributesParser attributes (cppFile, false );
29933120 if (!attributes.hasGeneratorOutput ())
29943121 continue ;
29953122
0 commit comments