]> Trent Huber's Code - cbs.git/commitdiff
Rearrangment and rewording
authorTrent Huber <trentmhuber@gmail.com>
Fri, 18 Apr 2025 10:50:11 +0000 (06:50 -0400)
committerTrent Huber <trentmhuber@gmail.com>
Fri, 18 Apr 2025 10:50:11 +0000 (06:50 -0400)
README.md
cbs.c

index e35ff3a9090f156032b4937edf6e0a89ad2b433f..539f7d5857a6f93b7351385a1103b8f1a7bcfed0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -45,23 +45,40 @@ cc -o main main.o
 
 ## Detailed usage
 
-The advantage of making a build system that only compiles C projects is that it can be made dead simple. C code just needs to be compiled and linked, and perhaps it would be nice to recurse the build into subdirectories. cbs uses three simple functions accordingly: `compile()` for compiling, `load()` for linking, and `build()` for recursion.
+The advantage of making a library that only builds C projects is that it can be made dead simple. The build system itself just needs a way of executing build files recursively, and each build file just needs to compile and link C code. cbs uses three simple functions accordingly: `build()` for recursion, `compile()` for compiling, and `load()` for linking.
 
-### Compiling
+### Recurring build files
+
+```c
+void build(char *path);
+```
+
+It is often advantageous to compartmentalize projects into a number of subdirectories both for organizational purposes and for rebuilding parts at a time without needing to rebuild the whole thing. The usual way this is done is by placing build scripts in any subdirectory you want to rebuild on its own. These scripts are both able to be ran by the programmer from a shell as well as being able to be run by the build system itself from some parent directory. In cbs, it is the `build()` function that performs this function.
+
+Build files must be named `build.c`, and the resulting build executable will always be named `build`. Each build file is in charge of building the contents of its resident directory. `build()` gets passed the name of the subdirectory you want to build, either as an absolute or relative path. It should be noted that **all relative paths in a build file are always assumed to be with respect to the directory that that build file is in**, not the root directory of the project. If `path` is a null pointer, this has the effect of staying in the current directory and thus (if the build file was modified) recompiling its own build file and rerunning itself. In essence, this function is what allows cbs to be truly automated.
+
+An example of building the contents of the directories `abc/src/` and `/usr/local/proj/src/`.
+
+```c
+build("abc/src/");
+build("/usr/local/proj/src/");
+```
+
+### Compiling source files
 
 ```c
 void compile(char *src, ...);
 ```
 
-The `compile()` function is given a single source file to compile. cbs will generate an object file of the same name as the source file and in the same directory. The general philosophy cbs takes on file extensions is to only use them when using *non-typical* file extensions, as **typical file extensions are always implicit**. In the case of compiling, source files have the typical extension `.c` and object files have `.o`. This has the convenient side effect of letting us reuse the list of source file names as object file names for the linker.[^1]
+The `compile()` function is given a single source file to compile. It will only run if it finds the source file has been modified since the last time it compiled it. This is similar to the caching behavior in most other build systems. When cbs generates an object file, it will be of the same name as its source file and in the same directory. The philosophy cbs takes on file extensions is to only use them when using *non-typical* file extensions, as **typical file extensions are always implicit**. What counts as a "typical" file extension depends on the situation. In the case of compiling, source files have the typical extension `.c` and object files have `.o`. As it turns out dropping the typical file extensions has its advantages in writing concise code.[^1]
 
-[^1]: One example is putting a list of comma-separated C strings in a macro. Then we can pass it both to function calls as arguments and to array initialization as elements. Using the macro in a null-terminated array allows us to iterate through the array and use them as the names of source files to compile, while using the macro in a function call allows us to link all the object files generated by the compilation.
+[^1]: One example is putting a list of source file names without the `.c` extension in a C macro. We can use it to initialize an array that we iterate through, passing its elements as arguments to `compile()`, but we can also pass the entire macro as arguments to `load()` to link all the object files we just generated, ensuring we don't ever miss one.
 
-`compile()` will only run if it finds the source file has been modified since the last time it compiled the resulting object file. This is similar to the caching behavior in most other build systems.
+If the source files you're compiling use project header files themselves and you wish to trigger recompilation should the header files be modified, you can pass them as additional arguments after the source file. The typical file extension assumed for these arguments is `.h`.
 
-If the source file uses project header files and you wish to trigger recompilation should they be modified, you can add their names after the name of the source file. The typical file extension assumed for these arguments is `.h`. In all cases, whether or not these additional arguments are included, **the arguments passed to `compile()` must be terminated with a null pointer**.
+In all cases, whether or not you pass additional arguments, **the arguments passed to `compile()` must be terminated with a null pointer**.
 
