Skip to content

Commit 7a4ce00

Browse files
committed
scan local includes recursively and invalidate sourcecpp cache if a local include changes
1 parent c8590c0 commit 7a4ce00

File tree

6 files changed

+163
-22
lines changed

6 files changed

+163
-22
lines changed

ChangeLog

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
2015-02-14 JJ Allaire <[email protected]>
2+
3+
* src/attributes.cpp: Allow includes of local files
4+
(e.g. #include "foo.hpp") in sourceCpp
5+
16
2015-02-13 JJ Allaire <[email protected]>
27

38
* src/attributes.cpp: Allow 'R' to come immediately after '***'

TODO

-5
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@ Syntactic sugar
8080

8181
rnt : idem
8282

83-
Attributes
84-
85-
o Consider allowing local includes in sourceCpp (but compilation cache
86-
must pay mind to any local includes)
87-
8883
Testing
8984

9085
o all r* functions : rnorm, etc ...

inst/NEWS.Rd

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
\itemize{
2020
\item The \code{pkg_types.h} file is now included in \code{RcppExports.cpp}
2121
if it is present in either the \code{inst/include} or \code{src}.
22+
\item \code{sourceCpp} was modified to allow includes of local files
23+
(e.g. #include "foo.hpp").
2224
\item The generated attributes code was simplified with respect to
2325
\code{RNGScope} and now uses \code{RObject} and its destructor rather than \code{SEXP}
2426
protect/unprotect.

inst/unitTests/cpp/attributes.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include <Rcpp.h>
22
using namespace Rcpp;
33

4+
// include a dummy header file to test support for local includes
5+
#include "attributes.hpp"
6+
47
//' @param foo // don't do anything to this
58
//' // or this
69
//' @param bar " // or this guy "

inst/unitTests/cpp/attributes.hpp

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
#ifndef __RCPP_UNITTESTS_ATTRIBUTES__
3+
#define __RCPP_UNITTESTS_ATTRIBUTES__
4+
5+
/*
6+
* dummy header used to test that local includes for sourceCpp are working
7+
*/
8+
9+
#endif // __RCPP_UNITTESTS_ATTRIBUTES__

src/attributes.cpp

+144-17
Original file line numberDiff line numberDiff line change
@@ -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);
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+
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

Comments
 (0)