一、编译服务框架

3L4C005SGLF2CH(RG59U0LH.jpg

二、具体实现

首先是编译模块,封装Compiler类,提供Compile函数接口,具体编译流程如下:

  1. 根据传入文件名,对其进行编译,这里需要实现一个工具类
  2. 工具类,对文件名进行拼接,待编译的文件放在compile_server/temp文件下,例如test.cpp
  3. 拼接目标有源文件拼接:test->./temp/test.cpp
  4. 可执行程序拼接:test->./temp/test.exe
  5. 错误信息文件拼接:test->./temp/test.stderr
  6. 正式逻辑开始,首先创建子进程,子进程进行编译服务
  7. 通过程序替换:execlp函数,执行g++编译源文件,生成text.exe
  8. g++编译可能成功,可能失败,如果失败,会通过stderr打印,这里对stderr进行重定向到指定文件(dup2函数):./temp/test.stderr
  9. 父进程对子进程编译结果进行判断,这里只需要判断./temp/test.exe函数是否存在,存在就是编译成功
  10. 在工具类中FileUtil类提供接口IsFileExists判断文件是否存在,其中调用stat函数

1、compiler.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>


#include "../common/util.hpp"
#include "../common/log.hpp"

//只提供编译服务
namespace ns_compiler
{
//引入路径拼接功能
using namespace ns_util;
using namespace ns_log;
class Compiler
{
public:
Compiler(){};
~Compiler(){}

static bool Compile(const std::string& file_name)
{
pid_t pid = fork();
if(pid<0)
{
LOG(ERROR)<<"内部错误,创建子进程失败"<<"\n";
return false;
}
else if(pid == 0)
{
//子进程
umask(0);
int _stderr = open(PathUtil::Stderr(file_name).c_str(), O_CREAT | O_WRONLY, 0644); //110 100 100
if(_stderr<0)
{
LOG(WARNING)<<"没有成功形成stderr文件"<<"\n";
exit(1);
}
dup2(_stderr, 2);
execlp("g++","g++", "-o", PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(),"-std=c++11", NULL);
// 执行完成后,这里判断有没有形成.exe的可执行程序,如果形成,说明编译成功
// 如果没有形成,需要将错误信息重定向到文件中
LOG(ERROR)<<"g++编译失败,可能是参数错误"<<"\n";
exit(1);
}
else
{
waitpid(pid, nullptr,0);
if(FileUtil::IsFileExists(PathUtil::Exe(file_name).c_str()))
{
LOG(INFO)<<PathUtil::Src(file_name)<<"编译成功"<<"\n";
return true;
}
LOG(ERROR)<<"编译失败,没有形成可执行程序"<<"\n";
return false;
}
}
};
}

2、util.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>

namespace ns_util
{
class TimeUtil
{
public:
static std::string GetTimeStamp()
{
struct timeval _time;
gettimeofday(&_time, nullptr);
return std::to_string(_time.tv_sec);
}
};

const std::string temp_path = "./temp/";

class PathUtil
{
public:
static std::string AddSuffix(const std::string& file_name, const std::string& suffix)
{
std::string path_name = temp_path+file_name;
path_name+=suffix;
return path_name;
}
// 构建源文件路径+后缀的完整文件名
// ./temp/1234.cpp
static std::string Src(const std::string& file_name)
{
return AddSuffix(file_name, ".cpp");
}

//构建可执行程序的完整路径+后缀名
//1234->./temp/1234.exe
static std::string Exe(const std::string& file_name)
{
return AddSuffix(file_name, ".exe");
}

//构建该程序对应的标准错误的完整路径+后缀名
//1234->./temp/1234.stderr
static std::string Stderr(const std::string& file_name)
{
return AddSuffix(file_name, ".stderr");
}
};

class FileUtil
{
public:
static bool IsFileExists(const std::string& file_name)
{
struct stat buf;
if(stat(file_name.c_str(), &buf)==0)
{
return true;
}
return false;
}
};


}

3、log.hpp

为了更方便调试和打印信息,封装一个日志函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#pragma once
#include <iostream>
#include <string>
#include "util.hpp"

namespace ns_log
{
using namespace ns_util;

enum{
INFO,
DEBUG,
WARNING,
ERROR,
FATAL
};

inline std::ostream& Log(const std::string& level, const std::string& file_name, int line)
{
//日志等级
std::string message = "[";
message+=level;
message+="]";
//报错文件名
message+="[";
message+=file_name;
message+="]";

//报错行数
message+="[";
message+=std::to_string(line);
message+="]";

//报错时间戳
message+="[";
message+=TimeUtil::GetTimeStamp();
message+="]";

std::cout<<message;
return std::cout;
}

#define LOG(level) Log(#level, __FILE__, __LINE__)
}