文件
Paze-C-CPP-Compiler/tools/pcmake.c
T
Paze AI cafa815116 feat: 运行模式/优化/C17/pcmake/多线程 五项功能
1. -run 脚本模式完善
   - shebang 脚本 (#!/usr/bin/env pcc -run) 支持
   - -rstdin 自定义输入、argv 参数传递
   - win32/pcc-run.cmd 便捷启动器

2. -O2 优化等级
   - -O0/-O1/-O2/-O3/-Os 全等级验证
   - __OPTIMIZE__ 宏正确设置,常量折叠生效

3. C17 全特性
   - 默认标准改为 C11 (__STDC_VERSION__=201112L)
   - 新增 -std=c17/c18/c99/c89 支持
   - _Generic/_Atomic/_Alignas/匿名结构/TLS 全通过

4. pcmake 构建工具 (tools/)
   - 纯 C 编写,用 pcc 自身编译
   - pcmake.conf 配置: sources/include/libs/output/flags
   - CreateProcess 正确处理带空格路径
   - pcmake.bat 一键构建 + clean
   - 示例: examples/demo 多文件项目

5. 多线程库封装
   - win32/include/pthread.h: POSIX 线程 Windows 实现
     (CreateThread 封装, 事件驱动条件变量, 惰性初始化互斥锁)
   - win32/include/threads.h: C11 标准线程 <threads.h>
   - 4线程并发/互斥/条件变量/生产者消费者 测试通过
2026-08-16 14:47:28 +08:00

349 行
9.9 KiB
C

/*
* pcmake.c - a tiny build tool for PCC
*
* Reads a "pcmake.conf" file and builds a C project.
* Written in pure C, compiles with pcc itself.
*
* pcmake.conf format:
* sources = main.c util.c
* include = include
* libs = m
* output = myprog
* flags = -O2 -Wall
*
* Usage:
* pcmake # build using pcmake.conf in current dir
* pcmake -f file # use a different config file
* pcmake clean # remove build artifacts
*
* License: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef _WIN32
# include <direct.h>
# include <io.h>
# include <windows.h>
# define IS_DIRSEP(c) ((c) == '/' || (c) == '\\')
#else
# include <unistd.h>
# define IS_DIRSEP(c) ((c) == '/')
#endif
#define MAX_TOKENS 256
#define MAX_LINE 1024
#define MAX_STR 4096
typedef struct {
char *tokens[MAX_TOKENS];
int count;
} TokenList;
static char *g_sources[MAX_TOKENS];
static char *g_include[MAX_TOKENS];
static char *g_libs[MAX_TOKENS];
static char g_output_buf[128] = "a";
static char *g_output = g_output_buf; /* never free() this */
static char *g_flags[MAX_TOKENS];
static int n_sources, n_include, n_libs, n_flags;
static void die(const char *msg) {
fprintf(stderr, "pcmake: %s\n", msg);
exit(1);
}
static char *xstrdup(const char *s) {
char *p = (char*)malloc(strlen(s) + 1);
if (!p) die("out of memory");
strcpy(p, s);
return p;
}
/* trim whitespace */
static char *trim(char *s) {
char *end;
while (isspace((unsigned char)*s)) s++;
if (*s == 0) return s;
end = s + strlen(s) - 1;
while (end > s && isspace((unsigned char)*end)) end--;
end[1] = 0;
return s;
}
/* split a line into tokens on whitespace */
static TokenList tokenize(const char *line) {
TokenList tl;
char buf[MAX_LINE];
char *p, *tok;
tl.count = 0;
strncpy(buf, line, sizeof buf - 1);
buf[sizeof buf - 1] = 0;
p = buf;
while (*p) {
while (*p && isspace((unsigned char)*p)) p++;
if (!*p) break;
tok = p;
while (*p && !isspace((unsigned char)*p)) p++;
if (*p) *p++ = 0;
if (tl.count < MAX_TOKENS)
tl.tokens[tl.count++] = xstrdup(tok);
}
return tl;
}
static void add_token(char **arr, int *n, const char *tok) {
if (*n < MAX_TOKENS)
arr[(*n)++] = xstrdup(tok);
}
/* parse one config line: key = value tokens */
static void parse_line(const char *line) {
char buf[MAX_LINE];
char *eq, *key, *val;
TokenList tl;
int i;
strncpy(buf, line, sizeof buf - 1);
buf[sizeof buf - 1] = 0;
key = trim(buf);
if (*key == 0 || *key == '#') /* empty or comment */
return;
eq = strchr(key, '=');
if (!eq)
return; /* ignore lines without '=' */
*eq = 0;
val = trim(eq + 1);
key = trim(key);
tl = tokenize(val);
if (strcmp(key, "sources") == 0) {
for (i = 0; i < tl.count; i++) add_token(g_sources, &n_sources, tl.tokens[i]);
} else if (strcmp(key, "include") == 0) {
for (i = 0; i < tl.count; i++) add_token(g_include, &n_include, tl.tokens[i]);
} else if (strcmp(key, "libs") == 0) {
for (i = 0; i < tl.count; i++) add_token(g_libs, &n_libs, tl.tokens[i]);
} else if (strcmp(key, "output") == 0) {
if (tl.count > 0) {
strncpy(g_output_buf, tl.tokens[0], sizeof g_output_buf - 1);
g_output_buf[sizeof g_output_buf - 1] = 0;
g_output = g_output_buf;
}
} else if (strcmp(key, "flags") == 0) {
for (i = 0; i < tl.count; i++) add_token(g_flags, &n_flags, tl.tokens[i]);
}
/* unknown keys ignored */
for (i = 0; i < tl.count; i++) free(tl.tokens[i]);
}
static void read_config(const char *fname) {
FILE *f = fopen(fname, "r");
char line[MAX_LINE];
if (!f) die("cannot open config file (expected 'pcmake.conf')");
while (fgets(line, sizeof line, f))
parse_line(line);
fclose(f);
}
/* compile one source file into an object file */
static int run_command(const char *cmd); /* fwd decl */
static int compile_file(const char *pcc, const char *bopt, const char *src, const char *obj) {
char cmd[MAX_STR];
int len = 0, i;
len += snprintf(cmd + len, sizeof cmd - len, "\"%s\" %s \"%s\" -c -o \"%s\"",
pcc, bopt, src, obj);
for (i = 0; i < n_include; i++)
len += snprintf(cmd + len, sizeof cmd - len, " -I \"%s\"", g_include[i]);
for (i = 0; i < n_flags; i++)
len += snprintf(cmd + len, sizeof cmd - len, " %s", g_flags[i]);
printf(" pcc -c %s\n", src);
return run_command(cmd);
}
/* replace .c extension with .o */
static void objname(const char *src, char *out, int outsize) {
const char *slash = NULL, *p;
const char *base;
const char *dot;
int n;
/* find last slash of either kind */
for (p = src; *p; p++) {
if (*p == '/' || *p == '\\')
slash = p;
}
base = slash ? slash + 1 : src;
dot = strrchr(base, '.');
n = (dot && dot > base) ? (int)(dot - base) : (int)strlen(base);
if (n >= outsize) n = outsize - 1;
memcpy(out, base, n);
out[n] = 0;
strncat(out, ".o", outsize - n - 1);
}
static int file_exists(const char *f) {
FILE *p = fopen(f, "r");
if (p) { fclose(p); return 1; }
return 0;
}
/* run a command line; Windows uses CreateProcess to handle
quoted paths with spaces correctly (system() misbehaves) */
static int run_command(const char *cmd) {
#ifdef _WIN32
STARTUPINFOA si;
PROCESS_INFORMATION pi;
DWORD code = 1;
char *cmdline = (char*)malloc(strlen(cmd) + 1);
if (!cmdline) return -1;
strcpy(cmdline, cmd);
memset(&si, 0, sizeof si);
si.cb = sizeof si;
if (CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
fprintf(stderr, "pcmake: CreateProcess failed (%lu): %s\n",
(unsigned long)GetLastError(), cmd);
code = 1;
}
free(cmdline);
return (int)code;
#else
return system(cmd);
#endif
}
int main(int argc, char **argv) {
const char *cfg = "pcmake.conf";
const char *pcc = "pcc";
char bopt[600] = ""; /* "-B <dir>" or empty when pcc.bat used */
char objbuf[512];
char cmd[MAX_STR];
int i, len, rc;
int is_clean = 0;
int any_failed = 0;
/* parse args */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
cfg = argv[++i];
} else if (strcmp(argv[i], "-pcc") == 0 && i + 1 < argc) {
pcc = argv[++i];
} else if (strcmp(argv[i], "-B") == 0 && i + 1 < argc) {
snprintf(bopt, sizeof bopt, "-B \"%s\"", argv[++i]);
} else if (strcmp(argv[i], "clean") == 0) {
is_clean = 1;
}
}
read_config(cfg);
/* locate pcc:
1. if -pcc was given, use it
2. else if pcc.bat wrapper exists in cwd, use it (it sets -B)
3. else search common install locations for pcc.exe */
if (strcmp(pcc, "pcc") == 0) {
const char *cands[] = {
"pcc.bat",
"pcc.exe",
"C:\\pcc\\pcc.exe",
"C:\\Program Files\\pcc\\pcc.exe",
"C:\\Program Files (x86)\\pcc\\pcc.exe",
NULL
};
int k;
for (k = 0; cands[k]; k++) {
if (file_exists(cands[k])) {
pcc = cands[k];
break;
}
}
}
if (is_clean) {
/* remove object files and output */
for (i = 0; i < n_sources; i++) {
objname(g_sources[i], objbuf, sizeof objbuf);
printf(" rm %s\n", objbuf);
remove(objbuf);
}
{
char outbuf[512];
strncpy(outbuf, g_output, sizeof outbuf - 1);
outbuf[sizeof outbuf - 1] = 0;
#ifdef _WIN32
strncat(outbuf, ".exe", sizeof outbuf - strlen(outbuf) - 1);
#endif
printf(" rm %s\n", outbuf);
remove(outbuf);
}
printf("pcmake: cleaned\n");
return 0;
}
if (n_sources == 0) {
fprintf(stderr, "pcmake: no sources specified in %s\n", cfg);
return 1;
}
printf("pcmake: building %s (%d source files)\n", g_output, n_sources);
/* compile each source */
for (i = 0; i < n_sources; i++) {
objname(g_sources[i], objbuf, sizeof objbuf);
rc = compile_file(pcc, bopt, g_sources[i], objbuf);
if (rc != 0) {
fprintf(stderr, "pcmake: compile failed: %s\n", g_sources[i]);
any_failed = 1;
}
}
if (any_failed) {
fprintf(stderr, "pcmake: build failed\n");
return 1;
}
/* link */
len = 0;
len += snprintf(cmd + len, sizeof cmd - len, "\"%s\" %s", pcc, bopt);
for (i = 0; i < n_include; i++)
len += snprintf(cmd + len, sizeof cmd - len, " -I \"%s\"", g_include[i]);
for (i = 0; i < n_flags; i++)
len += snprintf(cmd + len, sizeof cmd - len, " %s", g_flags[i]);
for (i = 0; i < n_sources; i++) {
objname(g_sources[i], objbuf, sizeof objbuf);
len += snprintf(cmd + len, sizeof cmd - len, " \"%s\"", objbuf);
}
for (i = 0; i < n_libs; i++)
len += snprintf(cmd + len, sizeof cmd - len, " -l%s", g_libs[i]);
{
char outbuf[512];
strncpy(outbuf, g_output, sizeof outbuf - 1);
outbuf[sizeof outbuf - 1] = 0;
#ifdef _WIN32
strncat(outbuf, ".exe", sizeof outbuf - strlen(outbuf) - 1);
#endif
len += snprintf(cmd + len, sizeof cmd - len, " -o \"%s\"", outbuf);
}
printf(" link %s\n", g_output);
rc = run_command(cmd);
if (rc != 0) {
fprintf(stderr, "pcmake: link failed\n");
return 1;
}
printf("pcmake: build OK -> %s%s\n", g_output,
#ifdef _WIN32
".exe"
#else
""
#endif
);
return 0;
}