#include #include #include #include #include #include namespace { bool IsOlderNewer( const std::string& o, const std::string& n) { struct stat os; struct stat ns; if ( stat( o.c_str(), &os)) return true; //throw std::runtime_error( std::string("stat failed for ") + o); if ( stat( n.c_str(), &ns)) throw std::runtime_error( std::string("stat failed for ") + n); return os.st_mtime < ns.st_mtime; } std::string Leafname( const std::string& name) { std::string::size_type lastslash = name.find_last_of( '/'); if ( lastslash==std::string::npos) return name; else return name.substr( lastslash+1); } struct Tempfile /* Provides a scoped temporary file. File's filename is obtained from tmpnam or as a constructor parameter. The file is deleted by the destructor. Clients use the public `stream' member to write to the file. */ { Tempfile() { Open( tmpnam( NULL)); } explicit Tempfile( const std::string& filename) { Open( filename); } operator std::ostream& () { return this->stream; } // doesn't seem to work... ~Tempfile() { Close(); } std::ofstream stream; std::string filename; private: void Open( const std::string& newfilename) { Close(); this->filename = newfilename; this->stream.open( this->filename.c_str()); if ( !this->stream) throw std::runtime_error( std::string( "Couldn't open temp file ") + this->filename); } void Close() { this->stream.close(); if ( !this->filename.empty()) remove( this->filename.c_str()); } }; void Build( const std::string& executablename, const std::string& sourcename) /* Makes a temporary copy of input file and compiles it. Reads special lines starting with `#!' to find how to build from . Default build is with g++, but if a line `#!-build ...' is found, it is taken as the build command, to which is appended ` -o '. */ { std::ifstream in( sourcename.c_str()); if ( !in) throw std::runtime_error( std::string( "Can't open for reading: ") + sourcename); std::string tempsourcename = tmpnam( NULL); // append leaf of sourcename so that the suffix is the same. tempsourcename += Leafname( sourcename); Tempfile out( tempsourcename); std::string build = "g++ -W -Wall -x c++"; /* This is overwritten by any `#!-build ...' line. */ /* Copy the source into temporary file, ommiting `#! ... lines' and inserting `#line ...' when appropriate. */ for ( int linenum=1; ; ++linenum) { std::string line; std::getline( in, line); if ( !in) break; if ( line.substr( 0, 2)=="#!") { if ( linenum==1) { /* Don't copy the first line. */ } else if ( line.substr( 2, 6)=="-build") { if ( build=="") build = line.substr( 8); else throw std::runtime_error( "Two -build directives found"); } else { throw std::runtime_error( std::string( "Unrecognised #! line: ") + line); } out.stream << "#line " << linenum+1 << " \"" << sourcename << "\"\n"; } else { out.stream << line << "\n"; } } if ( !out.stream) throw std::runtime_error( "Error writing temp file"); out.stream.close(); std::string command = build + " " + tempsourcename + " -o " + executablename; std::cout << "runc++2: " << command << "\n"; if ( system( command.c_str())) throw std::runtime_error( "build failed"); } } int main( int argc, char** argv) { try { //std::cerr << "runc++2.c++ called\n"; if ( argc<2) throw std::runtime_error( "Epecting at least one argument"); const char* infilename = argv[1]; std::string executable = std::string( infilename) + ".exe"; /* Could also append ID of host system to allow use of same script by different OS's on shared file systems. */ if ( IsOlderNewer( executable, infilename)) { //std::cerr << "Executable needs rebuilding\n"; Build( executable, infilename); } else { //std::cerr << "Executable is up to date\n"; } std::string command = executable; for ( int i=2; i