P2_SelfBuild
Le 四 05 十一月 2026
这一篇是关于我最近开发的一个stb风格库的介绍。它的名字叫作SelfBuild,顾名思义,就是自己编译的意思,这是一个非常简易的编译框架,灵感来源是Tsoding的Nob.h,但是我觉得还需要写一个nob.c还是不够优雅,于是我就想要一个单文件库。并且我想要实现只要第一次编译后,以后的所有改动都只要再运行一次可执行文件就会被更新到可执行文件中去,也就是自己编译自己,和nob.h的设计比较像。
因为它是单头文件库,所以我们先放出代码,然后再慢慢讲解。
#ifndef SELFBUILD_H
#define SELFBUILD_H
typedef struct{
char* targets[50];
char* links[50];
char* name;
char* compiler;
int target_top;
int links_top;
int debug;
}Sb_cmd;
void sb_add_target(Sb_cmd* cmd,char* target);
void sb_add_link(Sb_cmd* cmd,char* link);
void sb_set_output_name(Sb_cmd* sb_cmd,char* name);
void sb_set_compiler(Sb_cmd* sb_cmd,char* compiler);
void sb_set_debug(Sb_cmd* sb_cmd);
void sb_run(Sb_cmd* sb_cmd);
#endif
#ifdef SELFBUILD_IMPLEMENTATION
#ifndef SELFBUILD_IMPLEMENTATION_INCLUDED
#define SELFBUILD_IMPLEMENTATION_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <glob.h>
#include <sys/stat.h>
void sb_add_target(Sb_cmd* sb_cmd,char* target){
sb_cmd->targets[sb_cmd->target_top] = target;
if (sb_cmd->target_top!=50){
sb_cmd->target_top++;
}
}
void sb_add_link(Sb_cmd* sb_cmd,char* link){
sb_cmd->links[sb_cmd->links_top] = link;
if (sb_cmd->links_top!=50){
sb_cmd->links_top++;
}
}
void sb_set_compiler(Sb_cmd* sb_cmd,char* compiler){
sb_cmd->compiler = compiler;
}
void sb_set_output_name(Sb_cmd* sb_cmd,char* name){
sb_cmd->name = name;
}
void sb_set_debug(Sb_cmd* sb_cmd){
sb_cmd->debug ^= 1;
}
void sb_run(Sb_cmd* sb_cmd){
if (sb_cmd->compiler == NULL){
printf("[SB] need specify a compiler.");
exit(1);
}
if(sb_cmd->targets[0] == NULL){
printf("[SB] need at least one target.");
exit(1);
}
int count = 0;
int need_recompile = 0;
while(sb_cmd->targets[count]!=NULL){
if(strstr(sb_cmd->targets[count],"/")==NULL){
if(access(sb_cmd->targets[count],F_OK)!=0){
return;
}else{
struct stat tar_st;
struct stat exe_st;
stat(sb_cmd->targets[count],&tar_st);
char exe_buf[256];
memset(exe_buf,0x00,sizeof(exe_buf));
readlink("/proc/self/exe",exe_buf,sizeof(exe_buf));
stat(exe_buf,&exe_st);
if(tar_st.st_mtime>exe_st.st_mtime){
need_recompile=1;
}
}
}else{
glob_t glob_buf;
glob(sb_cmd->targets[count],0,NULL,&glob_buf);
if(glob_buf.gl_pathc==0){
globfree(&glob_buf);
return;
}else{
struct stat tar_st;
struct stat exe_st;
char exe_buf[256];
readlink("/proc/self/exe",exe_buf,sizeof(exe_buf));
stat(exe_buf,&exe_st);
for(int i=0;i<glob_buf.gl_pathc;i++){
stat(glob_buf.gl_pathv[i],&tar_st);
if(tar_st.st_mtime>exe_st.st_mtime){
need_recompile=1;
break;
}
}
}
globfree(&glob_buf);
}
count++;
}
int compiler_length = strlen(sb_cmd->compiler)+1;
int debug_length = sb_cmd->debug * 3;
int target_length = 0;
count = 0;
while(sb_cmd->targets[count]!=NULL){
target_length += (strlen(sb_cmd->targets[count])+1);
count++;
}
int name_length = 0;
if(sb_cmd->name!=NULL){
name_length = (strlen(sb_cmd->name)+3);
}
int link_length = 0;
count = 0;
while(sb_cmd->links[count]!=NULL){
link_length += (strlen(sb_cmd->links[count])+3);
count++;
}
int command_length = compiler_length + debug_length + target_length + name_length + link_length + 1;
char* command = malloc(command_length * sizeof(char));
sprintf(command,"%s ",sb_cmd->compiler);
if (sb_cmd->debug!=0){
sprintf(command,"%s-g ",command);
}
for (int i=0;i<sb_cmd->target_top;i++){
if(access(sb_cmd->targets[i],F_OK)){
free(command);
exit(0);
}
sprintf(command,"%s%s ",command,sb_cmd->targets[i]);
}
if (sb_cmd->name!=NULL){
sprintf(command,"%s-o %s ",command,sb_cmd->name);
}
if (sb_cmd->links_top!=0){
for (int i=0;i<sb_cmd->links_top;i++){
sprintf(command,"%s-l%s ",command,sb_cmd->links[i]);
}
}
//Add check update.
if(need_recompile){
printf("[SB] run %s\n",command);
int res = system(command);
if((res>>8) == 0){
printf("[SB] Compile success,run '%s' again to run the new version\n",sb_cmd->name);
}else{
printf("[SB] Compile faile!Check message above for more detail\n");
}
free(command);
exit(0);
}
free(command);
return;
}
#endif
#endif
整体并不算是一个很复杂的玩意,当然也因此放弃了一些可靠性,但是对我的个人项目这种经常5个文件都不到的小小项目所以完全没问题。总之,我们先来看看用法:
#define SELFBUILD_IMPLEMENTATION
#include "selfbuild.h"
#include <stdio.h>
int main(){
//SelfBuild Code
Sb_cmd cmd = {0};
sb_set_compiler(&cmd,"gcc");
sb_add_target(&cmd,"tmain.c");
sb_set_output_name(&cmd,"tmain");
sb_run(&cmd);
//Another Code
printf("Hello SelfBuild!\n");
printf("This is pretty good!\n");
}
这是一个很简单的HelloWorld,printf前的就是配置代码,具体来说就是指定编译器,设置需要编译的目标文件,输出的文件名称,然后运行。当然,没人想让自己的软件在运行时出来这些奇怪的东西,所以在sb_run中实际上在找不到目标文件或者文件比可执行文件旧,那就直接继续执行业务代码,否则就运行编译指令,然后退出,不执行业务代码,此时的可执行文件就是最新的了,只要再次运行就行了。至于这玩意有什么用,或者说解决了什么问题,解决了我写完一些测试代码之后找不到以前的编译命令的历史的时候还要再写一次的麻烦,以及只要修改完后直接运行可执行文件就能编译好了,不会有两条命令倒来倒去。
这里简单说说这个库的工作流程吧。首先它需要一个Sb_cmd结构体,它存储了一些重要的值,比如编译目标、需要链接的库、输出的文件名称等等。然后你通过以上的一些函数来向其中添加内容。最后在sb_run函数中,这个结构体的东西会被解析,首先解析编译的目标文件和编译器,如果缺少了这两个最重要的内容就会退出,然后是开始检查目标文件是否有更新,如果需要重新编译,那就进入下面的命令构建阶段,否则就直接返回,运行业务代码。检查文件更新的代码也很简单,就是通过linux提供的stat()来获取上一次修改的时间,和可执行文件的上一次修改时间进行比较,如果更新,那就重新编译。命令构建阶段其实就是按照我习惯的顺序来写的,它首先把所有参与构成的部分都遍历一遍,计算出命令长度,然后按顺序填进去,运行,然后free,退出,就是这么简单。
那么现在再介绍一下这里没用到的两个函数,一个是sb_set_debug,它其实就是一个开关,将debug标志反过来,而在最后的命令构成阶段会根据这个标志来决定要不要加入-g参数,不过唯一的问题是,我没有查其他编译器的debug参数是不是这个,大概率不是,所以抱歉了非GCC用户(笑)
然后是sb_add_link函数,这个函数会加入链接参数,但是我没做任何解析,就是直接把你写的玩意放到-l后面了,所以如果你要用数学库,你需要直接传入一个"m",而不是"math",说不定我之后会加一个宏吧,这样更方便些。
最后感觉有必要讲一下sb_add_target这个函数,它支持src/*.c这样的写法,虽然在命令构成阶段并不需要展开它,但是在此之前有一个判断,也就是判断源文件是否存在,如果存在,是否有文件更新了,这样才会真的运行编译命令,否则就是正常执行业务代码。
总体来说的确不是一个很完备的框架,但是的确帮助我变得更加懒惰了(笑)
最后,它并不适合任何人,但是我相信它不会对你的文件做什么的,毕竟最后还是依靠你的编译器在修改你的文件,而大部分编译器应该都不会在报错之类的时候修改你的可执行文件什么的,所以基本上不用担心。
Par QingYu, Catégorie : misc
Tags : Programming / Personal-Projects /
Autres articles
D1_Two-Bone-IK
Le 二 28 七月 2026
Document of how I implement IK solver in my project
Par QingYu, Catégorie : misc
Lire la suite …C2_Use Jpype+LibGDX to develop a game in EN
Le 日 05 四月 2026
按照上一篇所说,我会在这一篇中,较为详细的说明,如何使用Jpype+LibGDX这样的技术栈进行一些开发。注意,我不建议你把它用在生产中,但是你都固执到想要 …
Par QingYu, Catégorie : misc
Lire la suite …C2_Use Jpype+LibGDX to develop a game
Le 六 28 二月 2026
按照上一篇所说,我会在这一篇中,较为详细的说明,如何使用Jpype+LibGDX这样的技术栈进行一些开发。注意,我不建议你把它用在生产中,但是你都固执到想要 …
Par QingYu, Catégorie : misc
Lire la suite …C1_StarBeats
Le 六 28 二月 2026
Introduce my Programming projects.
Par QingYu, Catégorie : misc
Lire la suite …C1_StarBeats in EN
Le 六 28 二月 2026
Introduce my Programming projects.