编译模块只提供编译功能,所以我们不考虑把用户的代码转换成临时文件的整个过程,假设我们已经拿到了一份代码文件,里面是用户的代码。编译只有两种结果,编译成功和编译失败,出错的时候会将错误信息打印到stder上,我们需要将这段信息还给用户,所以需要输出重定向,生成个一个临时文件用来保存错误信息。同时编译本身这一过程必须是另一个进程或者线程,因为服务器不能卡死。
只负责提供编译功能,通过创建子进程,并进行进程替换,替换成gcc编译程序,运行用户代码。通过execl或者类似函数实现这一功能。 因为这一过程会产生大量临时文件,我们需要对文件和名字路径进行管理,我们需要一个temp文件夹用来存放临时文件。同时因为拿到的只有要处理的文件名,没有后缀我们需要构建出源文件程序名和可执行程序的文件名,还有标准错误的文件名。这个模块因为会经常使用,所以放在公共模块里。在进程替换完成之后,需要对子进程的编译是否成功进行判断,主进程等待到子进程后,可以通过判断是否生成同名的exe来判断子进程的任务是否成功。所以就需要查找文件是否存在的函数。
处理编译错误产生的错误信息,可以创建一个错误文件,并将标准错误重定向到这个文件上。
class Compiler
{
public:
Compiler() {}
~Compiler() {}
//负责编译客户的代码并生成cc文件
static bool Compile(const std::string &file_name)
{
// 创建子进程进行程序替换执行用户的代码主进程继续执行替他逻辑
pid_t pid = fork();
if(pid<0)
{
LOG(ERROR)<<"编译创建子进程失败"<<"\n";
}
else if (pid==0) //子进程执行的逻辑
{
//先打开日志文件并进行输出重定向,以创建文件和只写的方式
umask(0);
//root拥有读写权限,其他只有读权限
//子进程打开的文件描述符不用主动关闭
int _stderr = open(PathUtil::CompilerError(file_name).c_str(),O_CREAT|O_WRONLY,0644);
if(_stderr<0)
{
LOG(ERROR)<<"创建stderr文件失败"<<"\n";
}
//重定向标准错误到_stderr
//拷贝后旧的fd 新的没使用的fd
dup2(_stderr,2);
//日志打开完成进行程序替换
//子进程: 调用编译器,完成对代码的编译工作
//g++ -o target src -std=c++11
execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(), "-D", "COMPILER_ONLINE","-std=c++11", nullptr/*不要忘记*/);
LOG(ERROR) << "启动编译器g++失败" << "\n";
exit(2);
}
//主进程等待回收子进程
else
{
waitpid(pid,nullptr,0);
//通过查看可执行文件是否存在判断编译是否成功
if(FileUtil::IsFileExists(PathUtil::Exe(file_name)))
{
LOG(INFO)<<"编译成功:"<<PathUtil::Src(file_name)<<'\n';
return true;
}
}
LOG(INFO)<<"编译失败,无可用执行程序:"<<PathUtil::Src(file_name)<<'\n';
return false;
}
};