Implement chaz_MakeFile_add_obj_dir_rule

Add a rule to create object files in a separate directory and/or use
custom compiler flags for all source files in a directory.

This works with nmake and GNU make but isn't POSIX-compatible.
diff --git a/src/Charmonizer/Core/Make.c b/src/Charmonizer/Core/Make.c
index 1063bf3..71820bc 100644
--- a/src/Charmonizer/Core/Make.c
+++ b/src/Charmonizer/Core/Make.c
@@ -544,6 +544,70 @@
     free(src);
 }
 
+chaz_MakeRule*
+chaz_MakeFile_add_obj_dir_rule(chaz_MakeFile *makefile, const char *src_dir,
+                               const char *obj_dir, chaz_CFlags *cflags) {
+    chaz_MakeRule *rule;
+    const char *dir_sep       = chaz_OS_dir_sep();
+    const char *obj_ext       = chaz_CC_obj_ext();
+    const char *mkdir_command = NULL;
+    const char *cflags_string = NULL;
+    char       *target        = NULL;
+    char       *prereq        = NULL;
+    char       *cc_command    = NULL;
+
+    if (obj_dir == NULL) {
+        obj_dir = src_dir;
+    }
+
+    if (strcmp(chaz_Make.make_command, "nmake") == 0) {
+        /* nmake-style search paths in inference rules. */
+        target  = chaz_Util_join("", "{", src_dir, "\\}.c{", obj_dir,
+                                 "\\}", obj_ext, NULL);
+    }
+    else {
+        /* GNU make pattern match. TODO: This is not POSIX-compatible. */
+        target  = chaz_Util_join("", obj_dir, dir_sep, "%", obj_ext, NULL);
+        prereq  = chaz_Util_join(dir_sep, src_dir, "%.c", NULL);
+    }
+
+    rule = chaz_MakeFile_add_rule(makefile, target, prereq);
+
+    if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
+        mkdir_command = "@mkdir -p $$(dirname $@)";
+    }
+    else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
+        mkdir_command = "@for %F in ($@) do @mkdir %~dpF 2>nul";
+    }
+    else {
+        chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
+    }
+
+    if (cflags == NULL) {
+        cflags_string = "$(CFLAGS)";
+    }
+    else {
+        cflags_string = chaz_CFlags_get_string(cflags);
+    }
+
+    if (chaz_CC_msvc_version_num()) {
+        cc_command = chaz_Util_join(" ", "$(CC) /nologo", cflags_string,
+                                    "/c $< /Fo$@", NULL);
+    }
+    else {
+        cc_command = chaz_Util_join(" ", "$(CC)", cflags_string, "-c $< -o $@",
+                                    NULL);
+    }
+
+    chaz_MakeRule_add_command(rule, mkdir_command);
+    chaz_MakeRule_add_command(rule, cc_command);
+
+    free(cc_command);
+    free(prereq);
+    free(target);
+    return rule;
+}
+
 void
 chaz_MakeFile_write(chaz_MakeFile *makefile) {
     FILE   *out;
diff --git a/src/Charmonizer/Core/Make.h b/src/Charmonizer/Core/Make.h
index 50fc301..cf8c78f 100644
--- a/src/Charmonizer/Core/Make.h
+++ b/src/Charmonizer/Core/Make.h
@@ -191,6 +191,17 @@
 chaz_MakeFile_override_cflags(chaz_MakeFile *makefile, const char *obj,
                               chaz_CFlags *cflags);
 
+/** Add a rule to create object files in a separate directory and/or use
+ * custom compiler flags for all source files in a directory.
+ *
+ * @param src_dir The root directory of source files.
+ * @param obj_dir The directory for object files. Same as `src_dir` if NULL.
+ * @param cflags Compiler flags. Use `$(CFLAGS)` if NULL.
+ */
+chaz_MakeRule*
+chaz_MakeFile_add_obj_dir_rule(chaz_MakeFile *makefile, const char *src_dir,
+                               const char *obj_dir, chaz_CFlags *cflags);
+
 /** Write the makefile to a file named 'Makefile' in the current directory.
  *
  * @param makefile The makefile.