-To set flags to be passed to the compiler, the predefined `cflags` variable is used by setting it equal to an array of C strings which is terminated with a null pointer. Unless reinitialized, the same flags will be used in all subsequent `compile()` calls. `cflags` can be set to a null pointer when no flags are needed.
+To set flags to be passed to the compiler, the predefined `cflags` variable is used by setting it equal to an array of C strings which is also terminated with a null pointer. Unless reinitialized, the same flags will be used in all subsequent `compile()` calls. If no flags are needed, `cflags` can be set to a null pointer.
 
 An example of compiling `main.c` which depends on `foo.h` and `bar.h`. This function call will produce the file `main.o`.
 
@@ -70,13 +87,13 @@ cflags = (char *[]){"-Wall", "-O3", NULL};
 compile("main", "foo", "bar", NULL);
 ```
 
-### Linking
+### Linking object files
 
 ```c
 void load(char type, char *target, char *obj, ...);
 ```
 
-The first argument to `load()`[^2] is the type of the target file. The options are as follows.
+The first argument to `load()`[^2] is the type of the target file. The types are as follows.
 
 [^2]: Although the term "linking" is far more common to use nowadays, the original term when UNIX was first created was "loading," so I use it here to name the function that does the linking. Also the name "link" is already taken on UNIX based systems.
 
@@ -86,34 +103,17 @@ The first argument to `load()`[^2] is the type of the target file. The options a
 'd' - dynamically linked library
 ```
 
-The second argument is the name of the target. There is no assumed typical file extension for the target as executables commonly lack them. It is also common to prepend `lib` to files that are static or dynamic libraries; this is done automatically and in a nature similar to typical file extensions. The idea is similar to the manner in which you would typically pass system libraries to the linker flag `-l`.
+The second argument is the file name of the target. There is no assumed typical file extension for the target as executables commonly lack them. It is also common to prepend `lib` to files that are static or dynamic libraries; this is done automatically by `load()` if necessary. The idea is to replicate the manner in which you would typically pass system libraries to the linker flag `-l`.
 
-The rest of the arguments are the names of the files you want to link together. The typical file extension for these files is `.o`. Generally the only other files that would use a different file extension would be statically linked libraries or dynamically linked libraries (the linker flag `-l` should be used to link system libraries as opposed to project libraries). As is the case with compiling, **the arguments passed to `load()` must be terminated with a null pointer**.
+The rest of the arguments are the names of the files you want to link together. The typical file extension for these files is `.o` since we generally link object files. The only other files that would use a different file extension would be statically linked libraries or dynamically linked libraries (the linker flag `-l` should be used to link system libraries as opposed to project libraries). The `DYEXT` macro has been defined which represents the platform-dependent file extension for dynamic libraries: `".dylib"` for macOS and `".so"` for anything else. This helps ease the portability of build files.
 
-In a similar way as compiling, the predefined `lflags` variable can be used to define flags for the linker.
+As is the case with `compile()`, **the arguments passed to `load()` must be terminated with a null pointer**.
 
-The `DYEXT` macro has been defined which represents the platform-dependent file extension for dynamic libraries, `".dylib"` for macOS and `".so"` for anything else. This can be used to allow portability of build files.
+The predefined `lflags` variable is used to pass flags to the linker, used in a manner similar to the way `cflags` is used for compiling.
 
-An example of linking `liba.dylib` (or `liba.so`), `b.o`, `libc.a`, and the system math library into a statically linked library `libmain.a`.
+An example of linking `liba.so` (or `liba.dylib` on macOS), `b.o`, and `libc.a`, as well as the system math library into the statically linked library `libmain.a`.
 
 ```c
 lflags = (char *[]){"-lm", NULL};
 load('s', "main", "a" DYEXT, "b", "c.a", NULL);
 ```
-
-### Recursive builds
-
-```c
-void build(char *path);
-```
-
-It is often advantageous to compartmentalize projects into a number of subdirectories both for organizational purposes and for rebuilding parts at a time without needing to rebuild the whole thing. The usual way this is done is by placing build scripts in any subdirectory you want to rebuild on its own. These scripts double as both being able to be ran by the programmer from the shell as well as being able to be run by the build system itself from some parent directory. The `build()` function performs the latter function of the build system.
-
-`build()` gets passed the name of the subdirectory you want to build, either as an absolute or relative path. Another philosophy taken by cbs is that **relative paths in a build file are always assumed to be with respect to the directory that that build file is in**. The directory you pass `build()` must have a `build.c` file in it which will be (re)compiled and (re)run. If `path` is a null pointer, this has the effect of staying in the current directory and (again, if necessary) recompiling the build file and rerunning the build executable therein. Putting `build(NULL);` at the start of build files is what allows a build executable to recompile itself in the event its corresponding build file gets changed. In essence, this function is what allows cbs to be truly automated.
-
-An example of building the contents of the directories `abc/src/` and `/usr/local/proj/src/`.
-
-```c
-build("abc/src/");
-build("/usr/local/proj/src/");
-```
diff --git a/cbs.c b/cbs.c
index e6a77f9e700ba162cf97c53521a9bbb87f704e9a..2eaf27662be305e2c7a93514ae315de6c24c3c00 100644 (file)
--- a/cbs.c
+++ b/cbs.c
@@ -1,11 +1,11 @@
 #include <stdarg.h>
 #include <stdio.h>
