- 新增 bin/ 自包含工具链目录
- bin/pcc.exe: 编译器主程序
- bin/libpcc.dll: 嵌入式编译库
- bin/pcmake.exe: 构建工具(自动定位 pcc,无 bat 包装)
- bin/include/: 头文件(pcc 自动从 exe 位置查找)
- bin/lib/: 运行时库
修复: pcc 从 bin/ 直接运行找不到 pccdefs.h/libpcc1.a
(根因: pcc 从可执行文件位置查找 {B}/include 和 {B}/lib)
删除所有工具链脚本:
- bin/pcc-run.cmd (pcc -run 直接可用)
- bin/pcmake.bat (pcmake.exe 自动定位)
- win32/build-pcc.bat (纯命令构建)
- build-release.ps1
验证: ./pcc demo.c -o demo.exe 在 bin/ 下直接工作
390 行
11 KiB
C
390 行
11 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;
|
|
}
|
|
|
|
/* build the output file name: append .exe on Windows only if
|
|
the configured name does not already end with .exe (case-insensitive) */
|
|
static int ends_with_exe(const char *s)
|
|
{
|
|
int n = (int)strlen(s);
|
|
if (n < 4) return 0;
|
|
s += n - 4;
|
|
return (tolower((unsigned char)s[1])=='e')
|
|
&& (tolower((unsigned char)s[2])=='x') && (tolower((unsigned char)s[3])=='e');
|
|
}
|
|
|
|
static void make_output_name(const char *in, char *out, int outsize)
|
|
{
|
|
int n = (int)strlen(in);
|
|
if (n >= outsize) n = outsize - 1;
|
|
memcpy(out, in, n);
|
|
out[n] = 0;
|
|
#ifdef _WIN32
|
|
if (!ends_with_exe(out))
|
|
strncat(out, ".exe", outsize - n - 1);
|
|
#endif
|
|
}
|
|
|
|
/* 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 automatically:
|
|
1. if -pcc was given, use it
|
|
2. else look for pcc.exe in the same directory as pcmake.exe
|
|
3. else look in cwd and common install locations */
|
|
if (strcmp(pcc, "pcc") == 0) {
|
|
/* find pcmake's own executable path */
|
|
char selfpath[MAX_STR];
|
|
char selfdir[MAX_STR];
|
|
char *slash;
|
|
#ifdef _WIN32
|
|
GetModuleFileNameA(NULL, selfpath, sizeof selfpath);
|
|
#else
|
|
/* on POSIX use argv[0] relative path */
|
|
strncpy(selfpath, argv[0], sizeof selfpath - 1);
|
|
selfpath[sizeof selfpath - 1] = 0;
|
|
#endif
|
|
strncpy(selfdir, selfpath, sizeof selfdir - 1);
|
|
selfdir[sizeof selfdir - 1] = 0;
|
|
slash = strrchr(selfdir, '\\');
|
|
if (!slash) slash = strrchr(selfdir, '/');
|
|
if (slash) *slash = 0;
|
|
else strcpy(selfdir, ".");
|
|
|
|
{
|
|
char cand[MAX_STR];
|
|
snprintf(cand, sizeof cand, "%s\\pcc.exe", selfdir);
|
|
if (file_exists(cand)) {
|
|
pcc = cand; /* pcc.exe sits next to pcmake.exe */
|
|
/* bin/ is self-contained: pcc finds include/lib
|
|
relative to its own location, so no -B needed */
|
|
}
|
|
}
|
|
if (strcmp(pcc, "pcc") == 0) {
|
|
const char *cands[] = {
|
|
"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];
|
|
make_output_name(g_output, outbuf, sizeof outbuf);
|
|
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];
|
|
make_output_name(g_output, outbuf, sizeof outbuf);
|
|
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;
|
|
}
|
|
|
|
{
|
|
char outbuf[512];
|
|
make_output_name(g_output, outbuf, sizeof outbuf);
|
|
printf("pcmake: build OK -> %s\n", outbuf);
|
|
}
|
|
return 0;
|
|
}
|