浏览代码

Ready. Set. Go!

rexim 4 年之前
当前提交
61dc17340c
共有 9 个文件被更改,包括 539 次插入0 次删除
  1. 49 0
      .github/workflows/ci.yml
  2. 1 0
      .gitignore
  3. 20 0
      LICENSE
  4. 25 0
      README.md
  5. 2 0
      examples/.gitignore
  6. 44 0
      examples/foreach.c
  7. 14 0
      examples/string.c
  8. 27 0
      nobuild.c
  9. 357 0
      nobuild.h

+ 49 - 0
.github/workflows/ci.yml

@@ -0,0 +1,49 @@
+name: CI
+on: [push, pull_request]
+
+jobs:
+  build-linux-gcc:
+    runs-on: ubuntu-18.04
+    steps:
+      - uses: actions/checkout@v1
+      - name: build
+        run: |
+          $CC nobuild.c -o nobuild
+          ./nobuild
+        env:
+          CC: gcc
+          CXX: g++
+  build-linux-clang:
+    runs-on: ubuntu-18.04
+    steps:
+      - uses: actions/checkout@v1
+      - name: build
+        run: |
+          $CC nobuild.c -o nobuild
+          ./nobuild
+        env:
+          CC: clang
+          CXX: clang++
+  build-macos:
+    runs-on: macOS-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: build
+        run: |
+          $CC nobuild.c -o nobuild
+          ./nobuild
+        env:
+          CC: clang
+          CXX: clang++
+  build-windows-msvc:
+    runs-on: windows-2019
+    steps:
+      - uses: actions/checkout@v1
+        # this runs vcvarsall for us, so we get the MSVC toolchain in PATH.
+      - uses: seanmiddleditch/gha-setup-vsdevenv@master
+      - name: build
+        shell: cmd
+        # this replaces default PowerShell, which can't fail the build
+        run: |
+          cl.exe nobuild.c
+          .\nobuild.exe test

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+nobuild

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+Copyright 2021 Alexey Kutepov <[email protected]>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+# nobuild
+
+Header only library for writing build recipes in C.
+
+## Main idea
+
+The idea is that you should not need anything but a C compiler to build a C project. No make, no cmake, no shell, no cmd, no PowerShell etc. Only C compiler. So with the C compiler you bootstrap your build system and then you use the build system to build everything else.
+
+Try it out right here:
+
+```
+$ cc ./nobuild.c -o nobuild
+$ ./nobuild
+```
+
+Explore [nobuild.c](./nobuild.c) file and the [examples](./examples) folder to learn more.
+
+## How to use the library in your own project
+
+1. Copy [nobuild.h](./nobuild.h) to your project
+2. Create `nobuild.c` in your project with the build recipe. See our own [nobuild.c](./nobuild.c) for an example.
+3. Bootstrap the `nobuild` executable:
+   - `$ cc nobuild.c -o nobuild` on POSIX systems
+   - `$ cl.exe nobuild.c` on Windows with MSVC
+4. Run the build: `$ ./nobuild`

+ 2 - 0
examples/.gitignore

@@ -0,0 +1,2 @@
+string
+foreach

+ 44 - 0
examples/foreach.c