+#include <unistd.h>
+#include <sys/errno.h>
 #include <stdlib.h>
+#include <sys/wait.h>
 #include <string.h>
-#include <sys/errno.h>
 #include <sys/stat.h>
-#include <sys/wait.h>
-#include <unistd.h>
 
 #ifdef __APPLE__
 #define DYEXT ".dylib"
@@ -30,6 +30,64 @@ void error(char *fmt, ...) {
    exit(EXIT_FAILURE);
 }
 
+void await(pid_t cpid, char *what, char *who) {
+   int status;
+
+   if (cpid == -1) error("Unable to delegate the %s of `%s'", what, who);
+   if (waitpid(cpid, &status, 0) == -1)
+       error("Unable to await the %s of `%s'", what, who);
+   if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS)
+       exit(EXIT_FAILURE);
+}
+
+int modified(char *target, char *dep) {
+   char *ext;
+   struct stat tstat, dstat;
+
+   if ((ext = strrchr(dep, '.')) && strcmp(ext, DYEXT) == 0) return 0;
+
+   if (stat(target, &tstat) == -1) {
+       if (errno == ENOENT) return !(errno = 0);
+       error("Unable to stat `%s'", target);
+   }
+   if (stat(dep, &dstat) == -1) error("Unable to stat `%s'", dep);
+
+   return tstat.st_mtime < dstat.st_mtime;
+}
+
+void run(char *path, char **args, char *what, char *who) {
+   size_t i;
+
+   for (i = 0; args[i]; ++i) printf("%s ", args[i]);
+   printf("\n");
+
+   if (execve(path, args, environ) == -1)
+       error("Unable to run %s of `%s'", what, who);
+}
+
+void build(char *path) {
+   pid_t cpid;
+
+   if (path) {
+       if ((cpid = fork())) {
+           await(cpid, "execution", "build");
+           printf("cd ..\n");
+           return;
+       }
+       printf("cd %s\n", path);
+       if (chdir(path)) error("Unable to set working directory to `%s'", path);
+   }
+
+   if (modified("build", "build.c")) {
+       if ((cpid = fork()) == 0)
+           run("/usr/bin/cc", (char *[]){"cc", "-o", "build", "build.c", NULL},
+               "compilation", "build.c");
+       await(cpid, "compilation", "build.c");
+   } else if (!path) return;
+
+   run("build", (char *[]){"./build", NULL}, "execution", "build");
+}
+
 void *allocate(size_t s) {
    void *r;
 
@@ -69,41 +127,6 @@ char *extend(char *path, char *ext) {
    return r;
 }
 
-int modified(char *target, char *dep) {
-   char *e;
-   struct stat tstat, dstat;
-
-   if ((e = strrchr(dep, '.')) && strcmp(e, DYEXT) == 0) return 0;
-
-   if (stat(target, &tstat) == -1) {
-       if (errno == ENOENT) return !(errno = 0);
-       error("Unable to stat `%s'", target);
-   }
-   if (stat(dep, &dstat) == -1) error("Unable to stat `%s'", dep);
-
-   return tstat.st_mtime < dstat.st_mtime;
-}
-
-void run(char *path, char **args, char *what, char *who) {
-   size_t i;
-
-   for (i = 0; args[i]; ++i) printf("%s ", args[i]);
-   printf("\n");
-
-   if (execve(path, args, environ) == -1)
-       error("Unable to run %s of `%s'", what, who);
-}
-
-void await(pid_t cpid, char *what, char *who) {
-   int status;
-
-   if (cpid == -1) error("Unable to delegate the %s of `%s'", what, who);
-   if (waitpid(cpid, &status, 0) == -1)
-       error("Unable to await the %s of `%s'", what, who);
-   if (WIFEXITED(status) && WEXITSTATUS(status) != EXIT_SUCCESS)
-       exit(EXIT_FAILURE);
-}
-
 void compile(char *src, ...) {
    size_t f;
    char **args, **p, *obj, *hdr;
@@ -184,26 +207,3 @@ void load(char type, char *target, char *obj, ...) {
    while (a < fp) free(*a++);
    free(args);
 }
-
-void build(char *path) {
-   pid_t cpid;
-
-   if (path) {
-       if ((cpid = fork())) {
-           await(cpid, "execution", "build");
-           printf("cd ..\n");
-           return;
-       }
-       printf("cd %s\n", path);
-       if (chdir(path)) error("Unable to set working directory to `%s'", path);
-   }
-
-   if (modified("build", "build.c")) {
-       if ((cpid = fork()) == 0)
-           run("/usr/bin/cc", (char *[]){"cc", "-o", "build", "build.c", NULL},
-               "compilation", "build.c");
-       await(cpid, "compilation", "build.c");
-   } else if (!path) return;
-
-   run("build", (char *[]){"./build", NULL}, "execution", "build");
-}