@@ -53,6 +53,25 @@ namespace attributes {
53
53
std::string path () const { return path_; }
54
54
bool exists () const { return exists_; }
55
55
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
+ }
56
75
57
76
private:
58
77
std::string path_;
@@ -355,7 +374,8 @@ namespace attributes {
355
374
// Class used to parse and return attribute information from a source file
356
375
class SourceFileAttributesParser : public SourceFileAttributes {
357
376
public:
358
- explicit SourceFileAttributesParser (const std::string& sourceFile);
377
+ explicit SourceFileAttributesParser (const std::string& sourceFile,
378
+ bool localIncludes);
359
379
360
380
private:
361
381
// prohibit copying
@@ -405,6 +425,11 @@ namespace attributes {
405
425
const std::vector<std::string>& embeddedR () const {
406
426
return embeddedR_;
407
427
}
428
+
429
+ // Get local includes
430
+ const std::vector<FileInfo>& localIncludes () const {
431
+ return localIncludes_;
432
+ };
408
433
409
434
private:
410
435
@@ -437,6 +462,7 @@ namespace attributes {
437
462
FunctionMap functionMap_ ;
438
463
std::vector<std::string> modules_;
439
464
std::vector<std::string> embeddedR_;
465
+ std::vector<FileInfo> localIncludes_;
440
466
std::vector<std::vector<std::string> > roxygenChunks_;
441
467
std::vector<std::string> roxygenBuffer_;
442
468
};
@@ -690,6 +716,102 @@ namespace attributes {
690
716
return matches;
691
717
}
692
718
719
+ template <typename Stream>
720
+ void readFile (const std::string& file, Stream& os) {
721
+ std::ifstream ifs (file);
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>::const_iterator end =
808
+ std::unique (localIncludes.begin (), localIncludes.end ());
809
+ localIncludes.erase (end, localIncludes.end ());
810
+
811
+ // return includes
812
+ return localIncludes;
813
+ }
814
+
693
815
// Parse embedded R code chunks from a file (receives the lines of the
694
816
// file as a CharcterVector for using with regexec and as a standard
695
817
// stl vector for traversal/insepection)
@@ -860,18 +982,16 @@ namespace attributes {
860
982
}
861
983
862
984
// 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)
865
988
: sourceFile_(sourceFile)
866
989
{
867
990
// First read the entire file into a std::stringstream so we can check
868
991
// it for attributes (we don't want to do any of our more expensive
869
992
// 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_);
873
993
std::stringstream buffer;
874
- buffer << ifs. rdbuf ( );
994
+ readFile (sourceFile_, buffer );
875
995
std::string contents = buffer.str ();
876
996
877
997
// Check for attribute signature
@@ -881,15 +1001,8 @@ namespace attributes {
881
1001
// Now read into a list of strings (which we can pass to regexec)
882
1002
// First read into a std::deque (which will handle lots of append
883
1003
// operations efficiently) then copy into an R chracter vector
884
- std::string line;
885
1004
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);
893
1006
lines_ = Rcpp::wrap (lines);
894
1007
895
1008
// Scan for attributes
@@ -965,6 +1078,10 @@ namespace attributes {
965
1078
966
1079
// Parse embedded R
967
1080
embeddedR_ = parseEmbeddedR (lines_, lines);
1081
+
1082
+ // Recursively parse local includes
1083
+ if (localIncludes)
1084
+ localIncludes_ = parseLocalIncludes (sourceFile);
968
1085
}
969
1086
}
970
1087
@@ -2587,6 +2704,12 @@ namespace {
2587
2704
if (!FileInfo (dynlibPath ()).exists ())
2588
2705
return true ;
2589
2706
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
+
2590
2713
// not dirty
2591
2714
return false ;
2592
2715
}
@@ -2602,7 +2725,7 @@ namespace {
2602
2725
filecopy (cppSourcePath_, generatedCppSourcePath (), true );
2603
2726
2604
2727
// parse attributes
2605
- SourceFileAttributesParser sourceAttributes (cppSourcePath_);
2728
+ SourceFileAttributesParser sourceAttributes (cppSourcePath_, true );
2606
2729
2607
2730
// generate cpp for attributes and append them
2608
2731
std::ostringstream ostr;
@@ -2664,6 +2787,9 @@ namespace {
2664
2787
2665
2788
// capture embededded R
2666
2789
embeddedR_ = sourceAttributes.embeddedR ();
2790
+
2791
+ // capture local includes
2792
+ localIncludes_ = sourceAttributes.localIncludes ();
2667
2793
}
2668
2794
2669
2795
const std::string& contextId () const {
@@ -2793,6 +2919,7 @@ namespace {
2793
2919
std::vector<std::string> depends_;
2794
2920
std::vector<std::string> plugins_;
2795
2921
std::vector<std::string> embeddedR_;
2922
+ std::vector<FileInfo> localIncludes_;
2796
2923
};
2797
2924
2798
2925
// Dynlib cache that allows lookup by either file path or code contents
@@ -2989,7 +3116,7 @@ BEGIN_RCPP
2989
3116
2990
3117
// parse file (continue if there is no generator output)
2991
3118
std::string cppFile = cppFiles[i];
2992
- SourceFileAttributesParser attributes (cppFile);
3119
+ SourceFileAttributesParser attributes (cppFile, false );
2993
3120
if (!attributes.hasGeneratorOutput ())
2994
3121
continue ;
2995
3122
0 commit comments