@@ -0,0 +1,44 @@
+#include "../nobuild.h"
+
+void foreach_vargs(int ignore, ...)
+{
+    va_list args;
+
+    FOREACH_VARGS(ignore, arg, args, {
+        printf("    %s\n", arg);
+    });
+}
+
+void foreach_array(void)
+{
+    static const char *array[] = {
+        "foo", "bar", "baz"
+    };
+
+    FOREACH_ARRAY(const char *, item, array, {
+        printf("    %s\n", item);
+    });
+}
+
+void foreach_file_in_dir(const char *dir_path)
+{
+    FOREACH_FILE_IN_DIR(file, dir_path, {
+            printf("    %s\n", file);
+    });
+}
+
+#define DEMO(expr)                              \
+    do {                                        \
+        printf(#expr"\n");                      \
+        expr;                                   \
+    } while(0)
+
+int main(int argc, char *argv[])
+{
+    printf("=== Foreach Macro Example ===\n");
+    DEMO(foreach_vargs(69, "foo", "bar", "baz", NULL));
+    DEMO(foreach_array());
+    DEMO(foreach_file_in_dir("."));
+
+    return 0;
+}

+ 14 - 0
examples/string.c

@@ -0,0 +1,14 @@
+#include "../nobuild.h"
+
+#define DEMO(expr)                              \
+    printf(#expr " == \"%s\"\n", expr)
+
+int main(int argc, char *argv[])
+{
+    printf("=== String Manipulation Example ===\n");
+    DEMO(CONCAT("foo", "bar", "baz"));
+    DEMO(PATH("foo", "bar", "baz"));
+    DEMO(CONCAT_SEP("++", "foo", "bar", "baz"));
+    DEMO(remove_ext("main.c"));
+    return 0;
+}

+ 27 - 0
nobuild.c

@@ -0,0 +1,27 @@
+#include "./nobuild.h"
+
+void check_example(const char *example)
+{
+    const char *example_path = PATH("examples", remove_ext(example));
+
+#ifdef _WIN32
+    CMD("cl.exe", PATH("examples", example));
+    CMD(CONCAT(example_path, ".exe"));
+#else
+    CMD("cc", "-o", example_path, PATH("examples", example));
+    CMD(example_path);
+#endif // _WIN32
+}
+
+int main(int argc, char *argv[])
+{
+    FOREACH_FILE_IN_DIR(example, "examples", {
+        if(*example != '.') {
+            size_t n = strlen(example);
+            if (n >= 2 && strcmp(example + n - 2, ".c") == 0) {
+                check_example(example);
+            }
+        }
+    });
+    return 0;
+}

+ 357 - 0
nobuild.h

@@ -0,0 +1,357 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef _WIN32
+#   define WIN32_LEAN_AND_MEAN
+#   include "windows.h"
+#   include <process.h>
+#else
+#   include <sys/stat.h>
+#   include <sys/types.h>
+#   include <sys/wait.h>
+#   include <unistd.h>
+#endif // _WIN32
+
+// TODO: no way to disable echo in nobuild scripts
+// TODO: no way to ignore fails
+
+#ifdef _WIN32
+struct dirent
+{
+    char d_name[MAX_PATH+1];
+};
+
+typedef struct {
+    HANDLE hFind;
+    WIN32_FIND_DATA data;
+    struct dirent *dirent;
+} DIR;
+
+DIR *opendir(const char *dirpath)
+{
+    assert(dirpath);
+
+    char buffer[MAX_PATH];
+    snprintf(buffer, MAX_PATH, "%s\\*", dirpath);
+
+    DIR *dir = (DIR*)calloc(1, sizeof(DIR));
+
+    dir->hFind = FindFirstFile(buffer, &dir->data);
+    if (dir->hFind == INVALID_HANDLE_VALUE) {
+        goto fail;
+    }
+
+    return dir;
+
+fail:
+    if (dir) {
+        free(dir);
+    }
+
+    return NULL;
+}
+
+struct dirent *readdir(DIR *dirp)
+{
+    assert(dirp);
+
+    if (dirp->dirent == NULL) {
+        dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent));
+    } else {
+        if(!FindNextFile(dirp->hFind, &dirp->data)) {
+            return NULL;
+        }
+    }
+
+    memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name));
+
+    strncpy(
+        dirp->dirent->d_name,
+        dirp->data.cFileName,
+        sizeof(dirp->dirent->d_name) - 1);
+
+    return dirp->dirent;
+}
+
+void closedir(DIR *dirp)
+{
+    assert(dirp);
+
+    FindClose(dirp->hFind);
+    if (dirp->dirent) {
+        free(dirp->dirent);
+    }
+    free(dirp);
+}
+#else
+#    include <dirent.h>
+#endif // _WIN32
+
+#ifdef _WIN32
+#    define PATH_SEP "\\"
+#else
+#    define PATH_SEP "/"
+#endif // _WIN32
+
+#define PATH_SEP_LEN (sizeof(PATH_SEP) - 1)
+
+#define FOREACH_VARGS(param, arg, args, body)               \
+    do {                                                    \
+        va_start(args, param);                              \
+        for (const char *arg = va_arg(args, const char *);  \
+             arg != NULL;                                   \
+             arg = va_arg(args, const char *))              \
+        {                                                   \
+            body;                                           \
+        }                                                   \
+        va_end(args);                                       \
+    } while(0)
+
+#define FOREACH_ARRAY(type, item, items, body)  \
+    do {                                        \
+        for (size_t i = 0;                                              \
+             i < sizeof(items) / sizeof((items)[0]);                    \
+             ++i)                                                       \
+        {                                                               \
+            type item = items[i];                                       \
+            body;                                                       \
+        }                                                               \
+    } while(0)
+
+#define FOREACH_FILE_IN_DIR(file, dirpath, body)        \
+    do {                                                \
+        struct dirent *dp = NULL;                       \
+        DIR *dir = opendir(dirpath);                    \
+        while ((dp = readdir(dir))) {                   \
+            const char *file = dp->d_name;              \
+            body;                                       \
+        }                                               \
+        closedir(dir);                                  \
+    } while(0)
+
+const char *concat_sep_impl(const char *sep, ...)
+{
+    const size_t sep_len = strlen(sep);
+    size_t length = 0;
+    size_t seps_count = 0;
+
+    va_list args;
+
+    FOREACH_VARGS(sep, arg, args, {
+        length += strlen(arg);
+        seps_count += 1;
+    });
+    assert(length > 0);
+
+    seps_count -= 1;
+
+    char *result = malloc(length + seps_count * sep_len + 1);
+
+    length = 0;
+    FOREACH_VARGS(sep, arg, args, {
+        size_t n = strlen(arg);
+        memcpy(result + length, arg, n);
+        length += n;
+
+        if (seps_count > 0) {
+            memcpy(result + length, sep, sep_len);
+            length += sep_len;
+            seps_count -= 1;
+        }
+    });
+
+    result[length] = '\0';
+
+    return result;
+}
+
+#define CONCAT_SEP(sep, ...) concat_sep_impl(sep, __VA_ARGS__, NULL)
+
+#define PATH(...) CONCAT_SEP(PATH_SEP, __VA_ARGS__)
+
+void mkdirs_impl(int ignore, ...)
+{
+    size_t length = 0;
+    size_t seps_count = 0;
+
+    va_list args;
+
+    FOREACH_VARGS(ignore, arg, args, {
+        length += strlen(arg);
+        seps_count += 1;
+    });
+
+    assert(length > 0);
+    seps_count -= 1;
+
+    char *result = malloc(length + seps_count * PATH_SEP_LEN + 1);
+
+    length = 0;
+    FOREACH_VARGS(ignore, arg, args, {
+        size_t n = strlen(arg);
+        memcpy(result + length, arg, n);
+        length += n;
+
+        if (seps_count > 0) {
+            memcpy(result + length, PATH_SEP, PATH_SEP_LEN);
+            length += PATH_SEP_LEN;
+            seps_count -= 1;
+        }
+
+        result[length] = '\0';
+
+        printf("[INFO] mkdir %s\n", result);
+        if (mkdir(result, 0755) < 0) {
+            if (errno == EEXIST) {
+                fprintf(stderr, "[WARN] directory %s already exists\n",
+                        result);
+            } else {
+                fprintf(stderr, "[ERROR] could not create directory %s: %s\n",
+                        result, strerror(errno));
+                exit(1);
+            }
+        }
+    });
+}
+
+#define MKDIRS(...) mkdirs_impl(69, __VA_ARGS__, NULL)
+
+// TODO: there is no way to remove a folder
+// TODO: there is no way to remove a file
+
+const char *concat_impl(int ignore, ...)
+{
+    size_t length = 0;
+    va_list args;
+    FOREACH_VARGS(ignore, arg, args, {
+        length += strlen(arg);
+    });
+
+    char *result = malloc(length + 1);
+
+    length = 0;
+    FOREACH_VARGS(ignore, arg, args, {
+        size_t n = strlen(arg);
+        memcpy(result + length, arg, n);
+        length += n;
+    });
+    result[length] = '\0';
+
+    return result;
+}
+
+#define CONCAT(...) concat_impl(69, __VA_ARGS__, NULL)
+
+void nobuild_exec(const char **argv)
+{
+#ifdef _WIN32
+    intptr_t status = _spawnvp(_P_WAIT, argv[0], (char * const*) argv);
+    if (status < 0) {
+        fprintf(stderr, "[ERROR] could not start child process: %s\n",
+                strerror(errno));
+        exit(1);
+    }
+
+    if (status > 0) {
+        fprintf(stderr, "[ERROR] command exited with exit code %d\n",
+                status);
+        exit(1);
+    }
+#else
+    pid_t cpid = fork();
+    if (cpid == -1) {
+        fprintf(stderr, "[ERROR] could not fork a child process: %s\n",
+                strerror(errno));
+        exit(1);
+    }
+
+    if (cpid == 0) {
+        if (execvp(argv[0], (char * const*) argv) < 0) {
+            fprintf(stderr, "[ERROR] could not execute child process: %s\n",
+                    strerror(errno));
+            exit(1);
+        }
+    } else {
+        for (;;) {
+            int wstatus = 0;
+            wait(&wstatus);
+
+            if (WIFEXITED(wstatus)) {
+                int exit_status = WEXITSTATUS(wstatus);
+                if (exit_status != 0) {
+                    fprintf(stderr, "[ERROR] command exited with exit code %d\n", exit_status);
+                    exit(-1);
+                }
+
+                break;
+            }
+
+            if (WIFSIGNALED(wstatus)) {
+                fprintf(stderr, "[ERROR] command process was terminated by signal %d\n",
+                        WTERMSIG(wstatus));
+                exit(-1);
+            }
+        }
+    }
+#endif // _WIN32
+}
+
+void cmd_impl(int ignore, ...)
+{
+    size_t argc = 0;
+
+    va_list args;
+    FOREACH_VARGS(ignore, arg, args, {
+        argc += 1;
+    });
+
+    const char **argv = malloc(sizeof(const char*) * (argc + 1));
+
+    argc = 0;
+    FOREACH_VARGS(ignore, arg, args, {
+        argv[argc++] = arg;
+    });
+    argv[argc] = NULL;
+
+    assert(argc >= 1);
+
+    nobuild_exec(argv);
+}
+
+// TODO: there is no way to redirect the output of CMD to a file
+#define CMD(...)                                                \
+    do {                                                        \
+        printf("[INFO] %s\n", CONCAT_SEP(" ", __VA_ARGS__));    \
+        cmd_impl(69, __VA_ARGS__, NULL);                        \
+    } while(0)
+
+const char *remove_ext(const char *path)
+{
+    size_t n = strlen(path);
+    while (n > 0 && path[n - 1] != '.') {
+        n -= 1;
+    }
+
+    if (n > 0) {
+        char *result = malloc(n);
+        memcpy(result, path, n);
+        result[n - 1] = '\0';
+
+        return result;
+    } else {
+        return path;
+    }
+}
+
+char *shift(int *argc, char ***argv)
+{
+    assert(*argc > 0);
+    char *result = **argv;
+    *argv += 1;
+    *argc -= 1;
+    return result;
+}