Skip to content
Snippets Groups Projects
monad.cpp 12.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • toole1's avatar
    toole1 committed
    // monad
    // For illinois.edu CS 225 spring 2011
    // By Jack Toole
    
    #include "monad.h"
    
    namespace monad
    {
    void find_base_dir(const char * argv0);
    
    toole1's avatar
    toole1 committed
    void processArgs(int argc, const char * const * argv);
    
    toole1's avatar
    toole1 committed
    void copyRequiredFiles();
    void getLibs(const vector<string> & libs);
    string updateFolder(const string & folder, bool link);
    string getFolder(const string & folder, bool link);
    
    toole1's avatar
    toole1 committed
    void importFiles(const string & preservedFolder, const string & theSourceFolder,
    
    toole1's avatar
    toole1 committed
                     const string & destFolder, const vector<string> & files);
    void exec_command(const string & command);
    
    
    toole1's avatar
    toole1 committed
    string assignment_base;
    int8_t mp_part;
    const int8_t no_mp_part = -1;
    
    toole1's avatar
    toole1 committed
    
    string testsFolder;
    string sourceFolder;
    string gradeFolder;
    string tempFolder;
    
    
    toole1's avatar
    toole1 committed
    FileMap config;
    
    toole1's avatar
    toole1 committed
    
    
    toole1's avatar
    toole1 committed
    namespace opts
    {
    bool solution = false;
    
    toole1's avatar
    toole1 committed
    bool clean    = true;
    bool update   = true;
    bool staff    = false;
    bool newtests = false;
    bool provided = false;
    
    toole1's avatar
    toole1 committed
    
    bool verbose  = false;
    bool valgrind = false;
    bool parallel = false;
    
    bool help     = false;
    bool info     = false;
    bool license  = false;
    
    toole1's avatar
    toole1 committed
    #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(&currentTime);
    	timeStructure = localtime(&currentTime);
    	dateAndTime += asctime(timeStructure);
    
    	return dateAndTime;
    }
    
    
    
    
    toole1's avatar
    toole1 committed
    int main(int argc, const char * const * argv)
    
    toole1's avatar
    toole1 committed
    {
    	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);
    
    
    toole1's avatar
    toole1 committed
    	cout << versioninfo::official_name << endl;
    	cout << "Testing " << assignment_base;
    	if (mp_part != no_mp_part) cout << '.' << mp_part;
    	cout << "..." << endl;
    	cout << getRandomQuote() << endl;
    	cout << endl;
    
    toole1's avatar
    toole1 committed
    
    	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";
    
    toole1's avatar
    toole1 committed
    	if (opts::parallel)
    		makestr += " --jobs=4";
    
    	if (!opts::verbose)
    		makestr += " --quiet";
    
    toole1's avatar
    toole1 committed
    	if (opts::optimize)
    		makestr += " OPTIMIZE=on";
    	if (!config["Make Options"].empty())
    		makestr += " " + config["Make Options"][0];
    
    	if (opts::verbose)
    		cout << makestr <<endl;
    
    toole1's avatar
    toole1 committed
    
    	// 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;
    
    toole1's avatar
    toole1 committed
    	const char * verboseflag  = (opts::verbose  ? "--verbose"  : NULL);
    	const char * valgrindflag = (opts::valgrind ? "--valgrind" : NULL);
    	int score = exec("./proxy", verboseflag, valgrindflag);
    
    toole1's avatar
    toole1 committed
    
    	// 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;
    
    toole1's avatar
    toole1 committed
    		output::total_score(score, -1);
    
    toole1's avatar
    toole1 committed
    	}
    
    	return score;
    }
    
    
    
    toole1's avatar
    toole1 committed
    void monad::processArgs(int argc, const char * const * argv)
    
    toole1's avatar
    toole1 committed
    {
        // Create OptionsMap for options and vector for positional arguments:
    
    toole1's avatar
    toole1 committed
    	OptionsParser options;
    
    toole1's avatar
    toole1 committed
    
    	// Add our possible options to our map
    
    toole1's avatar
    toole1 committed
    	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);
    
    toole1's avatar
    toole1 committed
    
    	// Read in options and arguments
    
    toole1's avatar
    toole1 committed
    	vector<string> posargs = options.parse(argc, argv);
    
    toole1's avatar
    toole1 committed
    	
    	// Help
    
    toole1's avatar
    toole1 committed
    	if (opts::help || toLower(assignment) == "help")
    
    toole1's avatar
    toole1 committed
    	{
    
    toole1's avatar
    toole1 committed
    		if (toLower(assignment) == "config")
    			printHelpConfig();
    		else if (toLower(assignment) == "tests")
    			printHelpTests();
    		else
    			printHelp();
    
    toole1's avatar
    toole1 committed
    		exit(0);
    	}
    
    	// Info
    
    toole1's avatar
    toole1 committed
    	if (opts::info)
    
    toole1's avatar
    toole1 committed
    	{
    		printInfo();
    		exit(0);
    	}
    
    toole1's avatar
    toole1 committed
    	
    	// License
    	if (opts::license)
    	{
    		printLicense();
    		exit(0);
    	}
    	
    	// Clean
    	if (toLower(assignment) == "clean")
    	{
    		system("/bin/rm -rf *_grade/ *_tests/ *_newtests/ *_providedtests *_solution/");
    		exit(0);
    	}
    
    toole1's avatar
    toole1 committed
    
    	// Check argument list length
    
    toole1's avatar
    toole1 committed
    	if (assignment == "")
    
    toole1's avatar
    toole1 committed
    	{
    		cout << "Usage: " << argv[0] << " mp1" << endl;
    		cout << "Run \'" << argv[0] << " --help\' for more information" << endl;
    		exit(0);
    	}
    
    	// Find mp/lab name and tests folder
    
    toole1's avatar
    toole1 committed
    	vector<string> splitname = tokenize(assignment, '.');
    	assignment_base = splitname[0];
    
    toole1's avatar
    toole1 committed
    	if (splitname.size() == 1)
    		mp_part = no_mp_part;
    	else
    
    toole1's avatar
    toole1 committed
    		mp_part = lexical_cast<int>(splitname[1].c_str());
    
    toole1's avatar
    toole1 committed
    	
    
    toole1's avatar
    toole1 committed
    	gradeFolder = "./" + assignment_base + "_grade/";
    
    toole1's avatar
    toole1 committed
    	if (!exists(gradeFolder)) opts::clean = true;
    
    	if (opts::clean)
    		tempFolder = "";
    	else
    
    toole1's avatar
    toole1 committed
    		tempFolder  = "./" + assignment_base + "_temp/";
    
    toole1's avatar
    toole1 committed
    
    	// Find source folder (i.e. ../mp1)
    
    toole1's avatar
    toole1 committed
    	if (opts::solution)
    		sourceFolder = updateFolder(assignment_base + "_solution/", false);
    
    toole1's avatar
    toole1 committed
    	else
    
    toole1's avatar
    toole1 committed
    		sourceFolder = getFolder(assignment_base + '/', false);
    
    toole1's avatar
    toole1 committed
    
    	// tests folder
    	if (opts::provided)
    
    toole1's avatar
    toole1 committed
    		testsFolder = updateFolder(assignment_base + "_provided/", false);
    
    toole1's avatar
    toole1 committed
    	else if (opts::newtests)
    
    toole1's avatar
    toole1 committed
    		testsFolder = updateFolder(assignment_base + "_newtests/", false);
    
    toole1's avatar
    toole1 committed
    	else
    
    toole1's avatar
    toole1 committed
    		testsFolder = updateFolder(assignment_base + "_tests/", false);
    
    toole1's avatar
    toole1 committed
    }
    
    
    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"]);
    }
    
    
    
    toole1's avatar
    toole1 committed
    void monad::importFiles(const string & preservedFolder, const string & theSourceFolder,
    
    toole1's avatar
    toole1 committed
                     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);
    
    toole1's avatar
    toole1 committed
    	if (theSourceFolder    != "") assertExists(theSourceFolder, student_error_code);
    
    toole1's avatar
    toole1 committed
    
    	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) &&
    
    toole1's avatar
    toole1 committed
    		    (theSourceFolder == "" || (permissions(preservedFile) & S_IWUSR) == 0))
    
    toole1's avatar
    toole1 committed
    		{
    //!!			cout << "mv " << preservedFile << ' ' << destFile << endl;
    			EXIT_IF_ERROR(rename(preservedFile.c_str(), destFile.c_str()));
    		}
    
    toole1's avatar
    toole1 committed
    		else if (theSourceFolder != "")
    
    toole1's avatar
    toole1 committed
    		{
    			// copy the file from it's source
    
    toole1's avatar
    toole1 committed
    			string sourceFile = theSourceFolder + files[i];
    
    toole1's avatar
    toole1 committed
    			assertExists(sourceFile, student_error_code);
    
    			// Remove hacky call to exec here...
    
    			EXIT_IF_ERROR(exec("cp", "-RL", sourceFile.c_str(), destFile.c_str()) != 0,
    
    toole1's avatar
    toole1 committed
    				"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)
    
    toole1's avatar
    toole1 committed
    		exec(/*-1,*/ "svn","up", get.c_str());
    
    toole1's avatar
    toole1 committed
    	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
    
    toole1's avatar
    toole1 committed
    		int svnstatus = exec(/*-1,*/ "svn","co",svndir.c_str());
    
    toole1's avatar
    toole1 committed
    		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());
    }