-
toole1 authored
git-svn-id: https://subversion.cs.illinois.edu/svn/cs225@5313 6fbd10e7-183d-0410-a318-cb416676e4f2
toole1 authoredgit-svn-id: https://subversion.cs.illinois.edu/svn/cs225@5313 6fbd10e7-183d-0410-a318-cb416676e4f2
monad.cpp 12.13 KiB
// monad
// For illinois.edu CS 225 spring 2011
// By Jack Toole
#include "monad.h"
namespace monad
{
void find_base_dir(const char * argv0);
void processArgs(int argc, const char * const * argv);
void copyRequiredFiles();
void getLibs(const vector<string> & libs);
string updateFolder(const string & folder, bool link);
string getFolder(const string & folder, bool link);
void importFiles(const string & preservedFolder, const string & theSourceFolder,
const string & destFolder, const vector<string> & files);
void exec_command(const string & command);
string assignment_base;
int8_t mp_part;
const int8_t no_mp_part = -1;
string testsFolder;
string sourceFolder;
string gradeFolder;
string tempFolder;
FileMap config;
namespace opts
{
bool solution = false;
bool clean = true;
bool update = true;
bool staff = false;
bool newtests = false;
bool provided = false;
bool verbose = false;
bool valgrind = false;
bool parallel = false;
bool help = false;
bool info = false;
bool license = false;
#if OPTIMIZE
bool optimize = true;
#else
bool optimize = false;
#endif
}
}
string kyleGetDate() {
string dateAndTime = "Current Date and Time: ";
time_t currentTime;
struct tm * timeStructure;
time(¤tTime);
timeStructure = localtime(¤tTime);
dateAndTime += asctime(timeStructure);
return dateAndTime;
}
int main(int argc, const char * const * argv)
{
using namespace monad;
output::set_error_message();
// Find monad/ directory
find_base_dir(argv[0]);
// Read in local config settings.
// Necessary to do this early for [SVN Root] url
readConfig("./", config);
processArgs(argc, argv);
cout << versioninfo::official_name << endl;
cout << "Testing " << assignment_base;
if (mp_part != no_mp_part) cout << '.' << (int)mp_part;
cout << "..." << endl;
cout << getRandomQuote() << endl;
cout << endl;
cout << "Setting up test environment..." << endl;
// Read in test-specific config settings
if (mp_part == no_mp_part)
readConfig(testsFolder, config);
else
readConfig(testsFolder, config, to_string((int)mp_part));
copyRequiredFiles();
// Sleep for a second to avoid clock skew warnings
// This cummulatively adds about 5 minutes to grade each mp,
// but with the benefit of avoiding newsgroup posts
// CHANGED: Judging by previous emails, the time needed for
// this would vary significantly. not sure about solution
// sleep(1);
output::header("Compiling");
chdir(gradeFolder.c_str());
// #define MP_PART_NUMBER in proxy
ofstream mp_part_file;
mp_part_file.open("_mp_part_number.h");
if (mp_part == no_mp_part)
mp_part_file << "#define MP_PART_NUMBER NO_MP_PART" << endl;
else
mp_part_file << "#define MP_PART_NUMBER " << (int)mp_part << endl;
mp_part_file.close();
// run [Pre-Make Commands] config header
const vector<string> & processing_commands = config["Pre-Make Commands"];
for (size_t i = 0; i < processing_commands.size(); i++)
exec_command(processing_commands[i]);
string makestr = "/usr/bin/make --warn-undefined-variables -Wfatal-errors";
if (opts::parallel)
makestr += " --jobs=4";
if (!opts::verbose)
makestr += " --quiet";
if (opts::optimize)
makestr += " OPTIMIZE=on";
if (!config["Make Options"].empty())
makestr += " " + config["Make Options"][0];
if (opts::verbose)
cout << makestr <<endl;
// Compile with make
system(makestr.c_str()); // yes, system is insecure if the user has control
// over config.ini. But students don't.
// TODO (toole1): Yeah but this leaves us open to
// aliasing issues, or forces us to specify make's
// path. Ugly either way.
// TODO exec("make", "--quiet", "--warn-undefined-variables",
cout << endl << endl;
const char * verboseflag = (opts::verbose ? "--verbose" : NULL);
const char * valgrindflag = (opts::valgrind ? "--valgrind" : NULL);
int score = exec("./proxy", verboseflag, valgrindflag);
// TODO (toole1): this causes weird output when scores are like 200
if (score < 0)
{
output::header("Running tests");
cout << "Could not execute test cases" << endl << endl;
score = 0;
cout << endl;
output::total_score(score, -1);
}
return score;
}
void monad::processArgs(int argc, const char * const * argv)
{
// Create OptionsMap for options and vector for positional arguments:
OptionsParser options;
// Add our possible options to our map
options.addOption("solution", opts::solution);
options.addOption("newtests", opts::newtests);
options.addOption("provided", opts::provided);
options.addOption("clean", opts::clean);
options.addOption("update", opts::update);
options.addOption("staff", opts::staff);
options.addOption("optimize", opts::optimize);
options.addOption("verbose", opts::verbose);
options.addOption("valgrind", opts::valgrind);
options.addOption("parallel", opts::parallel);
options.addOption("help", opts::help);
options.addOption("h", opts::help);
options.addOption("info", opts::info);
options.addOption("version", opts::info);
options.addOption("v", opts::info);
options.addOption("license", opts::license);
// Add arguments
string assignment = "";
options.addArg(assignment);
// Read in options and arguments
vector<string> posargs = options.parse(argc, argv);
// Help
if (opts::help || toLower(assignment) == "help")
{
if (toLower(assignment) == "config")
printHelpConfig();
else if (toLower(assignment) == "tests")
printHelpTests();
else
printHelp();
exit(0);
}
// Info
if (opts::info)
{
printInfo();
exit(0);
}
// License
if (opts::license)
{
printLicense();
exit(0);
}
// Clean
if (toLower(assignment) == "clean")
{
system("/bin/rm -rf *_grade/ *_tests/ *_newtests/ *_providedtests *_solution/");
exit(0);
}
// Check argument list length
if (assignment == "")
{
cout << "Usage: " << argv[0] << " mp1" << endl;
cout << "Run \'" << argv[0] << " --help\' for more information" << endl;
exit(0);
}
// Find mp/lab name and tests folder
vector<string> splitname = tokenize(assignment, '.');
assignment_base = splitname[0];
if (splitname.size() == 1)
mp_part = no_mp_part;
else
mp_part = lexical_cast<int>(splitname[1].c_str());
gradeFolder = "./" + assignment_base + "_grade/";
if (!exists(gradeFolder)) opts::clean = true;
if (opts::clean)
tempFolder = "";
else
tempFolder = "./" + assignment_base + "_temp/";
// Find source folder (i.e. ../mp1)
if (opts::solution)
sourceFolder = updateFolder(assignment_base + "_solution/", false);
else
sourceFolder = getFolder(assignment_base + '/', false);
// tests folder
if (opts::provided)
testsFolder = updateFolder(assignment_base + "_provided/", false);
else if (opts::newtests)
testsFolder = updateFolder(assignment_base + "_newtests/", false);
else
testsFolder = updateFolder(assignment_base + "_tests/", false);
}
void monad::find_base_dir(const char * argv0)
{
EXIT_IF_ERROR(argv0 == NULL);
size_t argv0len = strlen(argv0);
char * dir = new char[argv0len + 1];
strncpy(dir, argv0, argv0len);
dir[argv0len] = '\0';
size_t i = argv0len + 1;
do
{
i--;
if (argv0[i] == '/') break;
} while (i != 0);
// Change directory
if (i != 0)
{
dir[i] = '\0';
EXIT_IF_ERROR(chdir(dir));
}
delete [] dir;
// Ensure the dir is correct
if (!exists("./.monadid"))
{
cerr << "Could not find monad directory. "
"Please run ./monad from the directory it is located in."
<< endl;
exit(-1);
}
}
void monad::copyRequiredFiles()
{
// Clear out the temp testing folder
if (opts::clean)
forceRemoveDir(gradeFolder);
else
{
forceRemoveDir(tempFolder);
EXIT_IF_ERROR(rename(gradeFolder.c_str(), tempFolder.c_str()));
}
exec("/bin/mkdir",gradeFolder.c_str());
// Copy and link appropriate files - parsed from config.ini
importFiles(tempFolder, "./", gradeFolder, config["Monad Files"]);
importFiles(tempFolder, testsFolder, gradeFolder, config["Testing Files"]);
importFiles("", sourceFolder, gradeFolder, config["Required Files"]);
importFiles(tempFolder, "", gradeFolder, config["Preserved Files"]);
forceRemoveDir(tempFolder);
getLibs(config["Libraries"]);
}
void monad::importFiles(const string & preservedFolder, const string & theSourceFolder,
const string & destFolder, const vector<string> & files)
{
// 0 for student errors for missing Required Files dir
int student_error_code = ((preservedFolder == "") ? 0 : -1);
assertExists(destFolder);
if (preservedFolder != "") assertExists(preservedFolder);
if (theSourceFolder != "") assertExists(theSourceFolder, student_error_code);
for (size_t i = 0; i < files.size(); i++)
{
string preservedFile = preservedFolder + files[i];
string destFile = destFolder + files[i];
// Move the file from it's preservation instance
if (preservedFolder != "" && exists(preservedFile) &&
(theSourceFolder == "" || (permissions(preservedFile) & S_IWUSR) == 0))
{
//!! cout << "mv " << preservedFile << ' ' << destFile << endl;
EXIT_IF_ERROR(rename(preservedFile.c_str(), destFile.c_str()));
}
else if (theSourceFolder != "")
{
// copy the file from it's source
string sourceFile = theSourceFolder + files[i];
assertExists(sourceFile, student_error_code);
// Remove hacky call to exec here...
EXIT_IF_ERROR(exec("cp", "-RL", sourceFile.c_str(), destFile.c_str()) != 0,
"cp " + sourceFile + " " + destFile + " failed");
}
else continue;
// TODO (toole1) Protect files. This is hacky and should be in util
// TODO (toole1) this should also be AFTER Pre-Make Commands
// EXIT_IF_ERROR(chmod(destFile.c_str(),
// S_IRUSR | (permissions(destFile) & S_IXUSR)) != 0);
}
}
void monad::getLibs(const vector<string> & libs)
{
for (size_t lib_i = 0; lib_i < libs.size(); lib_i++)
{
string folder = updateFolder(libs[lib_i], false);
protectDir(folder);
chdir(gradeFolder.c_str());
system(("/bin/ln -s ../"+folder+"* ./").c_str());
chdir("..");
}
}
string monad::updateFolder(const string & folder, bool link)
{
string get = getFolder(folder, link);
if (opts::update)
exec(/*-1,*/ "svn","up", get.c_str());
return get;
}
string monad::getFolder(const string & folder, bool link)
{
// Look in the current folder
string target = "./" + folder;
if (exists(target + "/"))
return target + "/";
// Look in the parent folder
string source = "../" + folder;
if (exists(source + "/"))
{
if (!link) return source + "/";
EXIT_IF_ERROR(symlink(source.c_str(), target.c_str()) != 0);
return target + "/";
}
// Maybe it actually *is* the parent folder
string cwd = getcwdstr();
size_t cwd_i = findNthLast(cwd, '/', 2);
if (cwd_i != string::npos &&
cwd.length() - cwd_i > folder.length() &&
cwd.compare(cwd_i, folder.length(), folder) == 0)
{
if (!link) return "../";
EXIT_IF_ERROR(symlink("../", target.c_str()) != 0);
return target + "/";
}
// Look two directories up and over - why not? If the parent folder is
// the target source folder for the mp/lab, then the tests or libs
// may be two up and over
source = "../../" + folder;
if (exists(source + "/"))
{
if (!link) return source + "/";
EXIT_IF_ERROR(symlink(source.c_str(), target.c_str()) != 0);
return target + "/";
}
// Check Subversion
const char * svn_config_name = NULL;
const char * svn_subdir = NULL;
if (!opts::staff)
{
svn_config_name = "SVN Root";
svn_subdir = "/_public/";
}
else
{
svn_config_name = "Staff SVN";
svn_subdir = "/_current/";
}
if (!config[svn_config_name].empty())
{
string svndir = config[svn_config_name][0] + svn_subdir + folder;
// TODO (toole1): Won't work if user needs to type password
int svnstatus = exec(/*-1,*/ "svn","co",svndir.c_str());
if (svnstatus == 0) return target + "/";
}
cerr << "Error: " << folder << " not found." << endl;
exit(-1);
return "";
}
// Execute a monad or command line command
void monad::exec_command(const string & command)
{
vector<string> args = tokenize(command, ' ');
// Allow processing of special internals
if (args[0] == "rename_main")
{
EXIT_IF_ERROR(args.size() != 3, "rename_main must take 2 arguments: a file and a new name");
rename_main(args[1], args[2]);
return;
}
#if DEBUG
cout << "Pre-Make Command: " << command << endl;
#endif
system(command.c_str());
}