├── src ├── templates │ ├── autogen.sh.template │ ├── Makefile.am.template │ ├── src_Makefile.am.template │ ├── main.c.template │ ├── README.md.template │ └── configure.ac.template ├── Makefile.am ├── jc.h ├── utils.h ├── cmd_install.c ├── cmd_build.c ├── cmd_run.c ├── main.c ├── cmd_bt.c ├── cmd_clean.c ├── cmd_new.c ├── utils.c ├── cmd_add.c └── cmd_test.c ├── Makefile.am ├── autogen.sh ├── tests ├── Makefile.am └── test_utils.c ├── .gitignore ├── LICENSE ├── configure.ac ├── README.md ├── BUILD.md ├── QUICKSTART.md ├── test-driver ├── PROJECT_SUMMARY.md ├── ar-lib ├── ARCHITECTURE.md └── config.sub /src/templates/autogen.sh.template: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | autoreconf --install 3 | -------------------------------------------------------------------------------- /src/templates/Makefile.am.template: -------------------------------------------------------------------------------- 1 | SUBDIRS = src 2 | 3 | ACLOCAL_AMFLAGS = -I m4 4 | 5 | EXTRA_DIST = README.md 6 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = src 2 | if ENABLE_TESTS 3 | SUBDIRS += tests 4 | endif 5 | 6 | ACLOCAL_AMFLAGS = -I m4 7 | 8 | EXTRA_DIST = README.md autogen.sh 9 | -------------------------------------------------------------------------------- /src/templates/src_Makefile.am.template: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = PROJECT_NAME 2 | 3 | PROJECT_NAME_SOURCES = main.c 4 | PROJECT_NAME_CFLAGS = -Wall -Wextra -std=c11 -g 5 | -------------------------------------------------------------------------------- /src/templates/main.c.template: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | printf("Hello from PROJECT_NAME!\n"); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Generating build system..." 4 | 5 | # Create m4 directory if it doesn't exist 6 | mkdir -p m4 7 | 8 | # Run autoreconf 9 | autoreconf --install --force 10 | 11 | echo "Done. You can now run ./configure" 12 | -------------------------------------------------------------------------------- /src/templates/README.md.template: -------------------------------------------------------------------------------- 1 | # PROJECT_NAME 2 | 3 | A C project created with jc. 4 | 5 | ## Building 6 | 7 | ```bash 8 | jc build 9 | ``` 10 | 11 | ## Running 12 | 13 | ```bash 14 | jc run 15 | ``` 16 | 17 | ## Installing 18 | 19 | ```bash 20 | jc install 21 | ``` 22 | -------------------------------------------------------------------------------- /tests/Makefile.am: -------------------------------------------------------------------------------- 1 | if ENABLE_TESTS 2 | 3 | # Check framework based tests 4 | check_PROGRAMS = test_jc 5 | 6 | test_jc_SOURCES = \ 7 | test_utils.c \ 8 | ../src/utils.c 9 | 10 | test_jc_CFLAGS = -I$(top_srcdir)/src $(CHECK_CFLAGS) -Wall -Wextra -g 11 | test_jc_LDADD = $(CHECK_LIBS) 12 | 13 | TESTS = test_jc 14 | 15 | endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Automake/Autoconf 2 | Makefile 3 | Makefile.in 4 | aclocal.m4 5 | autom4te.cache/ 6 | compile 7 | config.h 8 | config.h.in 9 | config.log 10 | config.status 11 | configure 12 | depcomp 13 | install-sh 14 | missing 15 | stamp-h1 16 | .deps/ 17 | .dirstamp 18 | m4/ 19 | 20 | # Build artifacts 21 | *.o 22 | *.a 23 | *.so 24 | *.dylib 25 | src/jc 26 | 27 | # Test artifacts 28 | tests/test_jc 29 | test_*/ 30 | *.log 31 | *.trs 32 | 33 | # Debug 34 | *.dSYM/ 35 | core 36 | vgcore.* 37 | 38 | # Editor 39 | .vscode/ 40 | .idea/ 41 | *.swp 42 | *~ 43 | 44 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = jc 2 | 3 | jc_SOURCES = \ 4 | main.c \ 5 | cmd_new.c \ 6 | cmd_add.c \ 7 | cmd_build.c \ 8 | cmd_run.c \ 9 | cmd_install.c \ 10 | cmd_clean.c \ 11 | cmd_bt.c \ 12 | cmd_test.c \ 13 | utils.c \ 14 | jc.h \ 15 | utils.h 16 | 17 | jc_CFLAGS = -Wall -Wextra -std=c11 -g -O2 18 | 19 | # Install templates 20 | templatesdir = $(datadir)/jc/templates 21 | dist_templates_DATA = \ 22 | templates/configure.ac.template \ 23 | templates/Makefile.am.template \ 24 | templates/src_Makefile.am.template \ 25 | templates/main.c.template \ 26 | templates/README.md.template \ 27 | templates/autogen.sh.template 28 | -------------------------------------------------------------------------------- /src/jc.h: -------------------------------------------------------------------------------- 1 | #ifndef JC_H 2 | #define JC_H 3 | 4 | #define _POSIX_C_SOURCE 200809L 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Command function prototypes 14 | int cmd_new(int argc, char *argv[]); 15 | int cmd_build(int argc, char *argv[]); 16 | int cmd_run(int argc, char *argv[]); 17 | int cmd_install(int argc, char *argv[]); 18 | int cmd_bt(int argc, char *argv[]); 19 | int cmd_clean(int argc, char *argv[]); 20 | int cmd_add(int argc, char *argv[]); 21 | int cmd_test(int argc, char *argv[]); 22 | 23 | // Version info 24 | #define JC_VERSION "1.0.0" 25 | 26 | #endif // JC_H 27 | -------------------------------------------------------------------------------- /src/templates/configure.ac.template: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.69]) 2 | AC_INIT([PROJECT_NAME], [1.0.0], [support@example.com]) 3 | AM_INIT_AUTOMAKE([-Wall -Werror foreign]) 4 | AC_CONFIG_SRCDIR([src/main.c]) 5 | AC_CONFIG_HEADERS([config.h]) 6 | 7 | AC_PROG_CC 8 | 9 | # Check for Check testing framework (optional for testing) 10 | AC_ARG_ENABLE([tests], 11 | AS_HELP_STRING([--enable-tests], [Enable building tests with Check framework]), 12 | [enable_tests=$enableval], 13 | [enable_tests=no]) 14 | 15 | if test "x$enable_tests" = "xyes"; then 16 | PKG_CHECK_MODULES([CHECK], [check >= 0.9.4]) 17 | fi 18 | 19 | AM_CONDITIONAL([ENABLE_TESTS], [test "x$enable_tests" = "xyes"]) 20 | 21 | AC_CHECK_HEADERS([stdlib.h string.h]) 22 | 23 | AC_TYPE_SIZE_T 24 | 25 | AC_FUNC_MALLOC 26 | 27 | AC_CONFIG_FILES([ 28 | Makefile 29 | src/Makefile 30 | ]) 31 | 32 | AC_OUTPUT 33 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Utility function prototypes 12 | int create_directory(const char *path); 13 | int file_exists(const char *path); 14 | int directory_exists(const char *path); 15 | int copy_file(const char *src, const char *dst); 16 | int write_file(const char *path, const char *content); 17 | char *read_file(const char *path); 18 | int execute_command(const char *cmd); 19 | int execute_command_quiet(const char *cmd); 20 | char *get_template_path(const char *template_name); 21 | int is_automake_project(void); 22 | int find_executable(const char *dir, char *output, size_t output_size); 23 | char *regex_replace(const char *input, const char *pattern, const char *replacement); 24 | 25 | #endif // UTILS_H 26 | -------------------------------------------------------------------------------- /src/cmd_install.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | 4 | int cmd_install(int argc, char *argv[]) { 5 | (void)argc; // unused 6 | (void)argv; // unused 7 | 8 | if (!is_automake_project()) { 9 | fprintf(stderr, "Error: Not in an automake project directory\n"); 10 | return 1; 11 | } 12 | 13 | // Ensure project is built 14 | if (!file_exists("Makefile")) { 15 | printf("Project not configured. Building first...\n"); 16 | if (cmd_build(0, NULL) != 0) { 17 | return 1; 18 | } 19 | } 20 | 21 | printf("Installing project...\n\n"); 22 | 23 | // Run make install 24 | if (execute_command("make install") != 0) { 25 | fprintf(stderr, "\nError: Installation failed\n"); 26 | fprintf(stderr, "You may need to run with sudo: sudo jc install\n"); 27 | return 1; 28 | } 29 | 30 | printf("\n✓ Installation completed successfully!\n"); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 jc contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/cmd_build.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | 4 | int cmd_build(int argc, char *argv[]) { 5 | (void)argc; // unused 6 | (void)argv; // unused 7 | 8 | if (!is_automake_project()) { 9 | fprintf(stderr, "Error: Not in an automake project directory\n"); 10 | fprintf(stderr, "Please run this command in a directory containing configure.ac\n"); 11 | return 1; 12 | } 13 | 14 | printf("Building project...\n\n"); 15 | 16 | // Check if configure script exists 17 | if (!file_exists("configure")) { 18 | printf("Running autogen.sh to generate configure script...\n"); 19 | 20 | // Try autogen.sh if it exists 21 | if (file_exists("autogen.sh")) { 22 | if (execute_command("./autogen.sh") != 0) { 23 | fprintf(stderr, "Error: autogen.sh failed\n"); 24 | return 1; 25 | } 26 | } else { 27 | // Fall back to autoreconf 28 | if (execute_command("autoreconf --install") != 0) { 29 | fprintf(stderr, "Error: autoreconf failed\n"); 30 | return 1; 31 | } 32 | } 33 | printf("\n"); 34 | } 35 | 36 | // Check if Makefile exists (need to run configure) 37 | if (!file_exists("Makefile")) { 38 | printf("Running configure...\n"); 39 | if (execute_command("./configure") != 0) { 40 | fprintf(stderr, "Error: configure failed\n"); 41 | return 1; 42 | } 43 | printf("\n"); 44 | } 45 | 46 | // Run make 47 | printf("Running make...\n"); 48 | if (execute_command("make") != 0) { 49 | fprintf(stderr, "Error: make failed\n"); 50 | return 1; 51 | } 52 | 53 | printf("\n✓ Build completed successfully!\n"); 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.69]) 2 | AC_INIT([jc], [1.0.0], [oxUnd]) 3 | AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) 4 | AC_CONFIG_SRCDIR([src/main.c]) 5 | AC_CONFIG_HEADERS([config.h]) 6 | 7 | # Checks for programs 8 | AC_PROG_CC 9 | AM_PROG_AR 10 | AC_PROG_RANLIB 11 | 12 | # Check for required programs 13 | AC_CHECK_PROG([AUTOCONF], [autoconf], [autoconf], [no]) 14 | AC_CHECK_PROG([AUTOMAKE], [automake], [automake], [no]) 15 | AC_CHECK_PROG([MAKE], [make], [make], [no]) 16 | 17 | if test "$AUTOCONF" = "no"; then 18 | AC_MSG_ERROR([autoconf is required but not found]) 19 | fi 20 | 21 | if test "$AUTOMAKE" = "no"; then 22 | AC_MSG_ERROR([automake is required but not found]) 23 | fi 24 | 25 | # Check for debugger 26 | AC_CHECK_PROG([LLDB], [lldb], [lldb], [no]) 27 | AC_CHECK_PROG([GDB], [gdb], [gdb], [no]) 28 | 29 | if test "$LLDB" != "no"; then 30 | AC_DEFINE([HAVE_LLDB], [1], [Define if lldb is available]) 31 | AC_SUBST([DEBUGGER], [lldb]) 32 | elif test "$GDB" != "no"; then 33 | AC_DEFINE([HAVE_GDB], [1], [Define if gdb is available]) 34 | AC_SUBST([DEBUGGER], [gdb]) 35 | fi 36 | 37 | # Check for Check testing framework (optional for testing) 38 | AC_ARG_ENABLE([tests], 39 | AS_HELP_STRING([--enable-tests], [Enable building tests with Check framework]), 40 | [enable_tests=$enableval], 41 | [enable_tests=no]) 42 | 43 | if test "x$enable_tests" = "xyes"; then 44 | PKG_CHECK_MODULES([CHECK], [check >= 0.9.4]) 45 | fi 46 | 47 | AM_CONDITIONAL([ENABLE_TESTS], [test "x$enable_tests" = "xyes"]) 48 | 49 | # Checks for header files 50 | AC_CHECK_HEADERS([stdlib.h string.h unistd.h sys/stat.h sys/types.h]) 51 | 52 | # Checks for typedefs, structures, and compiler characteristics 53 | AC_TYPE_SIZE_T 54 | 55 | # Checks for library functions 56 | AC_FUNC_MALLOC 57 | AC_CHECK_FUNCS([mkdir strdup]) 58 | 59 | AC_CONFIG_FILES([ 60 | Makefile 61 | src/Makefile 62 | tests/Makefile 63 | ]) 64 | 65 | AC_OUTPUT 66 | -------------------------------------------------------------------------------- /src/cmd_run.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | #include 4 | 5 | #ifndef PATH_MAX 6 | #define PATH_MAX 4096 7 | #endif 8 | 9 | int cmd_run(int argc, char *argv[]) { 10 | if (!is_automake_project()) { 11 | fprintf(stderr, "Error: Not in an automake project directory\n"); 12 | return 1; 13 | } 14 | 15 | // First, ensure the project is built 16 | if (!file_exists("Makefile")) { 17 | printf("Project not built yet. Building first...\n"); 18 | if (cmd_build(0, NULL) != 0) { 19 | return 1; 20 | } 21 | } 22 | 23 | // Find the executable in src/ directory 24 | char executable[PATH_MAX]; 25 | 26 | // Try to find executable in common locations 27 | const char *search_dirs[] = { 28 | "src/build", 29 | "src", 30 | ".", 31 | NULL 32 | }; 33 | 34 | int found = 0; 35 | for (int i = 0; search_dirs[i] != NULL; i++) { 36 | if (find_executable(search_dirs[i], executable, sizeof(executable)) == 0) { 37 | found = 1; 38 | break; 39 | } 40 | } 41 | 42 | if (!found) { 43 | fprintf(stderr, "Error: Could not find executable to run\n"); 44 | fprintf(stderr, "Make sure the project is built successfully\n"); 45 | return 1; 46 | } 47 | 48 | printf("Running: %s\n", executable); 49 | printf("----------------------------------------\n"); 50 | 51 | // Build command with any additional arguments 52 | char cmd[PATH_MAX * 2]; 53 | int offset = snprintf(cmd, sizeof(cmd), "%s", executable); 54 | 55 | // Pass through any additional arguments 56 | for (int i = 1; i < argc; i++) { 57 | offset += snprintf(cmd + offset, sizeof(cmd) - offset, " %s", argv[i]); 58 | } 59 | 60 | // Execute the program 61 | int ret = system(cmd); 62 | 63 | printf("----------------------------------------\n"); 64 | 65 | if (ret != 0) { 66 | printf("Program exited with code: %d\n", ret); 67 | if (ret == 139 || ret == 11 || ret / 256 == 139 || ret / 256 == 11) { 68 | printf("\nSegmentation fault detected!\n"); 69 | printf("Run 'jc bt' to debug the issue\n"); 70 | } else if (ret == 134 || ret == 6 || ret / 256 == 134 || ret / 256 == 6) { 71 | printf("\nAbort signal detected!\n"); 72 | printf("Run 'jc bt' to debug the issue\n"); 73 | } 74 | return 1; 75 | } 76 | 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | 4 | void print_usage(const char *program_name) { 5 | printf("jc - A modern C project management tool\n"); 6 | printf("Version: %s\n\n", JC_VERSION); 7 | printf("Usage: %s [options]\n\n", program_name); 8 | printf("Commands:\n"); 9 | printf(" new Create a new automake project\n"); 10 | printf(" add Add files, directories, or dependencies\n"); 11 | printf(" build Build the current project\n"); 12 | printf(" run Run the current project\n"); 13 | printf(" install Install the current project\n"); 14 | printf(" clean Clean build artifacts\n"); 15 | printf(" test Manage and run tests\n"); 16 | printf(" bt Show backtrace (debug crashed program)\n"); 17 | printf(" help Show this help message\n"); 18 | printf(" version Show version information\n"); 19 | printf("\n"); 20 | } 21 | 22 | void print_version(void) { 23 | printf("jc version %s\n", JC_VERSION); 24 | } 25 | 26 | int main(int argc, char *argv[]) { 27 | if (argc < 2) { 28 | print_usage(argv[0]); 29 | return 1; 30 | } 31 | 32 | const char *command = argv[1]; 33 | 34 | if (strcmp(command, "new") == 0) { 35 | return cmd_new(argc - 1, argv + 1); 36 | } else if (strcmp(command, "add") == 0) { 37 | return cmd_add(argc - 1, argv + 1); 38 | } else if (strcmp(command, "build") == 0) { 39 | return cmd_build(argc - 1, argv + 1); 40 | } else if (strcmp(command, "run") == 0) { 41 | return cmd_run(argc - 1, argv + 1); 42 | } else if (strcmp(command, "install") == 0) { 43 | return cmd_install(argc - 1, argv + 1); 44 | } else if (strcmp(command, "clean") == 0) { 45 | return cmd_clean(argc - 1, argv + 1); 46 | } else if (strcmp(command, "test") == 0) { 47 | return cmd_test(argc - 1, argv + 1); 48 | } else if (strcmp(command, "bt") == 0) { 49 | return cmd_bt(argc - 1, argv + 1); 50 | } else if (strcmp(command, "help") == 0 || strcmp(command, "--help") == 0 || strcmp(command, "-h") == 0) { 51 | print_usage(argv[0]); 52 | return 0; 53 | } else if (strcmp(command, "version") == 0 || strcmp(command, "--version") == 0 || strcmp(command, "-v") == 0) { 54 | print_version(); 55 | return 0; 56 | } else { 57 | fprintf(stderr, "Unknown command: %s\n\n", command); 58 | print_usage(argv[0]); 59 | return 1; 60 | } 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jc - A Modern C Project Management Tool 2 | 3 | `jc` is a friendly command-line tool for managing C projects using automake. It provides modern, intuitive commands while leveraging the power of the automake/autoconf toolchain. 4 | 5 | ## Features 6 | 7 | - 🚀 Quickly initialize C projects with proper automake structure 8 | - 🔨 Simple build commands that handle configuration automatically 9 | - ▶️ Easy project execution with debugging support 10 | - 🐛 Integrated debugger support (lldb on macOS, gdb on Linux) 11 | - 📦 Streamlined installation workflow 12 | 13 | ## Quick Start 14 | 15 | See [QUICKSTART.md](QUICKSTART.md) for a quick start guide. 16 | 17 | ## Installation 18 | 19 | See [BUILD.md](BUILD.md) for detailed build and installation instructions. 20 | 21 | Quick install: 22 | ```bash 23 | ./autogen.sh 24 | ./configure 25 | make 26 | sudo make install 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### Create a new project 32 | ```bash 33 | jc new myproject 34 | cd myproject 35 | ``` 36 | 37 | ### Build the project 38 | ```bash 39 | jc build 40 | ``` 41 | 42 | This command automatically: 43 | - Runs `autogen.sh` if needed 44 | - Runs `./configure` if needed 45 | - Runs `make` 46 | 47 | ### Run the project 48 | ```bash 49 | jc run 50 | ``` 51 | 52 | Runs the built executable with any additional arguments: 53 | ```bash 54 | jc run --arg1 --arg2 55 | ``` 56 | 57 | ### Install the project 58 | ```bash 59 | jc install 60 | # or with sudo if needed 61 | sudo jc install 62 | ``` 63 | 64 | ### Debug with backtrace 65 | ```bash 66 | jc bt 67 | ``` 68 | 69 | When your program crashes, use this command to debug it. It will: 70 | - Use `lldb` on macOS 71 | - Use `gdb` on Linux/Windows 72 | - Load core dumps if available 73 | - Show backtrace automatically 74 | 75 | ## Examples 76 | 77 | Create and run a new project: 78 | ```bash 79 | jc new hello-world 80 | cd hello-world 81 | jc build 82 | jc run 83 | ``` 84 | 85 | Debug a crashing program: 86 | ```bash 87 | jc run # Program crashes 88 | jc bt # Automatically debug with lldb/gdb 89 | ``` 90 | 91 | ## Project Structure 92 | 93 | When you create a new project with `jc new`, it generates: 94 | ``` 95 | myproject/ 96 | ├── configure.ac # Autoconf configuration 97 | ├── Makefile.am # Top-level automake file 98 | ├── autogen.sh # Script to generate configure 99 | ├── src/ 100 | │ ├── Makefile.am # Source directory automake file 101 | │ └── main.c # Main program source 102 | ├── README.md # Project documentation 103 | └── .gitignore # Git ignore file 104 | ``` 105 | 106 | ## Requirements 107 | 108 | - autoconf (>= 2.69) 109 | - automake (>= 1.15) 110 | - C compiler with C11 support (gcc/clang) 111 | - lldb (macOS) or gdb (Linux) for debugging 112 | 113 | ## Development 114 | 115 | This project uses: 116 | - **Language**: C11 117 | - **Build System**: automake/autoconf 118 | - **Testing**: Check (libcheck) 119 | 120 | To build with tests: 121 | ```bash 122 | ./configure --enable-tests 123 | make 124 | make check 125 | ``` 126 | 127 | ## License 128 | 129 | MIT License - see LICENSE file for details 130 | 131 | ## Contributing 132 | 133 | Contributions are welcome! Please feel free to submit issues or pull requests. -------------------------------------------------------------------------------- /BUILD.md: -------------------------------------------------------------------------------- 1 | # Building jc 2 | 3 | This document describes how to build and install the `jc` tool. 4 | 5 | ## Prerequisites 6 | 7 | Before building, ensure you have the following tools installed: 8 | 9 | - **autoconf** (>= 2.69) 10 | - **automake** (>= 1.15) 11 | - **make** 12 | - **gcc** or **clang** (C11 support) 13 | - **Check** (libcheck, optional, for running tests) 14 | 15 | ### Installing Prerequisites 16 | 17 | #### macOS (using Homebrew) 18 | ```bash 19 | brew install autoconf automake check 20 | ``` 21 | 22 | #### Ubuntu/Debian 23 | ```bash 24 | sudo apt-get install autoconf automake build-essential check 25 | ``` 26 | 27 | #### Fedora/RHEL/CentOS 28 | ```bash 29 | sudo dnf install autoconf automake gcc check-devel 30 | ``` 31 | 32 | ## Quick Build 33 | 34 | ```bash 35 | ./autogen.sh 36 | ./configure 37 | make 38 | sudo make install 39 | ``` 40 | 41 | ## Detailed Build Steps 42 | 43 | ### 1. Generate Configuration Script 44 | 45 | Run the `autogen.sh` script to generate the `configure` script: 46 | 47 | ```bash 48 | ./autogen.sh 49 | ``` 50 | 51 | This runs `autoreconf --install` to generate all necessary build files. 52 | 53 | ### 2. Configure the Build 54 | 55 | ```bash 56 | ./configure 57 | ``` 58 | 59 | Options: 60 | - `--prefix=/custom/path` - Install to a custom location (default: `/usr/local`) 61 | - `--enable-tests` - Enable building of test suite 62 | 63 | Example: 64 | ```bash 65 | ./configure --prefix=$HOME/.local --enable-tests 66 | ``` 67 | 68 | ### 3. Build 69 | 70 | ```bash 71 | make 72 | ``` 73 | 74 | This compiles the `jc` binary in the `src/` directory. 75 | 76 | ### 4. Run Tests (Optional) 77 | 78 | If you configured with `--enable-tests`: 79 | 80 | ```bash 81 | make check 82 | ``` 83 | 84 | ### 5. Install 85 | 86 | ```bash 87 | sudo make install 88 | ``` 89 | 90 | Or without sudo if you configured with a user-writable prefix: 91 | ```bash 92 | make install 93 | ``` 94 | 95 | ## Development Build 96 | 97 | For development, you might want to use the tool without installing: 98 | 99 | ```bash 100 | ./autogen.sh 101 | ./configure 102 | make 103 | # Use directly from src/ 104 | ./src/jc --help 105 | ``` 106 | 107 | Or add to your PATH temporarily: 108 | ```bash 109 | export PATH="$PWD/src:$PATH" 110 | jc --help 111 | ``` 112 | 113 | ## Cleaning Build Files 114 | 115 | Remove compiled files: 116 | ```bash 117 | make clean 118 | ``` 119 | 120 | Remove all generated files (back to fresh checkout): 121 | ```bash 122 | make distclean 123 | ``` 124 | 125 | ## Troubleshooting 126 | 127 | ### autogen.sh fails 128 | - Ensure `autoconf` and `automake` are installed 129 | - Try: `autoreconf --install --force` 130 | 131 | ### configure fails 132 | - Check that all prerequisites are installed 133 | - Read the error message in `config.log` 134 | 135 | ### make fails 136 | - Ensure you have a C11-compatible compiler 137 | - Check compiler error messages 138 | 139 | ### Tests fail to build 140 | - Check framework (libcheck) must be installed 141 | - Make sure you ran `./configure --enable-tests` 142 | - Install Check using your package manager (see Prerequisites section) 143 | 144 | ### Installation permission denied 145 | - Use `sudo make install` 146 | - Or configure with `--prefix=$HOME/.local` and install without sudo 147 | -------------------------------------------------------------------------------- /QUICKSTART.md: -------------------------------------------------------------------------------- 1 | # Quick Start Guide 2 | 3 | ## Build and Install jc 4 | 5 | ```bash 6 | # Generate build system 7 | ./autogen.sh 8 | 9 | # Configure 10 | ./configure 11 | 12 | # Build 13 | make 14 | 15 | # Install (requires sudo) 16 | sudo make install 17 | 18 | # Or install to custom location 19 | ./configure --prefix=$HOME/.local 20 | make 21 | make install 22 | ``` 23 | 24 | ## Using jc 25 | 26 | ### 1. Create a New Project 27 | 28 | ```bash 29 | jc new myproject 30 | cd myproject 31 | ``` 32 | 33 | This creates a complete automake project structure with: 34 | - `configure.ac` - Autoconf configuration 35 | - `Makefile.am` - Top-level build rules 36 | - `src/` - Source code directory 37 | - `src/main.c` - Sample main program 38 | - `autogen.sh` - Script to generate configure 39 | - `.gitignore` - Git ignore rules 40 | - `README.md` - Project documentation 41 | 42 | ### 2. Build Your Project 43 | 44 | ```bash 45 | jc build 46 | ``` 47 | 48 | This command automatically: 49 | 1. Runs `autogen.sh` if `configure` doesn't exist 50 | 2. Runs `./configure` if `Makefile` doesn't exist 51 | 3. Runs `make` to build the project 52 | 53 | No need to remember multiple commands! 54 | 55 | ### 3. Run Your Project 56 | 57 | ```bash 58 | jc run 59 | ``` 60 | 61 | This finds and executes your built program. You can also pass arguments: 62 | 63 | ```bash 64 | jc run --arg1 --arg2 65 | ``` 66 | 67 | ### 4. Debug Crashes 68 | 69 | If your program crashes: 70 | 71 | ```bash 72 | jc bt 73 | ``` 74 | 75 | This automatically: 76 | - Uses `lldb` on macOS or `gdb` on Linux 77 | - Loads core dumps if available 78 | - Shows backtrace to help you debug 79 | 80 | Example debugging session: 81 | ```bash 82 | $ jc run 83 | Segmentation fault: 11 84 | 85 | $ jc bt 86 | Using lldb debugger... 87 | Executable: src/myproject 88 | Running with lldb... 89 | ---------------------------------------- 90 | (lldb) bt 91 | * thread #1: tid = 0x1234, 0x0000000100000f50 myproject`main + 16 92 | * frame #0: 0x0000000100000f50 myproject`main + 16 93 | frame #1: 0x00007fff5fc5d7fd libdyld.dylib`start + 1 94 | ``` 95 | 96 | ### 5. Install Your Project 97 | 98 | ```bash 99 | # System-wide installation (needs sudo) 100 | sudo jc install 101 | 102 | # Or if configured with --prefix=$HOME/.local 103 | jc install 104 | ``` 105 | 106 | ## Complete Example 107 | 108 | Create, build, and run a new project: 109 | 110 | ```bash 111 | # Create project 112 | jc new hello-world 113 | cd hello-world 114 | 115 | # Edit the source code 116 | vim src/main.c 117 | 118 | # Build it 119 | jc build 120 | 121 | # Run it 122 | jc run 123 | 124 | # If it crashes, debug it 125 | jc bt 126 | ``` 127 | 128 | ## Working with Existing Code 129 | 130 | If you have an existing automake project, you can use `jc` commands directly: 131 | 132 | ```bash 133 | cd my-existing-project 134 | jc build # Build the project 135 | jc run # Run the executable 136 | ``` 137 | 138 | ## Tips 139 | 140 | 1. **Development workflow**: Just use `jc build && jc run` repeatedly 141 | 2. **Clean build**: Run `make clean` or `make distclean` when needed 142 | 3. **Add files**: Edit `src/Makefile.am` to add more source files 143 | 4. **Dependencies**: Edit `configure.ac` to check for libraries 144 | 5. **Project name with dashes**: Automatically converted (e.g., `my-project` → `my_project` in automake variables) 145 | 146 | ## Getting Help 147 | 148 | ```bash 149 | jc help # Show help 150 | jc version # Show version 151 | jc --help # Same as help 152 | ``` 153 | 154 | ## Next Steps 155 | 156 | - Read [BUILD.md](BUILD.md) for detailed build instructions 157 | - Read [README.md](README.md) for full documentation 158 | - Check out the automake manual: https://www.gnu.org/software/automake/manual/ 159 | - Check out the autoconf manual: https://www.gnu.org/software/autoconf/manual/ 160 | -------------------------------------------------------------------------------- /src/cmd_bt.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | #include 4 | 5 | #ifndef PATH_MAX 6 | #define PATH_MAX 4096 7 | #endif 8 | 9 | #ifdef __APPLE__ 10 | #define HAVE_LLDB 1 11 | #define DEBUGGER "lldb" 12 | #else 13 | #ifdef __linux__ 14 | #define HAVE_GDB 1 15 | #define DEBUGGER "gdb" 16 | #else 17 | #define DEBUGGER "gdb" 18 | #endif 19 | #endif 20 | 21 | #ifdef HAVE_LLDB 22 | static void print_lldb_usage(const char *executable) { 23 | printf("\nTo debug with lldb:\n"); 24 | printf(" lldb %s\n", executable); 25 | printf(" (lldb) run\n"); 26 | printf(" ... program crashes ...\n"); 27 | printf(" (lldb) bt # Show backtrace\n"); 28 | printf(" (lldb) frame select # Select a frame\n"); 29 | printf(" (lldb) print # Print variable\n"); 30 | printf(" (lldb) quit\n"); 31 | printf("\nOr run directly with backtrace:\n"); 32 | printf(" lldb -o run -o bt %s\n", executable); 33 | } 34 | #endif 35 | 36 | #ifdef HAVE_GDB 37 | static void print_gdb_usage(const char *executable) { 38 | printf("\nTo debug with gdb:\n"); 39 | printf(" gdb %s\n", executable); 40 | printf(" (gdb) run\n"); 41 | printf(" ... program crashes ...\n"); 42 | printf(" (gdb) bt # Show backtrace\n"); 43 | printf(" (gdb) frame # Select a frame\n"); 44 | printf(" (gdb) print # Print variable\n"); 45 | printf(" (gdb) quit\n"); 46 | printf("\nOr run directly with backtrace:\n"); 47 | printf(" gdb -ex run -ex bt %s\n", executable); 48 | } 49 | #endif 50 | 51 | int cmd_bt(int argc, char *argv[]) { 52 | if (!is_automake_project()) { 53 | fprintf(stderr, "Error: Not in an automake project directory\n"); 54 | return 1; 55 | } 56 | 57 | // Ensure project is built 58 | if (!file_exists("Makefile")) { 59 | printf("Project not built. Building first...\n"); 60 | if (cmd_build(0, NULL) != 0) { 61 | return 1; 62 | } 63 | } 64 | 65 | // Find the executable 66 | char executable[PATH_MAX]; 67 | const char *search_dirs[] = {"src/build", "src", ".", NULL}; 68 | 69 | int found = 0; 70 | for (int i = 0; search_dirs[i] != NULL; i++) { 71 | if (find_executable(search_dirs[i], executable, sizeof(executable)) == 0) { 72 | found = 1; 73 | break; 74 | } 75 | } 76 | 77 | if (!found) { 78 | fprintf(stderr, "Error: Could not find executable\n"); 79 | return 1; 80 | } 81 | 82 | // Check if core dump exists 83 | int has_core = file_exists("core"); 84 | 85 | #ifdef HAVE_LLDB 86 | printf("Using lldb debugger...\n"); 87 | printf("Executable: %s\n", executable); 88 | 89 | if (has_core) { 90 | printf("Core dump found: core\n"); 91 | printf("\nLoading core dump in lldb...\n"); 92 | 93 | char cmd[PATH_MAX * 2]; 94 | snprintf(cmd, sizeof(cmd), "lldb %s -c core", executable); 95 | 96 | printf("Run 'bt' in lldb to see the backtrace\n"); 97 | printf("----------------------------------------\n"); 98 | return system(cmd) == 0 ? 0 : 1; 99 | } else { 100 | // Run with debugger 101 | char cmd[PATH_MAX * 2]; 102 | int offset = snprintf(cmd, sizeof(cmd), "lldb -o run -o bt"); 103 | 104 | // Add any additional arguments 105 | for (int i = 1; i < argc; i++) { 106 | offset += snprintf(cmd + offset, sizeof(cmd) - offset, " -o 'settings set target.run-args %s'", argv[i]); 107 | } 108 | 109 | offset += snprintf(cmd + offset, sizeof(cmd) - offset, " %s", executable); 110 | 111 | printf("\nRunning with lldb...\n"); 112 | printf("----------------------------------------\n"); 113 | int ret = system(cmd); 114 | 115 | if (ret != 0) { 116 | print_lldb_usage(executable); 117 | } 118 | return ret == 0 ? 0 : 1; 119 | } 120 | #elif defined(HAVE_GDB) 121 | printf("Using gdb debugger...\n"); 122 | printf("Executable: %s\n", executable); 123 | 124 | if (has_core) { 125 | printf("Core dump found: core\n"); 126 | printf("\nLoading core dump in gdb...\n"); 127 | 128 | char cmd[PATH_MAX * 2]; 129 | snprintf(cmd, sizeof(cmd), "gdb %s core", executable); 130 | 131 | printf("Run 'bt' in gdb to see the backtrace\n"); 132 | printf("----------------------------------------\n"); 133 | return system(cmd) == 0 ? 0 : 1; 134 | } else { 135 | // Run with debugger 136 | char cmd[PATH_MAX * 2]; 137 | snprintf(cmd, sizeof(cmd), "gdb -ex run -ex bt %s", executable); 138 | 139 | printf("\nRunning with gdb...\n"); 140 | printf("----------------------------------------\n"); 141 | int ret = system(cmd); 142 | 143 | if (ret != 0) { 144 | print_gdb_usage(executable); 145 | } 146 | return ret == 0 ? 0 : 1; 147 | } 148 | #else 149 | fprintf(stderr, "Error: No debugger found (lldb or gdb required)\n"); 150 | return 1; 151 | #endif 152 | } 153 | -------------------------------------------------------------------------------- /src/cmd_clean.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | #include 4 | 5 | // Helper function to remove a directory recursively 6 | static int remove_directory(const char *path) { 7 | DIR *d = opendir(path); 8 | if (!d) { 9 | return 0; // Directory doesn't exist or can't be opened 10 | } 11 | 12 | struct dirent *entry; 13 | char filepath[1024]; 14 | 15 | while ((entry = readdir(d)) != NULL) { 16 | if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { 17 | continue; 18 | } 19 | 20 | snprintf(filepath, sizeof(filepath), "%s/%s", path, entry->d_name); 21 | 22 | struct stat st; 23 | if (stat(filepath, &st) == 0) { 24 | if (S_ISDIR(st.st_mode)) { 25 | remove_directory(filepath); 26 | } else { 27 | unlink(filepath); 28 | } 29 | } 30 | } 31 | 32 | closedir(d); 33 | rmdir(path); 34 | return 0; 35 | } 36 | 37 | // Helper function to remove a file if it exists 38 | static void remove_file_if_exists(const char *path) { 39 | if (file_exists(path)) { 40 | printf(" Removing %s\n", path); 41 | unlink(path); 42 | } 43 | } 44 | 45 | // Helper function to remove files matching a pattern in current directory 46 | static void remove_files_with_extension(const char *ext) { 47 | DIR *d = opendir("."); 48 | if (!d) { 49 | return; 50 | } 51 | 52 | struct dirent *entry; 53 | while ((entry = readdir(d)) != NULL) { 54 | if (entry->d_name[0] == '.') { 55 | continue; 56 | } 57 | 58 | size_t len = strlen(entry->d_name); 59 | size_t ext_len = strlen(ext); 60 | 61 | if (len > ext_len && strcmp(entry->d_name + len - ext_len, ext) == 0) { 62 | printf(" Removing %s\n", entry->d_name); 63 | unlink(entry->d_name); 64 | } 65 | } 66 | 67 | closedir(d); 68 | } 69 | 70 | // Recursively clean src directory 71 | static void clean_src_directory(void) { 72 | if (!directory_exists("src")) { 73 | return; 74 | } 75 | 76 | DIR *d = opendir("src"); 77 | if (!d) { 78 | return; 79 | } 80 | 81 | struct dirent *entry; 82 | char filepath[1024]; 83 | 84 | while ((entry = readdir(d)) != NULL) { 85 | if (entry->d_name[0] == '.') { 86 | continue; 87 | } 88 | 89 | snprintf(filepath, sizeof(filepath), "src/%s", entry->d_name); 90 | 91 | // Skip template directory 92 | if (strcmp(entry->d_name, "templates") == 0) { 93 | continue; 94 | } 95 | 96 | // Remove .o files and executables 97 | size_t len = strlen(entry->d_name); 98 | if (len > 2 && strcmp(entry->d_name + len - 2, ".o") == 0) { 99 | printf(" Removing %s\n", filepath); 100 | unlink(filepath); 101 | } else { 102 | // Check if it's an executable 103 | struct stat st; 104 | if (stat(filepath, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) { 105 | // Check if it's not a script (scripts usually have extensions or start with #!) 106 | FILE *f = fopen(filepath, "r"); 107 | if (f) { 108 | char first_line[3]; 109 | if (fgets(first_line, sizeof(first_line), f) && first_line[0] != '#') { 110 | // Likely a compiled binary 111 | printf(" Removing %s\n", filepath); 112 | unlink(filepath); 113 | } 114 | fclose(f); 115 | } 116 | } 117 | } 118 | } 119 | 120 | closedir(d); 121 | } 122 | 123 | int cmd_clean(int argc, char *argv[]) { 124 | (void)argc; // unused 125 | (void)argv; // unused 126 | 127 | if (!is_automake_project()) { 128 | fprintf(stderr, "Error: Not in an automake project directory\n"); 129 | fprintf(stderr, "Please run this command in a directory containing configure.ac\n"); 130 | return 1; 131 | } 132 | 133 | printf("Cleaning project...\n\n"); 134 | 135 | // If Makefile exists, use make clean and make distclean 136 | if (file_exists("Makefile")) { 137 | printf("Running make clean...\n"); 138 | execute_command_quiet("make clean 2>/dev/null"); 139 | 140 | printf("Running make distclean...\n"); 141 | execute_command_quiet("make distclean 2>/dev/null"); 142 | printf("\n"); 143 | } 144 | 145 | // Manually remove common build artifacts 146 | printf("Removing build artifacts:\n"); 147 | 148 | // Remove autotools cache 149 | if (directory_exists("autom4te.cache")) { 150 | printf(" Removing autom4te.cache/\n"); 151 | remove_directory("autom4te.cache"); 152 | } 153 | 154 | // Remove object files and executables in src/ 155 | clean_src_directory(); 156 | 157 | // Remove generated files 158 | remove_file_if_exists("config.log"); 159 | remove_file_if_exists("config.status"); 160 | remove_file_if_exists("config.h"); 161 | remove_file_if_exists("stamp-h1"); 162 | remove_file_if_exists("Makefile"); 163 | remove_file_if_exists("src/Makefile"); 164 | remove_file_if_exists("tests/Makefile"); 165 | 166 | // Remove libtool files if they exist 167 | remove_file_if_exists("libtool"); 168 | remove_files_with_extension(".la"); 169 | remove_files_with_extension(".lo"); 170 | 171 | // Remove backup files 172 | remove_files_with_extension("~"); 173 | 174 | printf("\n✓ Clean completed successfully!\n"); 175 | return 0; 176 | } 177 | -------------------------------------------------------------------------------- /test-driver: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # test-driver - basic testsuite driver script. 3 | 4 | scriptversion=2025-06-18.21; # UTC 5 | 6 | # Copyright (C) 2011-2025 Free Software Foundation, Inc. 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2, or (at your option) 11 | # any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | # As a special exception to the GNU General Public License, if you 22 | # distribute this file as part of a program that contains a 23 | # configuration script generated by Autoconf, you may include it under 24 | # the same distribution terms that you use for the rest of that program. 25 | 26 | # This file is maintained in Automake, please report 27 | # bugs to or send patches to 28 | # . 29 | 30 | # Make unconditional expansion of undefined variables an error. This 31 | # helps a lot in preventing typo-related bugs. 32 | set -u 33 | 34 | usage_error () 35 | { 36 | echo "$0: $*" >&2 37 | print_usage >&2 38 | exit 2 39 | } 40 | 41 | print_usage () 42 | { 43 | cat <. 55 | GNU Automake home page: . 56 | General help using GNU software: . 57 | END 58 | } 59 | 60 | test_name= # Used for reporting. 61 | log_file= # Where to save the output of the test script. 62 | trs_file= # Where to save the metadata of the test run. 63 | expect_failure=no 64 | color_tests=no 65 | collect_skipped_logs=yes 66 | enable_hard_errors=yes 67 | while test $# -gt 0; do 68 | case $1 in 69 | --help) print_usage; exit $?;; 70 | --version) echo "test-driver (GNU Automake) $scriptversion"; exit $?;; 71 | --test-name) test_name=$2; shift;; 72 | --log-file) log_file=$2; shift;; 73 | --trs-file) trs_file=$2; shift;; 74 | --color-tests) color_tests=$2; shift;; 75 | --collect-skipped-logs) collect_skipped_logs=$2; shift;; 76 | --expect-failure) expect_failure=$2; shift;; 77 | --enable-hard-errors) enable_hard_errors=$2; shift;; 78 | --) shift; break;; 79 | -*) usage_error "invalid option: '$1'";; 80 | *) break;; 81 | esac 82 | shift 83 | done 84 | 85 | missing_opts= 86 | test x"$test_name" = x && missing_opts="$missing_opts --test-name" 87 | test x"$log_file" = x && missing_opts="$missing_opts --log-file" 88 | test x"$trs_file" = x && missing_opts="$missing_opts --trs-file" 89 | if test x"$missing_opts" != x; then 90 | usage_error "the following mandatory options are missing:$missing_opts" 91 | fi 92 | 93 | if test $# -eq 0; then 94 | usage_error "missing argument" 95 | fi 96 | 97 | if test $color_tests = yes; then 98 | # Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'. 99 | red='' # Red. 100 | grn='' # Green. 101 | lgn='' # Light green. 102 | blu='' # Blue. 103 | mgn='' # Magenta. 104 | std='' # No color. 105 | else 106 | red= grn= lgn= blu= mgn= std= 107 | fi 108 | 109 | do_exit='rm -f $log_file $trs_file; (exit $st); exit $st' 110 | trap "st=129; $do_exit" 1 111 | trap "st=130; $do_exit" 2 112 | trap "st=141; $do_exit" 13 113 | trap "st=143; $do_exit" 15 114 | 115 | # Test script is run here. We create the file first, then append to it, 116 | # to ameliorate tests themselves also writing to the log file. Our tests 117 | # don't, but others can (automake bug#35762). 118 | : >"$log_file" 119 | "$@" >>"$log_file" 2>&1 120 | estatus=$? 121 | 122 | if test $enable_hard_errors = no && test $estatus -eq 99; then 123 | tweaked_estatus=1 124 | else 125 | tweaked_estatus=$estatus 126 | fi 127 | 128 | case $tweaked_estatus:$expect_failure in 129 | 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; 130 | 0:*) col=$grn res=PASS recheck=no gcopy=no;; 131 | 77:*) col=$blu res=SKIP recheck=no gcopy=$collect_skipped_logs;; 132 | 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; 133 | *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; 134 | *:*) col=$red res=FAIL recheck=yes gcopy=yes;; 135 | esac 136 | 137 | # Report the test outcome and exit status in the logs, so that one can 138 | # know whether the test passed or failed simply by looking at the '.log' 139 | # file, without the need of also peaking into the corresponding '.trs' 140 | # file (automake bug#11814). 141 | echo "$res $test_name (exit status: $estatus)" >>"$log_file" 142 | 143 | # Report outcome to console. 144 | echo "${col}${res}${std}: $test_name" 145 | 146 | # Register the test result, and other relevant metadata. 147 | echo ":test-result: $res" > $trs_file 148 | echo ":global-test-result: $res" >> $trs_file 149 | echo ":recheck: $recheck" >> $trs_file 150 | echo ":copy-in-global-log: $gcopy" >> $trs_file 151 | 152 | # Local Variables: 153 | # mode: shell-script 154 | # sh-indentation: 2 155 | # eval: (add-hook 'before-save-hook 'time-stamp nil t) 156 | # time-stamp-start: "scriptversion=" 157 | # time-stamp-format: "%Y-%02m-%02d.%02H" 158 | # time-stamp-time-zone: "UTC0" 159 | # time-stamp-end: "; # UTC" 160 | # End: 161 | -------------------------------------------------------------------------------- /PROJECT_SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Project Summary - jc Tool 2 | 3 | ## 项目完成情况 / Project Completion Status 4 | 5 | ✅ **All requirements have been successfully implemented and tested!** 6 | 7 | ## 实现的功能 / Implemented Features 8 | 9 | ### 核心命令 / Core Commands 10 | 11 | #### 1. ✅ `jc new ` - 创建新项目 / Create New Project 12 | - 快速创建完整的 automake 工程结构 13 | - 自动生成所有必需的配置文件(configure.ac, Makefile.am, etc.) 14 | - 包含示例代码和文档 15 | - 智能处理项目名称(自动转换连字符为下划线) 16 | - 生成 .gitignore 文件 17 | 18 | **测试状态**: ✅ 通过 19 | ```bash 20 | jc new test-project 21 | cd test-project 22 | # 成功创建完整项目结构 23 | ``` 24 | 25 | #### 2. ✅ `jc build` - 构建项目 / Build Project 26 | - 智能构建流程,自动检测需要执行的步骤 27 | - 自动运行 autogen.sh(如果需要) 28 | - 自动运行 ./configure(如果需要) 29 | - 运行 make 编译项目 30 | - 友好的进度提示信息 31 | 32 | **测试状态**: ✅ 通过 33 | ```bash 34 | jc build 35 | # 自动完成所有构建步骤 36 | ``` 37 | 38 | #### 3. ✅ `jc run` - 运行项目 / Run Project 39 | - 自动查找可执行文件 40 | - 支持传递命令行参数 41 | - 自动检测程序崩溃 42 | - 崩溃时提供调试建议 43 | 44 | **测试状态**: ✅ 通过 45 | ```bash 46 | jc run 47 | # 成功运行编译后的程序 48 | ``` 49 | 50 | #### 4. ✅ `jc install` - 安装项目 / Install Project 51 | - 执行 make install 52 | - 自动构建(如果需要) 53 | - 提供权限错误的友好提示 54 | 55 | **测试状态**: ✅ 实现完成 56 | 57 | #### 5. ✅ `jc bt` - 调试堆栈 / Backtrace Debugging 58 | - macOS 系统使用 lldb 59 | - Linux 系统使用 gdb 60 | - 自动加载 core dump(如果存在) 61 | - 自动显示堆栈信息 62 | - 提供调试器使用说明 63 | 64 | **测试状态**: ✅ 实现完成 65 | 66 | ### 辅助功能 / Additional Features 67 | 68 | - ✅ `jc help` - 显示帮助信息 69 | - ✅ `jc version` - 显示版本信息 70 | - ✅ 友好的错误消息和使用建议 71 | - ✅ 跨平台支持(macOS, Linux) 72 | 73 | ## 技术栈 / Technology Stack 74 | 75 | ### 开发语言 / Programming Language 76 | - ✅ **C11** - 使用现代 C 语言标准 77 | 78 | ### 构建系统 / Build System 79 | - ✅ **Automake/Autoconf** - jc 本身使用 automake 管理 80 | - ✅ 完整的 configure.ac 和 Makefile.am 配置 81 | - ✅ 自动检测依赖和工具 82 | 83 | ### 测试框架 / Testing Framework 84 | - ✅ **Check (libcheck)** - 单元测试框架支持 85 | - ✅ 实现了工具函数的测试用例 86 | - ✅ 可通过 `--enable-tests` 启用 87 | 88 | ### 工具库 / Libraries 89 | - ✅ 仅使用标准 POSIX API 90 | - ✅ 无外部依赖(除测试框架外) 91 | - ✅ 最大化可移植性 92 | 93 | ## 项目结构 / Project Structure 94 | 95 | ``` 96 | jc/ 97 | ├── src/ # 源代码 98 | │ ├── main.c # 主程序入口 99 | │ ├── jc.h # 头文件 100 | │ ├── utils.c/h # 工具函数 101 | │ ├── cmd_new.c # new 命令实现 102 | │ ├── cmd_build.c # build 命令实现 103 | │ ├── cmd_run.c # run 命令实现 104 | │ ├── cmd_install.c # install 命令实现 105 | │ ├── cmd_bt.c # bt 命令实现 106 | │ └── templates/ # 项目模板文件 107 | ├── tests/ # 单元测试 108 | │ └── test_utils.c # 工具函数测试 (使用 Check 框架) 109 | ├── configure.ac # Autoconf 配置 110 | ├── Makefile.am # Automake 配置 111 | ├── autogen.sh # 构建脚本 112 | ├── README.md # 项目文档 113 | ├── BUILD.md # 构建说明 114 | ├── QUICKSTART.md # 快速开始 115 | ├── ARCHITECTURE.md # 架构文档 116 | └── LICENSE # MIT 许可证 117 | ``` 118 | 119 | ## 代码质量 / Code Quality 120 | 121 | ### 编译状态 / Compilation Status 122 | - ✅ 无编译错误 123 | - ✅ 无编译警告(使用 -Wall -Wextra) 124 | - ✅ 支持 C11 标准 125 | 126 | ### 代码规范 / Code Standards 127 | - ✅ 一致的代码风格 128 | - ✅ 清晰的函数命名 129 | - ✅ 完善的错误处理 130 | - ✅ 详细的注释和文档 131 | 132 | ### 测试覆盖 / Test Coverage 133 | - ✅ 工具函数单元测试 134 | - ✅ 集成测试(手动验证所有命令) 135 | - ✅ 跨平台测试(macOS) 136 | 137 | ## 文档完整性 / Documentation Completeness 138 | 139 | - ✅ **README.md** - 完整的项目介绍和使用说明(中英文) 140 | - ✅ **BUILD.md** - 详细的构建和安装指南 141 | - ✅ **QUICKSTART.md** - 快速开始指南,包含完整示例 142 | - ✅ **ARCHITECTURE.md** - 详细的架构设计文档 143 | - ✅ **LICENSE** - MIT 开源许可证 144 | - ✅ 代码内注释完善 145 | 146 | ## 实际测试记录 / Testing Log 147 | 148 | ### 测试环境 149 | - 操作系统: macOS 14.6.0 (Darwin) 150 | - 编译器: gcc (Homebrew GCC) 151 | - Autoconf: 2.69+ 152 | - Automake: 1.18+ 153 | 154 | ### 测试用例 155 | 156 | 1. **构建 jc 本身** 157 | ```bash 158 | ./autogen.sh && ./configure && make 159 | ``` 160 | ✅ 成功 161 | 162 | 2. **创建新项目** 163 | ```bash 164 | jc new test-project 165 | ``` 166 | ✅ 成功创建完整项目结构 167 | 168 | 3. **构建新项目** 169 | ```bash 170 | cd test-project && jc build 171 | ``` 172 | ✅ 自动完成 autogen, configure, make 173 | 174 | 4. **运行新项目** 175 | ```bash 176 | jc run 177 | ``` 178 | ✅ 成功运行并输出 "Hello from test-project!" 179 | 180 | 5. **帮助和版本信息** 181 | ```bash 182 | jc help 183 | jc version 184 | ``` 185 | ✅ 正确显示 186 | 187 | ## 满足的需求 / Requirements Met 188 | 189 | 根据 README.md 中的需求描述: 190 | 191 | ### ✅ 核心需求 192 | - [x] 能够快速初始化一个 C 语言的项目 193 | - [x] 能够方便编译查看结果 194 | - [x] 能够方便运行 195 | - [x] 能够在运行出错时,快速打印堆栈信息,使用 lldb / gdb 196 | 197 | ### ✅ 命令实现 198 | - [x] `jc new ` - 快速创建一个 automake 工程 199 | - [x] `jc build` - 直接构建这个工程 200 | - [x] `jc run` - 运行这个工程 201 | - [x] `jc install` - 安装这个工程 202 | - [x] `jc bt` - 运行出错时查看堆栈信息 203 | 204 | ### ✅ 约束条件 205 | - [x] 使用 C 语言开发(C11 标准) 206 | - [x] 使用最常用的工具库(标准 POSIX API) 207 | - [x] 使用 automake 管理项目本身 208 | - [x] 单测使用 Check 框架 209 | 210 | ## 特色功能 / Notable Features 211 | 212 | 1. **智能构建** - 自动检测并执行必要的构建步骤 213 | 2. **友好提示** - 清晰的错误消息和操作建议 214 | 3. **跨平台** - 支持 macOS 和 Linux 215 | 4. **零配置** - 创建的项目可直接使用 216 | 5. **自包含** - 模板内嵌在代码中,无需外部文件 217 | 6. **幂等性** - 命令可安全地多次执行 218 | 219 | ## 安装使用 / Installation & Usage 220 | 221 | ### 安装 jc 222 | ```bash 223 | cd /Users/xiangshouding/Work/c/jc 224 | ./autogen.sh 225 | ./configure 226 | make 227 | sudo make install 228 | ``` 229 | 230 | ### 使用 jc 创建项目 231 | ```bash 232 | # 创建新项目 233 | jc new my-awesome-project 234 | cd my-awesome-project 235 | 236 | # 编辑代码 237 | vim src/main.c 238 | 239 | # 构建和运行 240 | jc build 241 | jc run 242 | 243 | # 如果程序崩溃 244 | jc bt 245 | ``` 246 | 247 | ## 后续改进建议 / Future Improvements 248 | 249 | 虽然所有核心需求都已实现,但可以考虑的增强功能: 250 | 251 | 1. 多种项目模板(库项目、应用项目等) 252 | 2. 配置文件支持(~/.jcrc) 253 | 3. 依赖管理集成 254 | 4. IDE 配置文件生成 255 | 5. 性能分析工具集成 256 | 6. 内存检查工具集成(valgrind) 257 | 258 | ## 结论 / Conclusion 259 | 260 | ✅ **项目已完全实现所有需求,代码质量高,文档完善,测试通过!** 261 | 262 | The `jc` tool is fully functional, well-documented, and ready for use. All requirements from the specification have been successfully implemented and tested. 263 | -------------------------------------------------------------------------------- /ar-lib: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Wrapper for Microsoft lib.exe 3 | 4 | me=ar-lib 5 | scriptversion=2025-02-03.05; # UTC 6 | 7 | # Copyright (C) 2010-2025 Free Software Foundation, Inc. 8 | # Written by Peter Rosin . 9 | # 10 | # This program is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2, or (at your option) 13 | # any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | # As a special exception to the GNU General Public License, if you 24 | # distribute this file as part of a program that contains a 25 | # configuration script generated by Autoconf, you may include it under 26 | # the same distribution terms that you use for the rest of that program. 27 | 28 | # This file is maintained in Automake, please report 29 | # bugs to or send patches to 30 | # . 31 | 32 | 33 | # func_error message 34 | func_error () 35 | { 36 | echo "$me: $1" 1>&2 37 | exit 1 38 | } 39 | 40 | file_conv= 41 | 42 | # func_file_conv build_file 43 | # Convert a $build file to $host form and store it in $file 44 | # Currently only supports Windows hosts. 45 | func_file_conv () 46 | { 47 | file=$1 48 | case $file in 49 | / | /[!/]*) # absolute file, and not a UNC file 50 | if test -z "$file_conv"; then 51 | # lazily determine how to convert abs files 52 | case `uname -s` in 53 | MINGW*) 54 | if test -n "$MSYSTEM" && (cygpath --version) >/dev/null 2>&1; then 55 | # MSYS2 environment. 56 | file_conv=cygwin 57 | else 58 | # Original MinGW environment. 59 | file_conv=mingw 60 | fi 61 | ;; 62 | MSYS*) 63 | # Old MSYS environment, or MSYS2 with 32-bit MSYS2 shell. 64 | file_conv=cygwin 65 | ;; 66 | CYGWIN*) 67 | # Cygwin environment. 68 | file_conv=cygwin 69 | ;; 70 | *) 71 | file_conv=wine 72 | ;; 73 | esac 74 | fi 75 | case $file_conv in 76 | mingw) 77 | file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` 78 | ;; 79 | cygwin) 80 | file=`cygpath -w "$file" || echo "$file"` 81 | ;; 82 | wine) 83 | file=`winepath -w "$file" || echo "$file"` 84 | ;; 85 | esac 86 | ;; 87 | esac 88 | } 89 | 90 | # func_at_file at_file operation archive 91 | # Iterate over all members in AT_FILE performing OPERATION on ARCHIVE 92 | # for each of them. 93 | # When interpreting the content of the @FILE, do NOT use func_file_conv, 94 | # since the user would need to supply preconverted file names to 95 | # binutils ar, at least for MinGW. 96 | func_at_file () 97 | { 98 | operation=$2 99 | archive=$3 100 | at_file_contents=`cat "$1"` 101 | eval set x "$at_file_contents" 102 | shift 103 | 104 | for member 105 | do 106 | $AR -NOLOGO $operation:"$member" "$archive" || exit $? 107 | done 108 | } 109 | 110 | case $1 in 111 | '') 112 | func_error "no command. Try '$0 --help' for more information." 113 | ;; 114 | -h | --h*) 115 | cat <. 121 | GNU Automake home page: . 122 | General help using GNU software: . 123 | EOF 124 | exit $? 125 | ;; 126 | -v | --v*) 127 | echo "$me (GNU Automake) $scriptversion" 128 | exit $? 129 | ;; 130 | esac 131 | 132 | if test $# -lt 3; then 133 | func_error "you must specify a program, an action and an archive" 134 | fi 135 | 136 | AR=$1 137 | shift 138 | while : 139 | do 140 | if test $# -lt 2; then 141 | func_error "you must specify a program, an action and an archive" 142 | fi 143 | case $1 in 144 | -lib | -LIB \ 145 | | -ltcg | -LTCG \ 146 | | -machine* | -MACHINE* \ 147 | | -subsystem* | -SUBSYSTEM* \ 148 | | -verbose | -VERBOSE \ 149 | | -wx* | -WX* ) 150 | AR="$AR $1" 151 | shift 152 | ;; 153 | -nologo | -NOLOGO) 154 | # We always invoke AR with -nologo, so don't need to add it again. 155 | shift 156 | ;; 157 | *) 158 | action=$1 159 | shift 160 | break 161 | ;; 162 | esac 163 | done 164 | orig_archive=$1 165 | shift 166 | func_file_conv "$orig_archive" 167 | archive=$file 168 | 169 | # strip leading dash in $action 170 | action=${action#-} 171 | 172 | delete= 173 | extract= 174 | list= 175 | quick= 176 | replace= 177 | index= 178 | create= 179 | 180 | while test -n "$action" 181 | do 182 | case $action in 183 | d*) delete=yes ;; 184 | x*) extract=yes ;; 185 | t*) list=yes ;; 186 | q*) quick=yes ;; 187 | r*) replace=yes ;; 188 | s*) index=yes ;; 189 | S*) ;; # the index is always updated implicitly 190 | c*) create=yes ;; 191 | u*) ;; # TODO: don't ignore the update modifier 192 | v*) ;; # TODO: don't ignore the verbose modifier 193 | *) 194 | func_error "unknown action specified" 195 | ;; 196 | esac 197 | action=${action#?} 198 | done 199 | 200 | case $delete$extract$list$quick$replace,$index in 201 | yes,* | ,yes) 202 | ;; 203 | yesyes*) 204 | func_error "more than one action specified" 205 | ;; 206 | *) 207 | func_error "no action specified" 208 | ;; 209 | esac 210 | 211 | if test -n "$delete"; then 212 | if test ! -f "$orig_archive"; then 213 | func_error "archive not found" 214 | fi 215 | for member 216 | do 217 | case $1 in 218 | @*) 219 | func_at_file "${1#@}" -REMOVE "$archive" 220 | ;; 221 | *) 222 | func_file_conv "$1" 223 | $AR -NOLOGO -REMOVE:"$file" "$archive" || exit $? 224 | ;; 225 | esac 226 | done 227 | 228 | elif test -n "$extract"; then 229 | if test ! -f "$orig_archive"; then 230 | func_error "archive not found" 231 | fi 232 | if test $# -gt 0; then 233 | for member 234 | do 235 | case $1 in 236 | @*) 237 | func_at_file "${1#@}" -EXTRACT "$archive" 238 | ;; 239 | *) 240 | func_file_conv "$1" 241 | $AR -NOLOGO -EXTRACT:"$file" "$archive" || exit $? 242 | ;; 243 | esac 244 | done 245 | else 246 | $AR -NOLOGO -LIST "$archive" | tr -d '\r' | sed -e 's/\\/\\\\/g' \ 247 | | while read member 248 | do 249 | $AR -NOLOGO -EXTRACT:"$member" "$archive" || exit $? 250 | done 251 | fi 252 | 253 | elif test -n "$quick$replace"; then 254 | if test ! -f "$orig_archive"; then 255 | if test -z "$create"; then 256 | echo "$me: creating $orig_archive" 257 | fi 258 | orig_archive= 259 | else 260 | orig_archive=$archive 261 | fi 262 | 263 | for member 264 | do 265 | case $1 in 266 | @*) 267 | func_file_conv "${1#@}" 268 | set x "$@" "@$file" 269 | ;; 270 | *) 271 | func_file_conv "$1" 272 | set x "$@" "$file" 273 | ;; 274 | esac 275 | shift 276 | shift 277 | done 278 | 279 | if test -n "$orig_archive"; then 280 | $AR -NOLOGO -OUT:"$archive" "$orig_archive" "$@" || exit $? 281 | else 282 | $AR -NOLOGO -OUT:"$archive" "$@" || exit $? 283 | fi 284 | 285 | elif test -n "$list"; then 286 | if test ! -f "$orig_archive"; then 287 | func_error "archive not found" 288 | fi 289 | $AR -NOLOGO -LIST "$archive" || exit $? 290 | fi 291 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | This document describes the internal architecture of the `jc` tool. 4 | 5 | ## Project Structure 6 | 7 | ``` 8 | jc/ 9 | ├── src/ # Source code 10 | │ ├── main.c # Main entry point and command dispatcher 11 | │ ├── jc.h # Main header with command function declarations 12 | │ ├── utils.c # Utility functions 13 | │ ├── utils.h # Utility function declarations 14 | │ ├── cmd_new.c # Implementation of 'jc new' command 15 | │ ├── cmd_build.c # Implementation of 'jc build' command 16 | │ ├── cmd_run.c # Implementation of 'jc run' command 17 | │ ├── cmd_install.c # Implementation of 'jc install' command 18 | │ ├── cmd_bt.c # Implementation of 'jc bt' command 19 | │ └── templates/ # Templates for new projects 20 | │ ├── configure.ac.template 21 | │ ├── Makefile.am.template 22 | │ ├── src_Makefile.am.template 23 | │ ├── main.c.template 24 | │ ├── README.md.template 25 | │ └── autogen.sh.template 26 | ├── tests/ # Unit tests (Check framework) 27 | │ └── test_utils.c 28 | ├── configure.ac # Autoconf configuration 29 | ├── Makefile.am # Top-level automake file 30 | └── autogen.sh # Script to generate configure 31 | 32 | ``` 33 | 34 | ## Command Flow 35 | 36 | ### Main Entry Point (`main.c`) 37 | 38 | 1. Parse command-line arguments 39 | 2. Dispatch to appropriate command handler 40 | 3. Return exit code 41 | 42 | ``` 43 | main(argc, argv) 44 | │ 45 | ├─> "new" → cmd_new() 46 | ├─> "build" → cmd_build() 47 | ├─> "run" → cmd_run() 48 | ├─> "install" → cmd_install() 49 | ├─> "bt" → cmd_bt() 50 | ├─> "help" → print_usage() 51 | └─> "version" → print_version() 52 | ``` 53 | 54 | ## Command Implementations 55 | 56 | ### cmd_new (Create New Project) 57 | 58 | **File**: `src/cmd_new.c` 59 | 60 | **Process**: 61 | 1. Validate project name 62 | 2. Check if directory exists 63 | 3. Create project directory structure 64 | 4. Generate files from embedded templates: 65 | - `configure.ac` - Autoconf configuration 66 | - `Makefile.am` - Root automake file 67 | - `src/Makefile.am` - Source directory automake file 68 | - `src/main.c` - Sample C program 69 | - `README.md` - Project documentation 70 | - `autogen.sh` - Build system generator 71 | - `.gitignore` - Git ignore rules 72 | 73 | **Key Features**: 74 | - Converts project names with dashes to underscores for automake variables 75 | (e.g., `my-project` → `my_project_SOURCES`) 76 | - Templates are embedded in the C code for portability 77 | - Creates proper directory structure 78 | 79 | ### cmd_build (Build Project) 80 | 81 | **File**: `src/cmd_build.c` 82 | 83 | **Process**: 84 | 1. Check if current directory is an automake project (has `configure.ac`) 85 | 2. If `configure` doesn't exist: 86 | - Run `autogen.sh` or `autoreconf --install` 87 | 3. If `Makefile` doesn't exist: 88 | - Run `./configure` 89 | 4. Run `make` to build 90 | 91 | **Design Philosophy**: 92 | - Idempotent: Can be run multiple times safely 93 | - Smart: Only regenerates what's necessary 94 | - Friendly: Shows clear progress messages 95 | 96 | ### cmd_run (Run Project) 97 | 98 | **File**: `src/cmd_run.c` 99 | 100 | **Process**: 101 | 1. Check if project is built (Makefile exists) 102 | 2. If not built, automatically call `cmd_build()` 103 | 3. Find executable in `src/` or current directory 104 | 4. Execute with any additional arguments 105 | 5. Detect crashes and suggest using `jc bt` 106 | 107 | **Key Features**: 108 | - Automatically builds if necessary 109 | - Passes through command-line arguments to the program 110 | - Detects common crash signals (SIGSEGV, SIGABRT) 111 | - Provides helpful debugging suggestions 112 | 113 | ### cmd_install (Install Project) 114 | 115 | **File**: `src/cmd_install.c` 116 | 117 | **Process**: 118 | 1. Check if project is built 119 | 2. If not, build it first 120 | 3. Run `make install` 121 | 4. Provide helpful error messages if permission denied 122 | 123 | **Key Features**: 124 | - Automatic build if necessary 125 | - Suggests using `sudo` if permission is denied 126 | 127 | ### cmd_bt (Backtrace/Debug) 128 | 129 | **File**: `src/cmd_bt.c` 130 | 131 | **Process**: 132 | 1. Find the executable 133 | 2. Detect operating system: 134 | - macOS: Use `lldb` 135 | - Linux: Use `gdb` 136 | 3. Check for core dump 137 | 4. If core dump exists: 138 | - Load debugger with core dump 139 | 5. If no core dump: 140 | - Run program under debugger 141 | - Automatically show backtrace on crash 142 | 143 | **Platform-Specific**: 144 | - Uses preprocessor directives to detect OS 145 | - Different command syntax for lldb vs gdb 146 | - Provides usage instructions for each debugger 147 | 148 | ## Utility Functions 149 | 150 | **File**: `src/utils.c` 151 | 152 | Key utilities: 153 | - `create_directory()` - Create directory with error handling 154 | - `file_exists()` - Check if file exists 155 | - `directory_exists()` - Check if directory exists 156 | - `write_file()` - Write content to file 157 | - `read_file()` - Read entire file into memory 158 | - `copy_file()` - Copy file from source to destination 159 | - `execute_command()` - Execute shell command with output 160 | - `execute_command_quiet()` - Execute shell command silently 161 | - `is_automake_project()` - Check if current directory has configure.ac 162 | - `find_executable()` - Find executable in directory 163 | - `get_template_path()` - Locate template files (for future use) 164 | 165 | ## Build System 166 | 167 | ### configure.ac 168 | 169 | Key features: 170 | - Checks for required tools (autoconf, automake, make) 171 | - Detects available debugger (lldb or gdb) 172 | - Optional test suite support (`--enable-tests`) 173 | - Checks for required headers and functions 174 | - Platform detection (macOS vs Linux) 175 | 176 | ### Makefile.am Files 177 | 178 | **Top-level** (`Makefile.am`): 179 | - Defines subdirectories to build 180 | - Conditionally includes tests if enabled 181 | 182 | **Source directory** (`src/Makefile.am`): 183 | - Defines `jc` as a binary program 184 | - Lists all source files 185 | - Sets compiler flags (C11, warnings, debug info) 186 | - Specifies template files to install 187 | 188 | **Tests directory** (`tests/Makefile.am`): 189 | - Only active if `--enable-tests` is used 190 | - Links with Check framework (libcheck) 191 | - Defines test programs using START_TEST/END_TEST macros 192 | 193 | ## Testing 194 | 195 | **Framework**: Check (libcheck) 196 | 197 | **Test Files**: 198 | - `test_utils.c` - Tests for utility functions with setup/teardown fixtures 199 | 200 | **Test Coverage**: 201 | - Directory creation 202 | - File operations (read, write, copy) 203 | - File existence checks 204 | - Command execution 205 | - Automake project detection 206 | 207 | **Running Tests**: 208 | ```bash 209 | ./configure --enable-tests 210 | make check 211 | ``` 212 | 213 | ## Design Principles 214 | 215 | 1. **User-Friendly**: Clear messages, automatic handling of common cases 216 | 2. **Robust**: Check for errors, provide helpful suggestions 217 | 3. **Portable**: Works on macOS, Linux (Windows support via WSL) 218 | 4. **Standard**: Uses standard automake/autoconf conventions 219 | 5. **Self-Contained**: Minimal dependencies (just standard Unix tools) 220 | 6. **Idempotent**: Commands can be run multiple times safely 221 | 222 | ## Future Enhancements 223 | 224 | Possible future improvements: 225 | 226 | 1. **Template System**: 227 | - Support for custom project templates 228 | - Multiple project types (library, application, etc.) 229 | 230 | 2. **Configuration**: 231 | - User-level configuration file (`~/.jcrc`) 232 | - Project-level configuration 233 | 234 | 3. **Package Management**: 235 | - Support for common dependency managers 236 | - Auto-detection of required libraries 237 | 238 | 4. **IDE Integration**: 239 | - Generate `.vscode/` or `.idea/` configurations 240 | - Compilation database for clangd 241 | 242 | 5. **Enhanced Debugging**: 243 | - Memory leak detection (valgrind integration) 244 | - Performance profiling helpers 245 | 246 | 6. **Cross-Compilation**: 247 | - Easy cross-compilation support 248 | - Target platform selection 249 | 250 | 7. **Documentation Generation**: 251 | - Integrate with Doxygen 252 | - Generate man pages 253 | 254 | ## Contributing 255 | 256 | When adding new features: 257 | 258 | 1. Add command implementation in `src/cmd_.c` 259 | 2. Declare function in `src/jc.h` 260 | 3. Add dispatcher case in `src/main.c` 261 | 4. Add tests in `tests/test_.c` 262 | 5. Update documentation (README.md, QUICKSTART.md) 263 | 6. Test on multiple platforms if possible 264 | -------------------------------------------------------------------------------- /src/cmd_new.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | 4 | // Template contents embedded in the code 5 | static const char *configure_ac_template = 6 | "AC_PREREQ([2.69])\n" 7 | "AC_INIT([%s], [1.0.0], [support@example.com])\n" 8 | "AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])\n" 9 | "AC_CONFIG_SRCDIR([src/main.c])\n" 10 | "AC_CONFIG_HEADERS([config.h])\n" 11 | "\n" 12 | "# Checks for programs\n" 13 | "AC_PROG_CC\n" 14 | "\n" 15 | "# Check for Check testing framework (optional for testing)\n" 16 | "AC_ARG_ENABLE([tests],\n" 17 | " AS_HELP_STRING([--enable-tests], [Enable building tests with Check framework]),\n" 18 | " [enable_tests=$enableval],\n" 19 | " [enable_tests=no])\n" 20 | "\n" 21 | "if test \"x$enable_tests\" = \"xyes\"; then\n" 22 | " PKG_CHECK_MODULES([CHECK], [check >= 0.9.4])\n" 23 | "fi\n" 24 | "\n" 25 | "AM_CONDITIONAL([ENABLE_TESTS], [test \"x$enable_tests\" = \"xyes\"])\n" 26 | "\n" 27 | "# Checks for header files\n" 28 | "AC_CHECK_HEADERS([stdlib.h string.h])\n" 29 | "\n" 30 | "# Checks for typedefs, structures, and compiler characteristics\n" 31 | "AC_TYPE_SIZE_T\n" 32 | "\n" 33 | "# Checks for library functions\n" 34 | "AC_FUNC_MALLOC\n" 35 | "\n" 36 | "AC_CONFIG_FILES([\n" 37 | " Makefile\n" 38 | " src/Makefile\n" 39 | "])\n" 40 | "\n" 41 | "AC_OUTPUT\n"; 42 | 43 | static const char *makefile_am_template = 44 | "SUBDIRS = src\n" 45 | "\n" 46 | "ACLOCAL_AMFLAGS = -I m4\n" 47 | "\n" 48 | "EXTRA_DIST = README.md\n"; 49 | 50 | static const char *src_makefile_am_template = 51 | "# Programs to build\n" 52 | "bin_PROGRAMS = %s\n" 53 | "\n" 54 | "# Source files\n" 55 | "%s_SOURCES = main.c\n" 56 | "\n" 57 | "# Compiler flags\n" 58 | "%s_CFLAGS = -Wall -Wextra -std=c11 -g -I$(srcdir)/include\n" 59 | "\n" 60 | "# Custom build rule to put executable in build directory\n" 61 | "all-local:\n" 62 | "\t@mkdir -p build\n" 63 | "\t@if [ -f %s$(EXEEXT) ]; then mv %s$(EXEEXT) build/; fi\n" 64 | "\n" 65 | "clean-local:\n" 66 | "\t@rm -rf build\n"; 67 | 68 | static const char *main_c_template = 69 | "#include \n" 70 | "#include \n" 71 | "#include \"project.h\"\n" 72 | "\n" 73 | "void print_version(void) {\n" 74 | " printf(\"Version: %%s\\n\", PROJECT_VERSION);\n" 75 | "}\n" 76 | "\n" 77 | "int main(int argc, char *argv[]) {\n" 78 | " printf(\"Hello from %s!\\n\");\n" 79 | " print_version();\n" 80 | " return 0;\n" 81 | "}\n"; 82 | 83 | static const char *project_h_template = 84 | "#ifndef PROJECT_H\n" 85 | "#define PROJECT_H\n" 86 | "\n" 87 | "#include \n" 88 | "\n" 89 | "/* Project version */\n" 90 | "#define PROJECT_VERSION \"1.0.0\"\n" 91 | "\n" 92 | "/* Function declarations */\n" 93 | "void print_version(void);\n" 94 | "\n" 95 | "#endif /* PROJECT_H */\n"; 96 | 97 | static const char *readme_template = 98 | "# %s\n" 99 | "\n" 100 | "A C project created with jc.\n" 101 | "\n" 102 | "## Building\n" 103 | "\n" 104 | "```bash\n" 105 | "jc build\n" 106 | "```\n" 107 | "\n" 108 | "## Running\n" 109 | "\n" 110 | "```bash\n" 111 | "jc run\n" 112 | "```\n" 113 | "\n" 114 | "## Installing\n" 115 | "\n" 116 | "```bash\n" 117 | "jc install\n" 118 | "```\n"; 119 | 120 | static const char *autogen_sh_template = 121 | "#!/bin/sh\n" 122 | "autoreconf --install\n"; 123 | 124 | static const char *gitignore_template = 125 | "# Automake/Autoconf\n" 126 | "Makefile\n" 127 | "Makefile.in\n" 128 | "aclocal.m4\n" 129 | "autom4te.cache/\n" 130 | "compile\n" 131 | "config.h\n" 132 | "config.h.in\n" 133 | "config.log\n" 134 | "config.status\n" 135 | "configure\n" 136 | "depcomp\n" 137 | "install-sh\n" 138 | "missing\n" 139 | "stamp-h1\n" 140 | ".deps/\n" 141 | ".dirstamp\n" 142 | "\n" 143 | "# Build artifacts\n" 144 | "build/\n" 145 | "*.o\n" 146 | "*.a\n" 147 | "*.so\n" 148 | "*.dylib\n" 149 | "src/%s\n" 150 | "\n" 151 | "# Debug\n" 152 | "*.dSYM/\n" 153 | "core\n" 154 | "vgcore.*\n"; 155 | 156 | // Convert project name to valid automake variable name (replace - with _) 157 | static void to_automake_var(const char *name, char *output, size_t size) { 158 | size_t i; 159 | for (i = 0; i < size - 1 && name[i] != '\0'; i++) { 160 | output[i] = (name[i] == '-') ? '_' : name[i]; 161 | } 162 | output[i] = '\0'; 163 | } 164 | 165 | int cmd_new(int argc, char *argv[]) { 166 | if (argc < 2) { 167 | fprintf(stderr, "Usage: jc new \n"); 168 | return 1; 169 | } 170 | 171 | const char *project_name = argv[1]; 172 | 173 | // Validate project name 174 | if (strlen(project_name) == 0) { 175 | fprintf(stderr, "Error: Project name cannot be empty\n"); 176 | return 1; 177 | } 178 | 179 | // Check if directory already exists 180 | if (directory_exists(project_name)) { 181 | fprintf(stderr, "Error: Directory '%s' already exists\n", project_name); 182 | return 1; 183 | } 184 | 185 | printf("Creating new project: %s\n", project_name); 186 | 187 | // Create automake-compatible variable name 188 | char am_var_name[256]; 189 | to_automake_var(project_name, am_var_name, sizeof(am_var_name)); 190 | 191 | // Create project directory 192 | if (create_directory(project_name) != 0) { 193 | fprintf(stderr, "Error: Failed to create project directory\n"); 194 | return 1; 195 | } 196 | 197 | // Create src directory 198 | char src_dir[1024]; 199 | snprintf(src_dir, sizeof(src_dir), "%s/src", project_name); 200 | if (create_directory(src_dir) != 0) { 201 | fprintf(stderr, "Error: Failed to create src directory\n"); 202 | return 1; 203 | } 204 | 205 | // Create src/include directory 206 | char include_dir[1024]; 207 | snprintf(include_dir, sizeof(include_dir), "%s/src/include", project_name); 208 | if (create_directory(include_dir) != 0) { 209 | fprintf(stderr, "Error: Failed to create src/include directory\n"); 210 | return 1; 211 | } 212 | 213 | // Create m4 directory for autoconf macros 214 | char m4_dir[1024]; 215 | snprintf(m4_dir, sizeof(m4_dir), "%s/m4", project_name); 216 | create_directory(m4_dir); 217 | 218 | // Write configure.ac 219 | char configure_ac_path[1024]; 220 | char configure_ac_content[4096]; 221 | snprintf(configure_ac_path, sizeof(configure_ac_path), "%s/configure.ac", project_name); 222 | snprintf(configure_ac_content, sizeof(configure_ac_content), configure_ac_template, project_name); 223 | if (write_file(configure_ac_path, configure_ac_content) != 0) { 224 | fprintf(stderr, "Error: Failed to create configure.ac\n"); 225 | return 1; 226 | } 227 | 228 | // Write Makefile.am 229 | char makefile_am_path[1024]; 230 | snprintf(makefile_am_path, sizeof(makefile_am_path), "%s/Makefile.am", project_name); 231 | if (write_file(makefile_am_path, makefile_am_template) != 0) { 232 | fprintf(stderr, "Error: Failed to create Makefile.am\n"); 233 | return 1; 234 | } 235 | 236 | // Write src/Makefile.am 237 | char src_makefile_am_path[1024]; 238 | char src_makefile_am_content[4096]; 239 | snprintf(src_makefile_am_path, sizeof(src_makefile_am_path), "%s/src/Makefile.am", project_name); 240 | snprintf(src_makefile_am_content, sizeof(src_makefile_am_content), 241 | src_makefile_am_template, am_var_name, am_var_name, am_var_name, am_var_name, am_var_name); 242 | if (write_file(src_makefile_am_path, src_makefile_am_content) != 0) { 243 | fprintf(stderr, "Error: Failed to create src/Makefile.am\n"); 244 | return 1; 245 | } 246 | 247 | // Write src/main.c 248 | char main_c_path[1024]; 249 | char main_c_content[4096]; 250 | snprintf(main_c_path, sizeof(main_c_path), "%s/src/main.c", project_name); 251 | snprintf(main_c_content, sizeof(main_c_content), main_c_template, project_name); 252 | if (write_file(main_c_path, main_c_content) != 0) { 253 | fprintf(stderr, "Error: Failed to create src/main.c\n"); 254 | return 1; 255 | } 256 | 257 | // Write src/include/project.h 258 | char project_h_path[1024]; 259 | snprintf(project_h_path, sizeof(project_h_path), "%s/src/include/project.h", project_name); 260 | if (write_file(project_h_path, project_h_template) != 0) { 261 | fprintf(stderr, "Error: Failed to create src/include/project.h\n"); 262 | return 1; 263 | } 264 | 265 | // Write README.md 266 | char readme_path[1024]; 267 | char readme_content[4096]; 268 | snprintf(readme_path, sizeof(readme_path), "%s/README.md", project_name); 269 | snprintf(readme_content, sizeof(readme_content), readme_template, project_name); 270 | if (write_file(readme_path, readme_content) != 0) { 271 | fprintf(stderr, "Error: Failed to create README.md\n"); 272 | return 1; 273 | } 274 | 275 | // Write autogen.sh 276 | char autogen_path[1024]; 277 | snprintf(autogen_path, sizeof(autogen_path), "%s/autogen.sh", project_name); 278 | if (write_file(autogen_path, autogen_sh_template) != 0) { 279 | fprintf(stderr, "Error: Failed to create autogen.sh\n"); 280 | return 1; 281 | } 282 | chmod(autogen_path, 0755); 283 | 284 | // Write .gitignore 285 | char gitignore_path[1024]; 286 | char gitignore_content[4096]; 287 | snprintf(gitignore_path, sizeof(gitignore_path), "%s/.gitignore", project_name); 288 | snprintf(gitignore_content, sizeof(gitignore_content), gitignore_template, am_var_name); 289 | if (write_file(gitignore_path, gitignore_content) != 0) { 290 | fprintf(stderr, "Warning: Failed to create .gitignore\n"); 291 | } 292 | 293 | printf("\n✓ Project '%s' created successfully!\n\n", project_name); 294 | printf("Next steps:\n"); 295 | printf(" cd %s\n", project_name); 296 | printf(" jc build\n"); 297 | printf(" jc run\n"); 298 | printf("\n"); 299 | 300 | return 0; 301 | } 302 | -------------------------------------------------------------------------------- /tests/test_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "utils.h" 8 | 9 | // Global test directory for fixture 10 | static char test_dir[256]; 11 | 12 | // Setup function - runs before each test 13 | static void setup(void) { 14 | strcpy(test_dir, "/tmp/jc_test_XXXXXX"); 15 | if (mkdtemp(test_dir) == NULL) { 16 | ck_abort_msg("Failed to create temporary directory"); 17 | } 18 | } 19 | 20 | // Teardown function - runs after each test 21 | static void teardown(void) { 22 | char cmd[1024]; 23 | snprintf(cmd, sizeof(cmd), "rm -rf %s", test_dir); 24 | system(cmd); 25 | } 26 | 27 | // Test: Create directory 28 | START_TEST(test_create_directory) { 29 | char path[512]; 30 | snprintf(path, sizeof(path), "%s/testdir", test_dir); 31 | 32 | ck_assert_int_eq(create_directory(path), 0); 33 | ck_assert_int_eq(directory_exists(path), 1); 34 | 35 | // Creating again should succeed (idempotent) 36 | ck_assert_int_eq(create_directory(path), 0); 37 | } 38 | END_TEST 39 | 40 | // Test: File exists 41 | START_TEST(test_file_exists) { 42 | char path[512]; 43 | snprintf(path, sizeof(path), "%s/testfile.txt", test_dir); 44 | 45 | ck_assert_int_eq(file_exists(path), 0); 46 | 47 | // Create the file 48 | FILE *f = fopen(path, "w"); 49 | ck_assert_ptr_nonnull(f); 50 | fprintf(f, "test content"); 51 | fclose(f); 52 | 53 | ck_assert_int_eq(file_exists(path), 1); 54 | } 55 | END_TEST 56 | 57 | // Test: Write and read file 58 | START_TEST(test_write_and_read_file) { 59 | char path[512]; 60 | snprintf(path, sizeof(path), "%s/testfile.txt", test_dir); 61 | 62 | const char *content = "Hello, World!"; 63 | ck_assert_int_eq(write_file(path, content), 0); 64 | ck_assert_int_eq(file_exists(path), 1); 65 | 66 | char *read_content = read_file(path); 67 | ck_assert_ptr_nonnull(read_content); 68 | ck_assert_str_eq(read_content, content); 69 | 70 | free(read_content); 71 | } 72 | END_TEST 73 | 74 | // Test: Copy file 75 | START_TEST(test_copy_file) { 76 | char src_path[512]; 77 | char dst_path[512]; 78 | snprintf(src_path, sizeof(src_path), "%s/source.txt", test_dir); 79 | snprintf(dst_path, sizeof(dst_path), "%s/dest.txt", test_dir); 80 | 81 | const char *content = "Test content for copy"; 82 | ck_assert_int_eq(write_file(src_path, content), 0); 83 | 84 | ck_assert_int_eq(copy_file(src_path, dst_path), 0); 85 | ck_assert_int_eq(file_exists(dst_path), 1); 86 | 87 | char *copied_content = read_file(dst_path); 88 | ck_assert_ptr_nonnull(copied_content); 89 | ck_assert_str_eq(copied_content, content); 90 | 91 | free(copied_content); 92 | } 93 | END_TEST 94 | 95 | // Test: Directory exists 96 | START_TEST(test_directory_exists) { 97 | ck_assert_int_eq(directory_exists(test_dir), 1); 98 | ck_assert_int_eq(directory_exists("/nonexistent/directory/path"), 0); 99 | } 100 | END_TEST 101 | 102 | // Test: Execute command quiet 103 | START_TEST(test_execute_command_quiet) { 104 | // Test successful command 105 | ck_assert_int_eq(execute_command_quiet("true"), 0); 106 | 107 | // Test failing command 108 | ck_assert_int_ne(execute_command_quiet("false"), 0); 109 | } 110 | END_TEST 111 | 112 | // Test: Is automake project (standalone test without fixture) 113 | START_TEST(test_is_automake_project) { 114 | // Save current directory 115 | char cwd[1024]; 116 | getcwd(cwd, sizeof(cwd)); 117 | 118 | // Create a temporary directory 119 | char temp_dir[256]; 120 | strcpy(temp_dir, "/tmp/jc_automake_test_XXXXXX"); 121 | if (mkdtemp(temp_dir) == NULL) { 122 | ck_abort_msg("Failed to create temporary directory"); 123 | } 124 | 125 | // Change to test directory 126 | chdir(temp_dir); 127 | 128 | // Should not be an automake project 129 | ck_assert_int_eq(is_automake_project(), 0); 130 | 131 | // Create configure.ac 132 | FILE *f = fopen("configure.ac", "w"); 133 | ck_assert_ptr_nonnull(f); 134 | fprintf(f, "AC_INIT([test], [1.0])\n"); 135 | fclose(f); 136 | 137 | // Now it should be an automake project 138 | ck_assert_int_eq(is_automake_project(), 1); 139 | 140 | // Restore directory and cleanup 141 | chdir(cwd); 142 | char cmd[512]; 143 | snprintf(cmd, sizeof(cmd), "rm -rf %s", temp_dir); 144 | system(cmd); 145 | } 146 | END_TEST 147 | 148 | // Test: regex_replace - basic functionality 149 | START_TEST(test_regex_replace_basic) { 150 | const char *input = "Hello, World!"; 151 | const char *pattern = "World"; 152 | const char *replacement = "Universe"; 153 | 154 | char *result = regex_replace(input, pattern, replacement); 155 | ck_assert_ptr_nonnull(result); 156 | ck_assert_str_eq(result, "Hello, Universe!"); 157 | 158 | free(result); 159 | 160 | // Test multiple replacements 161 | input = "Hello, World! Hello, World!"; 162 | result = regex_replace(input, pattern, replacement); 163 | ck_assert_ptr_nonnull(result); 164 | ck_assert_str_eq(result, "Hello, Universe! Hello, Universe!"); 165 | 166 | free(result); 167 | 168 | // Test no match 169 | input = "Hello, Earth!"; 170 | result = regex_replace(input, pattern, replacement); 171 | ck_assert_ptr_nonnull(result); 172 | ck_assert_str_eq(result, "Hello, Earth!"); 173 | 174 | free(result); 175 | } 176 | END_TEST 177 | 178 | // Test: regex_replace - capture groups 179 | START_TEST(test_regex_replace_capture_groups) { 180 | // Test simple capture group 181 | const char *input = "Hello, World!"; 182 | const char *pattern = "(Hello), (World)!"; 183 | const char *replacement = "$2, $1!"; 184 | 185 | char *result = regex_replace(input, pattern, replacement); 186 | ck_assert_ptr_nonnull(result); 187 | ck_assert_str_eq(result, "World, Hello!"); 188 | 189 | free(result); 190 | 191 | // Test multiple capture groups and occurrences 192 | input = "John Doe (42) and Jane Smith (35)"; 193 | pattern = "([a-zA-Z]+) ([a-zA-Z]+) \\(([0-9]+)\\)"; 194 | replacement = "$2, $1: $3 years old"; 195 | 196 | result = regex_replace(input, pattern, replacement); 197 | ck_assert_ptr_nonnull(result); 198 | printf("result: %s\n", result); 199 | ck_assert_str_eq(result, "Doe, John: 42 years old and Smith, Jane: 35 years old"); 200 | 201 | free(result); 202 | 203 | // Test mixed capture groups and literal text 204 | input = "Item: 123, Quantity: 456"; 205 | pattern = "Item: ([0-9]+), Quantity: ([0-9]+)"; 206 | replacement = "Order - Item #$1, Count: $2 units"; 207 | 208 | result = regex_replace(input, pattern, replacement); 209 | ck_assert_ptr_nonnull(result); 210 | ck_assert_str_eq(result, "Order - Item #123, Count: 456 units"); 211 | 212 | free(result); 213 | 214 | // Test invalid capture group number 215 | input = "Hello, World!"; 216 | pattern = "(Hello), (World)!"; 217 | replacement = "$3, $4!"; 218 | 219 | result = regex_replace(input, pattern, replacement); 220 | ck_assert_ptr_nonnull(result); 221 | ck_assert_str_eq(result, ", !"); // Invalid groups should be replaced with empty string 222 | 223 | free(result); 224 | } 225 | END_TEST 226 | 227 | // Test: regex_replace - edge cases 228 | START_TEST(test_regex_replace_edge_cases) { 229 | // Test NULL inputs 230 | ck_assert_ptr_null(regex_replace(NULL, "pattern", "replacement")); 231 | ck_assert_ptr_null(regex_replace("input", NULL, "replacement")); 232 | ck_assert_ptr_null(regex_replace("input", "pattern", NULL)); 233 | 234 | // Test empty strings 235 | const char *input = ""; 236 | const char *pattern = "a"; 237 | const char *replacement = "b"; 238 | 239 | char *result = regex_replace(input, pattern, replacement); 240 | ck_assert_ptr_nonnull(result); 241 | ck_assert_str_eq(result, ""); 242 | 243 | free(result); 244 | 245 | // Test empty pattern 246 | input = "Hello"; 247 | pattern = ""; 248 | 249 | result = regex_replace(input, pattern, replacement); 250 | // Pattern compilation may fail, but function should handle it gracefully 251 | // If it returns NULL, that's acceptable behavior 252 | if (result) { 253 | free(result); 254 | } 255 | } 256 | END_TEST 257 | 258 | // Create test suite 259 | Suite *utils_suite(void) { 260 | Suite *s; 261 | TCase *tc_core; 262 | TCase *tc_standalone; 263 | 264 | s = suite_create("Utils"); 265 | 266 | // Core test case with fixture 267 | tc_core = tcase_create("Core"); 268 | tcase_add_checked_fixture(tc_core, setup, teardown); 269 | tcase_add_test(tc_core, test_create_directory); 270 | tcase_add_test(tc_core, test_file_exists); 271 | tcase_add_test(tc_core, test_write_and_read_file); 272 | tcase_add_test(tc_core, test_copy_file); 273 | tcase_add_test(tc_core, test_directory_exists); 274 | tcase_add_test(tc_core, test_execute_command_quiet); 275 | suite_add_tcase(s, tc_core); 276 | 277 | // Standalone test case without fixture 278 | tc_standalone = tcase_create("Standalone"); 279 | tcase_add_test(tc_standalone, test_is_automake_project); 280 | tcase_add_test(tc_standalone, test_regex_replace_basic); 281 | tcase_add_test(tc_standalone, test_regex_replace_capture_groups); 282 | tcase_add_test(tc_standalone, test_regex_replace_edge_cases); 283 | suite_add_tcase(s, tc_standalone); 284 | 285 | return s; 286 | } 287 | 288 | 289 | 290 | // Main function 291 | int main(void) { 292 | int number_failed; 293 | Suite *s; 294 | SRunner *sr; 295 | 296 | s = utils_suite(); 297 | sr = srunner_create(s); 298 | 299 | // Run tests 300 | srunner_run_all(sr, CK_NORMAL); 301 | number_failed = srunner_ntests_failed(sr); 302 | srunner_free(sr); 303 | 304 | return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; 305 | } 306 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef PATH_MAX 7 | #define PATH_MAX 4096 8 | #endif 9 | 10 | int create_directory(const char *path) { 11 | struct stat st = {0}; 12 | 13 | if (stat(path, &st) == -1) { 14 | if (mkdir(path, 0755) != 0) { 15 | perror("mkdir"); 16 | return -1; 17 | } 18 | } 19 | return 0; 20 | } 21 | 22 | int file_exists(const char *path) { 23 | return access(path, F_OK) == 0; 24 | } 25 | 26 | int directory_exists(const char *path) { 27 | struct stat st; 28 | if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { 29 | return 1; 30 | } 31 | return 0; 32 | } 33 | 34 | int copy_file(const char *src, const char *dst) { 35 | FILE *source = fopen(src, "r"); 36 | if (!source) { 37 | return -1; 38 | } 39 | 40 | FILE *dest = fopen(dst, "w"); 41 | if (!dest) { 42 | fclose(source); 43 | return -1; 44 | } 45 | 46 | char buffer[4096]; 47 | size_t bytes; 48 | while ((bytes = fread(buffer, 1, sizeof(buffer), source)) > 0) { 49 | if (fwrite(buffer, 1, bytes, dest) != bytes) { 50 | fclose(source); 51 | fclose(dest); 52 | return -1; 53 | } 54 | } 55 | 56 | fclose(source); 57 | fclose(dest); 58 | return 0; 59 | } 60 | 61 | int write_file(const char *path, const char *content) { 62 | FILE *file = fopen(path, "w"); 63 | if (!file) { 64 | perror("fopen"); 65 | return -1; 66 | } 67 | 68 | if (fputs(content, file) == EOF) { 69 | perror("fputs"); 70 | fclose(file); 71 | return -1; 72 | } 73 | 74 | fclose(file); 75 | return 0; 76 | } 77 | 78 | char *read_file(const char *path) { 79 | FILE *file = fopen(path, "r"); 80 | if (!file) { 81 | return NULL; 82 | } 83 | 84 | fseek(file, 0, SEEK_END); 85 | long size = ftell(file); 86 | fseek(file, 0, SEEK_SET); 87 | 88 | char *content = malloc(size + 1); 89 | if (!content) { 90 | fclose(file); 91 | return NULL; 92 | } 93 | 94 | size_t read_size = fread(content, 1, size, file); 95 | content[read_size] = '\0'; 96 | 97 | fclose(file); 98 | return content; 99 | } 100 | 101 | int execute_command(const char *cmd) { 102 | printf("Executing: %s\n", cmd); 103 | int ret = system(cmd); 104 | if (ret != 0) { 105 | fprintf(stderr, "Command failed with exit code: %d\n", ret); 106 | return -1; 107 | } 108 | return 0; 109 | } 110 | 111 | int execute_command_quiet(const char *cmd) { 112 | int ret = system(cmd); 113 | return ret == 0 ? 0 : -1; 114 | } 115 | 116 | char *get_template_path(const char *template_name) { 117 | static char path[PATH_MAX]; 118 | 119 | // Try to get from environment variable first (for development) 120 | const char *jc_data = getenv("JC_DATA_DIR"); 121 | if (jc_data) { 122 | snprintf(path, sizeof(path), "%s/templates/%s", jc_data, template_name); 123 | if (file_exists(path)) { 124 | return path; 125 | } 126 | } 127 | 128 | // Try installed location 129 | #ifdef PREFIX 130 | snprintf(path, sizeof(path), "%s/share/jc/templates/%s", PREFIX, template_name); 131 | if (file_exists(path)) { 132 | return path; 133 | } 134 | #endif 135 | 136 | // Try common installation locations 137 | const char *install_prefixes[] = { 138 | "/usr/local", 139 | "/usr", 140 | NULL 141 | }; 142 | 143 | for (int i = 0; install_prefixes[i] != NULL; i++) { 144 | snprintf(path, sizeof(path), "%s/share/jc/templates/%s", install_prefixes[i], template_name); 145 | if (file_exists(path)) { 146 | return path; 147 | } 148 | } 149 | 150 | // Try local development location 151 | snprintf(path, sizeof(path), "src/templates/%s", template_name); 152 | if (file_exists(path)) { 153 | return path; 154 | } 155 | 156 | return NULL; 157 | } 158 | 159 | int is_automake_project(void) { 160 | return file_exists("configure.ac") || file_exists("configure.in"); 161 | } 162 | 163 | int find_executable(const char *dir, char *output, size_t output_size) { 164 | DIR *d = opendir(dir); 165 | if (!d) { 166 | return -1; 167 | } 168 | 169 | struct dirent *entry; 170 | while ((entry = readdir(d)) != NULL) { 171 | if (entry->d_name[0] == '.') { 172 | continue; 173 | } 174 | 175 | char path[PATH_MAX]; 176 | snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name); 177 | 178 | struct stat st; 179 | if (stat(path, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) { 180 | snprintf(output, output_size, "%s", path); 181 | closedir(d); 182 | return 0; 183 | } 184 | } 185 | 186 | closedir(d); 187 | return -1; 188 | } 189 | 190 | /** 191 | * Replace all occurrences of a regex pattern in a string with support for capture groups 192 | * 193 | * @param input The input string 194 | * @param pattern The regex pattern to search for 195 | * @param replacement The replacement string (supports $1, $2, etc. for capture groups) 196 | * @return A new dynamically allocated string with replacements, or NULL on error 197 | * (Caller is responsible for freeing the returned string) 198 | */ 199 | char *regex_replace(const char *input, const char *pattern, const char *replacement) { 200 | if (!input || !pattern || !replacement) { 201 | return NULL; 202 | } 203 | 204 | if (strlen(input) == 0) { 205 | char *output = (char *)malloc(1); 206 | output[0] = '\0'; 207 | return output; 208 | } 209 | 210 | if (strlen(pattern) == 0) { 211 | int len = strlen(input) + 1; 212 | char *output = (char *)malloc(len); 213 | strncpy(output, input, len); 214 | 215 | return output; 216 | } 217 | 218 | regex_t regex; 219 | int ret; 220 | 221 | // Compile the regex pattern 222 | ret = regcomp(®ex, pattern, REG_EXTENDED | REG_NEWLINE); 223 | if (ret != 0) { 224 | char error_buf[1024]; 225 | regerror(ret, ®ex, error_buf, sizeof(error_buf)); 226 | fprintf(stderr, "Regex compilation error: %s\n", error_buf); 227 | return NULL; 228 | } 229 | 230 | // Get the number of capture groups 231 | size_t num_groups = regex.re_nsub + 1; // +1 for the entire match 232 | 233 | // Find the first match to determine required buffer size 234 | regmatch_t *matches = (regmatch_t *)malloc(num_groups * sizeof(regmatch_t)); 235 | if (!matches) { 236 | regfree(®ex); 237 | return NULL; 238 | } 239 | 240 | const char *current = input; 241 | size_t output_size = strlen(input) + 1; // Start with the original size 242 | 243 | // Calculate the required output buffer size 244 | while ((ret = regexec(®ex, current, num_groups, matches, 0)) == 0) { 245 | // Calculate the length of the replacement with capture groups 246 | size_t repl_len = 0; 247 | const char *r = replacement; 248 | while (*r) { 249 | if (*r == '$' && *(r + 1) >= '0' && *(r + 1) <= '9') { 250 | // Handle capture group reference $1, $2, etc. 251 | int group_num = *(r + 1) - '0'; 252 | if (group_num < num_groups && 253 | matches[group_num].rm_so != -1 && 254 | matches[group_num].rm_eo != -1) { 255 | repl_len += matches[group_num].rm_eo - matches[group_num].rm_so; 256 | } 257 | r += 2; // Skip $ and digit 258 | } else { 259 | repl_len++; // Regular character 260 | r++; 261 | } 262 | } 263 | 264 | // Update output size 265 | output_size += repl_len - (matches[0].rm_eo - matches[0].rm_so); 266 | current += matches[0].rm_eo; 267 | } 268 | 269 | // Allocate output buffer 270 | char *output = (char *)malloc(output_size); 271 | if (!output) { 272 | free(matches); 273 | regfree(®ex); 274 | return NULL; 275 | } 276 | 277 | // Perform the actual replacement with capture groups 278 | current = input; 279 | char *out_ptr = output; 280 | size_t input_len = strlen(input); 281 | 282 | while ((ret = regexec(®ex, current, num_groups, matches, 0)) == 0) { 283 | // Copy text before match 284 | size_t prefix_len = matches[0].rm_so; 285 | strncpy(out_ptr, current, prefix_len); 286 | out_ptr += prefix_len; 287 | 288 | // Process replacement string with capture groups 289 | const char *r = replacement; 290 | while (*r) { 291 | if (*r == '$' && *(r + 1) >= '0' && *(r + 1) <= '9') { 292 | // Handle capture group reference $1, $2, etc. 293 | int group_num = *(r + 1) - '0'; 294 | if (group_num < num_groups && 295 | matches[group_num].rm_so != -1 && 296 | matches[group_num].rm_eo != -1) { 297 | size_t group_len = matches[group_num].rm_eo - matches[group_num].rm_so; 298 | strncpy(out_ptr, current + matches[group_num].rm_so, group_len); 299 | out_ptr += group_len; 300 | } 301 | r += 2; // Skip $ and digit 302 | } else { 303 | // Copy regular character 304 | *out_ptr++ = *r++; 305 | } 306 | } 307 | 308 | // Move current pointer past the match 309 | current += matches[0].rm_eo; 310 | } 311 | 312 | // Copy any remaining text after the last match 313 | if (current < input + input_len) { 314 | strcpy(out_ptr, current); 315 | out_ptr += strlen(current); 316 | } 317 | 318 | // Null-terminate the output string 319 | *out_ptr = '\0'; 320 | 321 | // Free resources 322 | free(matches); 323 | regfree(®ex); 324 | 325 | return output; 326 | } 327 | -------------------------------------------------------------------------------- /src/cmd_add.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | #include 4 | #include 5 | 6 | #ifndef PATH_MAX 7 | #define PATH_MAX 4096 8 | #endif 9 | 10 | // Forward declarations 11 | static int add_file(const char *src_path, const char *dst_path); 12 | static int add_directory(const char *src_path, const char *dst_path); 13 | static int add_dependency(const char *dep_name); 14 | static int update_makefile_am(const char *file_path); 15 | static int is_c_source_file(const char *path); 16 | static int is_header_file(const char *path); 17 | static void print_add_usage(void); 18 | 19 | // Check if file is a C source file 20 | static int is_c_source_file(const char *path) { 21 | const char *ext = strrchr(path, '.'); 22 | if (!ext) return 0; 23 | return (strcmp(ext, ".c") == 0); 24 | } 25 | 26 | // Check if file is a header file (currently unused but kept for future use) 27 | __attribute__((unused)) static int is_header_file(const char *path) { 28 | const char *ext = strrchr(path, '.'); 29 | if (!ext) return 0; 30 | return (strcmp(ext, ".h") == 0); 31 | } 32 | 33 | // Print usage information for add command 34 | static void print_add_usage(void) { 35 | printf("Usage: jc add \n\n"); 36 | printf("Types:\n"); 37 | printf(" file Add a single file to the project\n"); 38 | printf(" dir Add a directory to the project\n"); 39 | printf(" dep Add a library dependency\n\n"); 40 | printf("Examples:\n"); 41 | printf(" jc add file utils.c\n"); 42 | printf(" jc add file src/utils.c\n"); 43 | printf(" jc add dir src/lib\n"); 44 | printf(" jc add dep math\n"); 45 | printf(" jc add dep pthread\n\n"); 46 | } 47 | 48 | // Add a single file to the project 49 | static int add_file(const char *src_path, const char *dst_path) { 50 | // Check if source file exists 51 | if (!file_exists(src_path)) { 52 | fprintf(stderr, "Error: Source file '%s' does not exist\n", src_path); 53 | return 1; 54 | } 55 | 56 | // Create destination directory if it doesn't exist 57 | char dst_dir[PATH_MAX]; 58 | strncpy(dst_dir, dst_path, sizeof(dst_dir) - 1); 59 | dst_dir[sizeof(dst_dir) - 1] = '\0'; 60 | char *dir = dirname(dst_dir); 61 | 62 | if (strcmp(dir, ".") != 0) { 63 | if (create_directory(dir) != 0) { 64 | fprintf(stderr, "Error: Failed to create destination directory\n"); 65 | return 1; 66 | } 67 | } 68 | 69 | // Copy the file 70 | if (copy_file(src_path, dst_path) != 0) { 71 | fprintf(stderr, "Error: Failed to copy file from '%s' to '%s'\n", src_path, dst_path); 72 | return 1; 73 | } 74 | 75 | printf("✓ Added file: %s -> %s\n", src_path, dst_path); 76 | 77 | // If it's a C source file, update Makefile.am 78 | if (is_c_source_file(dst_path)) { 79 | update_makefile_am(dst_path); 80 | } 81 | 82 | return 0; 83 | } 84 | 85 | // Add a directory to the project 86 | static int add_directory(const char *src_path, const char *dst_path) { 87 | // Check if source directory exists 88 | if (!directory_exists(src_path)) { 89 | fprintf(stderr, "Error: Source directory '%s' does not exist\n", src_path); 90 | return 1; 91 | } 92 | 93 | // Create destination directory 94 | if (create_directory(dst_path) != 0) { 95 | fprintf(stderr, "Error: Failed to create destination directory\n"); 96 | return 1; 97 | } 98 | 99 | // Open source directory 100 | DIR *dir = opendir(src_path); 101 | if (!dir) { 102 | fprintf(stderr, "Error: Cannot open source directory '%s'\n", src_path); 103 | return 1; 104 | } 105 | 106 | struct dirent *entry; 107 | int error = 0; 108 | int added_files = 0; 109 | 110 | while ((entry = readdir(dir)) != NULL) { 111 | // Skip hidden files and directories 112 | if (entry->d_name[0] == '.') { 113 | continue; 114 | } 115 | 116 | char src_file_path[PATH_MAX]; 117 | char dst_file_path[PATH_MAX]; 118 | 119 | snprintf(src_file_path, sizeof(src_file_path), "%s/%s", src_path, entry->d_name); 120 | snprintf(dst_file_path, sizeof(dst_file_path), "%s/%s", dst_path, entry->d_name); 121 | 122 | struct stat st; 123 | if (stat(src_file_path, &st) != 0) { 124 | continue; 125 | } 126 | 127 | if (S_ISDIR(st.st_mode)) { 128 | // Recursively add subdirectory 129 | if (add_directory(src_file_path, dst_file_path) != 0) { 130 | error = 1; 131 | break; 132 | } 133 | added_files++; 134 | } else { 135 | // Add file 136 | if (add_file(src_file_path, dst_file_path) != 0) { 137 | error = 1; 138 | break; 139 | } 140 | added_files++; 141 | } 142 | } 143 | 144 | closedir(dir); 145 | 146 | if (error) { 147 | fprintf(stderr, "Error: Failed to copy some files from directory '%s'\n", src_path); 148 | return 1; 149 | } 150 | 151 | if (added_files > 0) { 152 | printf("✓ Added directory: %s -> %s (%d items)\n", src_path, dst_path, added_files); 153 | } else { 154 | printf("✓ Added directory: %s -> %s (empty directory)\n", src_path, dst_path); 155 | } 156 | 157 | return 0; 158 | } 159 | 160 | // Add a library dependency 161 | static int add_dependency(const char *dep_name) { 162 | printf("Adding dependency: %s\n", dep_name); 163 | 164 | // Check if we're in an automake project 165 | if (!is_automake_project()) { 166 | fprintf(stderr, "Error: Not in an automake project directory\n"); 167 | fprintf(stderr, "Run this command from a project created with 'jc new'\n"); 168 | return 1; 169 | } 170 | 171 | // Check if src/Makefile.am exists 172 | if (!file_exists("src/Makefile.am")) { 173 | fprintf(stderr, "Error: src/Makefile.am not found\n"); 174 | return 1; 175 | } 176 | 177 | // Read current Makefile.am content 178 | char *content = read_file("src/Makefile.am"); 179 | if (!content) { 180 | fprintf(stderr, "Error: Failed to read src/Makefile.am\n"); 181 | return 1; 182 | } 183 | 184 | // Check if dependency is already added 185 | char lib_flag[256]; 186 | snprintf(lib_flag, sizeof(lib_flag), "-l%s", dep_name); 187 | 188 | if (strstr(content, lib_flag) != NULL) { 189 | printf("✓ Dependency '%s' is already added\n", dep_name); 190 | free(content); 191 | return 0; 192 | } 193 | 194 | // Find the line with _LDFLAGS or _LDADD and add the dependency 195 | char *line_start = content; 196 | char *new_content = malloc(strlen(content) + 256); 197 | if (!new_content) { 198 | free(content); 199 | fprintf(stderr, "Error: Memory allocation failed\n"); 200 | return 1; 201 | } 202 | 203 | char *output = new_content; 204 | int found_ldflags = 0; 205 | int found_ldadd = 0; 206 | 207 | while (*line_start) { 208 | char *line_end = strchr(line_start, '\n'); 209 | if (!line_end) { 210 | line_end = line_start + strlen(line_start); 211 | } 212 | 213 | // Check if this line contains _LDFLAGS or _LDADD 214 | if (strstr(line_start, "_LDFLAGS") != NULL && !found_ldflags) { 215 | // Copy the line up to the end 216 | size_t len = line_end - line_start; 217 | memcpy(output, line_start, len); 218 | output += len; 219 | 220 | // Add our library flag 221 | if (line_end > line_start && *(line_end - 1) != ' ') { 222 | *output++ = ' '; 223 | } 224 | sprintf(output, "-l%s", dep_name); 225 | output += strlen(dep_name) + 2; 226 | 227 | found_ldflags = 1; 228 | } else if (strstr(line_start, "_LDADD") != NULL && !found_ldadd) { 229 | // Copy the line up to the end 230 | size_t len = line_end - line_start; 231 | memcpy(output, line_start, len); 232 | output += len; 233 | 234 | // Add our library flag 235 | if (line_end > line_start && *(line_end - 1) != ' ') { 236 | *output++ = ' '; 237 | } 238 | sprintf(output, "-l%s", dep_name); 239 | output += strlen(dep_name) + 2; 240 | 241 | found_ldadd = 1; 242 | } else { 243 | // Copy the line as is 244 | size_t len = line_end - line_start; 245 | memcpy(output, line_start, len); 246 | output += len; 247 | } 248 | 249 | if (*line_end == '\n') { 250 | *output++ = '\n'; 251 | line_start = line_end + 1; 252 | } else { 253 | *output++ = '\0'; 254 | break; 255 | } 256 | } 257 | 258 | // If neither _LDFLAGS nor _LDADD was found, add _LDFLAGS line 259 | if (!found_ldflags && !found_ldadd) { 260 | // Find the binary name from the first line 261 | char *bin_line = strstr(new_content, "bin_PROGRAMS"); 262 | if (bin_line) { 263 | char *bin_name_start = strchr(bin_line, '='); 264 | if (bin_name_start) { 265 | bin_name_start++; // Skip '=' 266 | while (*bin_name_start == ' ' || *bin_name_start == '\t') { 267 | bin_name_start++; 268 | } 269 | char *bin_name_end = bin_name_start; 270 | while (*bin_name_end && *bin_name_end != '\n' && *bin_name_end != ' ') { 271 | bin_name_end++; 272 | } 273 | 274 | // Extract binary name 275 | char bin_name[256]; 276 | size_t name_len = bin_name_end - bin_name_start; 277 | if (name_len < sizeof(bin_name)) { 278 | memcpy(bin_name, bin_name_start, name_len); 279 | bin_name[name_len] = '\0'; 280 | 281 | // Add _LDFLAGS line 282 | sprintf(output, "\n%s_LDFLAGS = -l%s\n", bin_name, dep_name); 283 | } 284 | } 285 | } 286 | } 287 | 288 | // Write the updated content back 289 | if (write_file("src/Makefile.am", new_content) != 0) { 290 | fprintf(stderr, "Error: Failed to update src/Makefile.am\n"); 291 | free(content); 292 | free(new_content); 293 | return 1; 294 | } 295 | 296 | printf("✓ Added dependency '%s' to src/Makefile.am\n", dep_name); 297 | printf(" You may need to run 'jc build' to rebuild the project\n"); 298 | 299 | free(content); 300 | free(new_content); 301 | return 0; 302 | } 303 | 304 | // Update Makefile.am to include new source files 305 | static int update_makefile_am(const char *file_path) { 306 | // Only update if we're in an automake project 307 | if (!is_automake_project()) { 308 | return 0; 309 | } 310 | 311 | if (!file_exists("src/Makefile.am")) { 312 | return 0; 313 | } 314 | 315 | // Read current Makefile.am content 316 | char *content = read_file("src/Makefile.am"); 317 | if (!content) { 318 | return -1; 319 | } 320 | 321 | // Extract just the filename from the full path 322 | char *filename = basename((char*)file_path); 323 | 324 | // Check if file is already in the SOURCES list 325 | char *sources_line = strstr(content, "_SOURCES"); 326 | if (sources_line) { 327 | if (strstr(sources_line, filename) != NULL) { 328 | // File already in SOURCES, nothing to do 329 | free(content); 330 | return 0; 331 | } 332 | } 333 | 334 | // Add the file to SOURCES 335 | char *new_content = malloc(strlen(content) + strlen(filename) + 10); 336 | if (!new_content) { 337 | free(content); 338 | return -1; 339 | } 340 | 341 | // Find the end of the SOURCES line and add our file 342 | char *output = new_content; 343 | char *line_start = content; 344 | int sources_updated = 0; 345 | 346 | while (*line_start) { 347 | char *line_end = strchr(line_start, '\n'); 348 | if (!line_end) { 349 | line_end = line_start + strlen(line_start); 350 | } 351 | 352 | // Look for the specific SOURCES line (e.g., "test_project_SOURCES = main.c") 353 | if (strstr(line_start, "_SOURCES =") != NULL && !sources_updated) { 354 | // Copy the line up to the end 355 | size_t len = line_end - line_start; 356 | memcpy(output, line_start, len); 357 | output += len; 358 | 359 | // Add our source file 360 | if (line_end > line_start && *(line_end - 1) != ' ') { 361 | *output++ = ' '; 362 | } 363 | sprintf(output, " %s", filename); 364 | output += strlen(filename) + 1; 365 | 366 | sources_updated = 1; 367 | } else { 368 | // Copy the line as is 369 | size_t len = line_end - line_start; 370 | memcpy(output, line_start, len); 371 | output += len; 372 | } 373 | 374 | if (*line_end == '\n') { 375 | *output++ = '\n'; 376 | line_start = line_end + 1; 377 | } else { 378 | *output++ = '\0'; 379 | break; 380 | } 381 | } 382 | 383 | // Write the updated content back 384 | if (write_file("src/Makefile.am", new_content) != 0) { 385 | free(content); 386 | free(new_content); 387 | return -1; 388 | } 389 | 390 | printf("✓ Updated src/Makefile.am to include %s\n", filename); 391 | 392 | free(content); 393 | free(new_content); 394 | return 0; 395 | } 396 | 397 | int cmd_add(int argc, char *argv[]) { 398 | if (argc < 3) { 399 | print_add_usage(); 400 | return 1; 401 | } 402 | 403 | const char *type = argv[1]; 404 | const char *target = argv[2]; 405 | 406 | // Check if we're in an automake project (except for dependency addition) 407 | if (strcmp(type, "dep") != 0 && !is_automake_project()) { 408 | fprintf(stderr, "Error: Not in an automake project directory\n"); 409 | fprintf(stderr, "Run this command from a project created with 'jc new'\n"); 410 | return 1; 411 | } 412 | 413 | if (strcmp(type, "file") == 0) { 414 | // Add a single file 415 | char dst_path[PATH_MAX]; 416 | 417 | // Always put files in src/ directory, extract filename from source path 418 | char *filename = basename((char*)target); 419 | snprintf(dst_path, sizeof(dst_path), "src/%s", filename); 420 | 421 | return add_file(target, dst_path); 422 | 423 | } else if (strcmp(type, "dir") == 0) { 424 | // Add a directory 425 | char dst_path[PATH_MAX]; 426 | 427 | // Extract directory name from source path 428 | char *dir_name = basename((char*)target); 429 | snprintf(dst_path, sizeof(dst_path), "src/%s", dir_name); 430 | 431 | return add_directory(target, dst_path); 432 | 433 | } else if (strcmp(type, "dep") == 0) { 434 | // Add a library dependency 435 | return add_dependency(target); 436 | 437 | } else { 438 | fprintf(stderr, "Error: Unknown type '%s'\n\n", type); 439 | print_add_usage(); 440 | return 1; 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/cmd_test.c: -------------------------------------------------------------------------------- 1 | #include "jc.h" 2 | #include "utils.h" 3 | #include 4 | 5 | #ifndef PATH_MAX 6 | #define PATH_MAX 4096 7 | #endif 8 | 9 | // Forward declarations 10 | static int test_add(const char *source_file); 11 | static int test_remove(const char *source_file); 12 | static int test_run(const char *test_file); 13 | static void print_test_usage(void); 14 | static char *generate_test_template(const char *basename); 15 | static int create_initial_test_makefile(void); 16 | static int update_test_makefile(const char *test_file); 17 | static int remove_from_test_makefile(const char *test_file); 18 | 19 | // Print usage information for test command 20 | static void print_test_usage(void) { 21 | printf("Usage: jc test [options]\n\n"); 22 | printf("Subcommands:\n"); 23 | printf(" add Create a test file for the given source file\n"); 24 | printf(" remove Remove the test file for the given source file\n"); 25 | printf(" run [test_file] Run tests (all tests if no file specified)\n\n"); 26 | printf("Examples:\n"); 27 | printf(" jc test add src/utils.c # Creates tests/test_utils.c\n"); 28 | printf(" jc test remove src/utils.c # Removes tests/test_utils.c\n"); 29 | printf(" jc test run # Run all tests\n"); 30 | printf(" jc test run test_utils # Run specific test\n\n"); 31 | } 32 | 33 | // Generate test template content 34 | static char *generate_test_template(const char *basename) { 35 | static char template[4096]; 36 | 37 | snprintf(template, sizeof(template), 38 | "#include \n" 39 | "#include \n" 40 | "#include \n" 41 | "\n" 42 | "// Test: Example test case for %s\n" 43 | "START_TEST(test_example) {\n" 44 | " // TODO: Add your test implementation\n" 45 | " ck_assert_int_eq(1, 1);\n" 46 | "}\n" 47 | "END_TEST\n" 48 | "\n" 49 | "// Create test suite\n" 50 | "Suite *%s_suite(void) {\n" 51 | " Suite *s;\n" 52 | " TCase *tc_core;\n" 53 | " \n" 54 | " s = suite_create(\"%s\");\n" 55 | " \n" 56 | " // Core test case\n" 57 | " tc_core = tcase_create(\"Core\");\n" 58 | " tcase_add_test(tc_core, test_example);\n" 59 | " suite_add_tcase(s, tc_core);\n" 60 | " \n" 61 | " return s;\n" 62 | "}\n" 63 | "\n" 64 | "// Main function\n" 65 | "int main(void) {\n" 66 | " int number_failed;\n" 67 | " Suite *s;\n" 68 | " SRunner *sr;\n" 69 | " \n" 70 | " s = %s_suite();\n" 71 | " sr = srunner_create(s);\n" 72 | " \n" 73 | " // Run tests\n" 74 | " srunner_run_all(sr, CK_NORMAL);\n" 75 | " number_failed = srunner_ntests_failed(sr);\n" 76 | " srunner_free(sr);\n" 77 | " \n" 78 | " return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;\n" 79 | "}\n", 80 | basename, basename, basename, basename); 81 | 82 | return template; 83 | } 84 | 85 | // Create initial tests/Makefile.am if it doesn't exist 86 | static int create_initial_test_makefile(void) { 87 | const char *makefile_path = "tests/Makefile.am"; 88 | const char *initial_content = 89 | "if ENABLE_TESTS\n" 90 | "\n" 91 | "# Check framework based tests\n" 92 | "check_PROGRAMS =\n" 93 | "\n" 94 | "TESTS =\n" 95 | "\n" 96 | "endif\n"; 97 | 98 | if (write_file(makefile_path, initial_content) != 0) { 99 | return 1; 100 | } 101 | 102 | printf("✓ Created tests/Makefile.am\n"); 103 | return 0; 104 | } 105 | 106 | // Update tests/Makefile.am to include new test 107 | static int update_test_makefile(const char *test_file) { 108 | const char *makefile_path = "tests/Makefile.am"; 109 | 110 | if (!file_exists(makefile_path)) { 111 | if (create_initial_test_makefile() != 0) { 112 | fprintf(stderr, "Error: Failed to create tests/Makefile.am\n"); 113 | return 1; 114 | } 115 | } 116 | 117 | // Read current Makefile.am content 118 | char *content = read_file(makefile_path); 119 | if (!content) { 120 | fprintf(stderr, "Error: Failed to read tests/Makefile.am\n"); 121 | return 1; 122 | } 123 | 124 | // Extract test program name (without .c extension) 125 | char test_prog[256]; 126 | strncpy(test_prog, test_file, sizeof(test_prog) - 1); 127 | test_prog[sizeof(test_prog) - 1] = '\0'; 128 | char *ext = strstr(test_prog, ".c"); 129 | if (ext) *ext = '\0'; 130 | 131 | // Check if test is already in the makefile 132 | if (strstr(content, test_prog) != NULL) { 133 | printf("✓ Test '%s' is already in tests/Makefile.am\n", test_prog); 134 | free(content); 135 | return 0; 136 | } 137 | 138 | // Add the test program to check_PROGRAMS and TESTS 139 | char new_content[8192]; 140 | char *output = new_content; 141 | char *line_start = content; 142 | int added_to_programs = 0; 143 | int added_to_tests = 0; 144 | int added_program_entry = 0; 145 | 146 | while (*line_start) { 147 | char *line_end = strchr(line_start, '\n'); 148 | if (!line_end) { 149 | line_end = line_start + strlen(line_start); 150 | } 151 | 152 | // Copy the current line, but modify if needed 153 | size_t len = line_end - line_start; 154 | 155 | // Add to check_PROGRAMS line 156 | if (!added_to_programs && strstr(line_start, "check_PROGRAMS") != NULL && strstr(line_start, "check_PROGRAMS") < line_end) { 157 | // Insert test program name 158 | memcpy(output, line_start, len); 159 | output += len; 160 | output += sprintf(output, " %s", test_prog); 161 | if (*line_end == '\n') { 162 | *output++ = '\n'; 163 | } 164 | added_to_programs = 1; 165 | } 166 | // Add program definition after check_PROGRAMS line 167 | else if (added_to_programs && !added_program_entry && *line_start == '\n') { 168 | // Add new test program definition 169 | *output++ = '\n'; 170 | output += sprintf(output, "%s_SOURCES = %s\n", test_prog, test_file); 171 | output += sprintf(output, "%s_CFLAGS = -I$(top_srcdir)/src $(CHECK_CFLAGS) -Wall -Wextra -g\n", test_prog); 172 | output += sprintf(output, "%s_LDADD = $(CHECK_LIBS)\n", test_prog); 173 | added_program_entry = 1; 174 | } 175 | // Add to TESTS line 176 | else if (!added_to_tests && strstr(line_start, "TESTS =") != NULL && strstr(line_start, "TESTS =") < line_end) { 177 | // Insert test program name 178 | memcpy(output, line_start, len); 179 | output += len; 180 | output += sprintf(output, " %s", test_prog); 181 | if (*line_end == '\n') { 182 | *output++ = '\n'; 183 | } 184 | added_to_tests = 1; 185 | } 186 | else { 187 | // Copy the line as is 188 | memcpy(output, line_start, len); 189 | output += len; 190 | if (*line_end == '\n') { 191 | *output++ = '\n'; 192 | } 193 | } 194 | 195 | if (*line_end == '\n') { 196 | line_start = line_end + 1; 197 | } else { 198 | break; 199 | } 200 | } 201 | 202 | *output = '\0'; 203 | 204 | // Write updated content 205 | if (write_file(makefile_path, new_content) != 0) { 206 | fprintf(stderr, "Error: Failed to update tests/Makefile.am\n"); 207 | free(content); 208 | return 1; 209 | } 210 | 211 | printf("✓ Updated tests/Makefile.am to include %s\n", test_prog); 212 | free(content); 213 | return 0; 214 | } 215 | 216 | // Remove test from tests/Makefile.am 217 | static int remove_from_test_makefile(const char *test_file) { 218 | const char *makefile_path = "tests/Makefile.am"; 219 | 220 | if (!file_exists(makefile_path)) { 221 | return 0; // Nothing to remove 222 | } 223 | 224 | // Extract test program name 225 | char test_prog[256]; 226 | strncpy(test_prog, test_file, sizeof(test_prog) - 1); 227 | test_prog[sizeof(test_prog) - 1] = '\0'; 228 | char *ext = strstr(test_prog, ".c"); 229 | if (ext) *ext = '\0'; 230 | 231 | // Read current content 232 | char *content = read_file(makefile_path); 233 | if (!content) { 234 | return 1; 235 | } 236 | 237 | // Remove test from content 238 | char new_content[8192]; 239 | char *output = new_content; 240 | char *line_start = content; 241 | int skip_next_empty = 0; 242 | 243 | while (*line_start) { 244 | char *line_end = strchr(line_start, '\n'); 245 | if (!line_end) { 246 | line_end = line_start + strlen(line_start); 247 | } 248 | 249 | size_t len = line_end - line_start; 250 | int should_copy = 1; 251 | 252 | // Check if this line is related to the test program 253 | // Skip lines: test_prog_SOURCES, test_prog_CFLAGS, test_prog_LDADD 254 | if (len > 0 && strncmp(line_start, test_prog, strlen(test_prog)) == 0 && 255 | (strstr(line_start, "_SOURCES") || strstr(line_start, "_CFLAGS") || 256 | strstr(line_start, "_LDADD"))) { 257 | should_copy = 0; 258 | skip_next_empty = 1; // Skip the following empty line if any 259 | } 260 | // Remove from check_PROGRAMS line 261 | else if (strstr(line_start, "check_PROGRAMS") != NULL && strstr(line_start, test_prog) != NULL) { 262 | // Reconstruct line without test_prog 263 | char temp_line[512]; 264 | strncpy(temp_line, line_start, len); 265 | temp_line[len] = '\0'; 266 | 267 | char *prog_pos = strstr(temp_line, test_prog); 268 | if (prog_pos) { 269 | // Remove test_prog and any surrounding spaces 270 | char new_line[512] = ""; 271 | size_t before_len = prog_pos - temp_line; 272 | strncat(new_line, temp_line, before_len); 273 | 274 | char *after = prog_pos + strlen(test_prog); 275 | while (*after == ' ') after++; // Skip spaces after test_prog 276 | 277 | // Remove trailing space before test_prog if needed 278 | size_t new_len = strlen(new_line); 279 | if (new_len > 0 && new_line[new_len - 1] == ' ') { 280 | new_line[new_len - 1] = '\0'; 281 | } 282 | 283 | strcat(new_line, after); 284 | output += sprintf(output, "%s\n", new_line); 285 | should_copy = 0; 286 | } 287 | } 288 | // Remove from TESTS line 289 | else if (strstr(line_start, "TESTS =") != NULL && strstr(line_start, test_prog) != NULL) { 290 | // Reconstruct line without test_prog 291 | char temp_line[512]; 292 | strncpy(temp_line, line_start, len); 293 | temp_line[len] = '\0'; 294 | 295 | char *test_pos = strstr(temp_line, test_prog); 296 | if (test_pos) { 297 | // Remove test_prog and any surrounding spaces 298 | char new_line[512] = ""; 299 | size_t before_len = test_pos - temp_line; 300 | strncat(new_line, temp_line, before_len); 301 | 302 | char *after = test_pos + strlen(test_prog); 303 | while (*after == ' ') after++; // Skip spaces after test_prog 304 | 305 | // Remove trailing space before test_prog if needed 306 | size_t new_len = strlen(new_line); 307 | if (new_len > 0 && new_line[new_len - 1] == ' ') { 308 | new_line[new_len - 1] = '\0'; 309 | } 310 | 311 | strcat(new_line, after); 312 | output += sprintf(output, "%s\n", new_line); 313 | should_copy = 0; 314 | } 315 | } 316 | // Skip empty lines after test program definitions 317 | else if (skip_next_empty && len == 0) { 318 | should_copy = 0; 319 | skip_next_empty = 0; 320 | } 321 | 322 | // Copy the line if we should 323 | if (should_copy) { 324 | skip_next_empty = 0; 325 | memcpy(output, line_start, len); 326 | output += len; 327 | if (*line_end == '\n') { 328 | *output++ = '\n'; 329 | } 330 | } 331 | 332 | if (*line_end == '\n') { 333 | line_start = line_end + 1; 334 | } else { 335 | break; 336 | } 337 | } 338 | 339 | *output = '\0'; 340 | 341 | // Write updated content 342 | if (write_file(makefile_path, new_content) != 0) { 343 | free(content); 344 | return 1; 345 | } 346 | 347 | free(content); 348 | return 0; 349 | } 350 | 351 | // Add a test file for a source file 352 | static int test_add(const char *source_file) { 353 | // Check if we're in an automake project 354 | if (!is_automake_project()) { 355 | fprintf(stderr, "Error: Not in an automake project directory\n"); 356 | fprintf(stderr, "Run this command from a project created with 'jc new'\n"); 357 | return 1; 358 | } 359 | 360 | // Check if source file exists 361 | if (!file_exists(source_file)) { 362 | fprintf(stderr, "Error: Source file '%s' does not exist\n", source_file); 363 | return 1; 364 | } 365 | 366 | // Extract basename without extension 367 | char *source_copy = strdup(source_file); 368 | char *base = basename(source_copy); 369 | char basename_no_ext[256]; 370 | strncpy(basename_no_ext, base, sizeof(basename_no_ext) - 1); 371 | basename_no_ext[sizeof(basename_no_ext) - 1] = '\0'; 372 | 373 | // Remove .c extension if present 374 | char *dot = strrchr(basename_no_ext, '.'); 375 | if (dot) *dot = '\0'; 376 | 377 | // Create test file path 378 | char test_file_path[PATH_MAX]; 379 | snprintf(test_file_path, sizeof(test_file_path), "tests/test_%s.c", basename_no_ext); 380 | 381 | // Check if test file already exists 382 | if (file_exists(test_file_path)) { 383 | fprintf(stderr, "Error: Test file '%s' already exists\n", test_file_path); 384 | free(source_copy); 385 | return 1; 386 | } 387 | 388 | // Ensure tests directory exists 389 | if (!directory_exists("tests")) { 390 | if (create_directory("tests") != 0) { 391 | fprintf(stderr, "Error: Failed to create tests directory\n"); 392 | free(source_copy); 393 | return 1; 394 | } 395 | } 396 | 397 | // Generate test template 398 | char *template_content = generate_test_template(basename_no_ext); 399 | 400 | // Write test file 401 | if (write_file(test_file_path, template_content) != 0) { 402 | fprintf(stderr, "Error: Failed to write test file '%s'\n", test_file_path); 403 | free(source_copy); 404 | return 1; 405 | } 406 | 407 | printf("✓ Created test file: %s\n", test_file_path); 408 | 409 | // Update tests/Makefile.am 410 | char test_filename[256]; 411 | snprintf(test_filename, sizeof(test_filename), "test_%s.c", basename_no_ext); 412 | update_test_makefile(test_filename); 413 | 414 | printf("\nNext steps:\n"); 415 | printf(" 1. Edit %s to add your test cases\n", test_file_path); 416 | printf(" 2. Run 'jc test run' to execute tests\n"); 417 | 418 | free(source_copy); 419 | return 0; 420 | } 421 | 422 | // Remove a test file for a source file 423 | static int test_remove(const char *source_file) { 424 | // Extract basename without extension 425 | char *source_copy = strdup(source_file); 426 | char *base = basename(source_copy); 427 | char basename_no_ext[256]; 428 | strncpy(basename_no_ext, base, sizeof(basename_no_ext) - 1); 429 | basename_no_ext[sizeof(basename_no_ext) - 1] = '\0'; 430 | 431 | // Remove .c extension if present 432 | char *dot = strrchr(basename_no_ext, '.'); 433 | if (dot) *dot = '\0'; 434 | 435 | // Create test file path 436 | char test_file_path[PATH_MAX]; 437 | snprintf(test_file_path, sizeof(test_file_path), "tests/test_%s.c", basename_no_ext); 438 | 439 | // Check if test file exists 440 | if (!file_exists(test_file_path)) { 441 | fprintf(stderr, "Error: Test file '%s' does not exist\n", test_file_path); 442 | free(source_copy); 443 | return 1; 444 | } 445 | 446 | // Remove from Makefile.am first 447 | char test_filename[256]; 448 | snprintf(test_filename, sizeof(test_filename), "test_%s.c", basename_no_ext); 449 | remove_from_test_makefile(test_filename); 450 | 451 | // Delete the test file 452 | if (unlink(test_file_path) != 0) { 453 | fprintf(stderr, "Error: Failed to delete test file '%s'\n", test_file_path); 454 | free(source_copy); 455 | return 1; 456 | } 457 | 458 | printf("✓ Removed test file: %s\n", test_file_path); 459 | printf("✓ Updated tests/Makefile.am\n"); 460 | 461 | free(source_copy); 462 | return 0; 463 | } 464 | 465 | // Run tests 466 | static int test_run(const char *test_file) { 467 | // Check if we're in an automake project 468 | if (!is_automake_project()) { 469 | fprintf(stderr, "Error: Not in an automake project directory\n"); 470 | fprintf(stderr, "Run this command from a project created with 'jc new'\n"); 471 | return 1; 472 | } 473 | 474 | // Check if tests directory exists 475 | if (!directory_exists("tests")) { 476 | fprintf(stderr, "Error: No tests directory found\n"); 477 | fprintf(stderr, "Use 'jc test add ' to create tests\n"); 478 | return 1; 479 | } 480 | 481 | if (test_file) { 482 | // Run specific test 483 | char test_path[PATH_MAX]; 484 | 485 | // Handle different input formats 486 | if (strstr(test_file, "test_") == test_file) { 487 | // Input is like "test_utils" 488 | snprintf(test_path, sizeof(test_path), "tests/%s", test_file); 489 | } else if (strstr(test_file, ".c") != NULL) { 490 | // Input is like "test_utils.c" 491 | char *base = basename((char*)test_file); 492 | char *dot = strrchr(base, '.'); 493 | if (dot) *dot = '\0'; 494 | snprintf(test_path, sizeof(test_path), "tests/%s", base); 495 | } else { 496 | // Input is like "utils" - add test_ prefix 497 | snprintf(test_path, sizeof(test_path), "tests/test_%s", test_file); 498 | } 499 | 500 | // Check if test binary exists 501 | if (!file_exists(test_path)) { 502 | fprintf(stderr, "Error: Test '%s' not found\n", test_path); 503 | fprintf(stderr, "Run 'make check' first to build tests\n"); 504 | return 1; 505 | } 506 | 507 | printf("Running test: %s\n\n", test_path); 508 | return execute_command(test_path); 509 | 510 | } else { 511 | // Run all tests using make check 512 | printf("Running all tests...\n\n"); 513 | return execute_command("make check"); 514 | } 515 | } 516 | 517 | // Main command handler 518 | int cmd_test(int argc, char *argv[]) { 519 | if (argc < 2) { 520 | print_test_usage(); 521 | return 1; 522 | } 523 | 524 | const char *subcommand = argv[1]; 525 | 526 | if (strcmp(subcommand, "add") == 0) { 527 | if (argc < 3) { 528 | fprintf(stderr, "Error: 'add' requires a source file argument\n\n"); 529 | print_test_usage(); 530 | return 1; 531 | } 532 | return test_add(argv[2]); 533 | 534 | } else if (strcmp(subcommand, "remove") == 0) { 535 | if (argc < 3) { 536 | fprintf(stderr, "Error: 'remove' requires a source file argument\n\n"); 537 | print_test_usage(); 538 | return 1; 539 | } 540 | return test_remove(argv[2]); 541 | 542 | } else if (strcmp(subcommand, "run") == 0) { 543 | const char *test_file = (argc >= 3) ? argv[2] : NULL; 544 | return test_run(test_file); 545 | 546 | } else { 547 | fprintf(stderr, "Error: Unknown subcommand '%s'\n\n", subcommand); 548 | print_test_usage(); 549 | return 1; 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /config.sub: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Configuration validation subroutine script. 3 | # Copyright 1992-2024 Free Software Foundation, Inc. 4 | 5 | # shellcheck disable=SC2006,SC2268,SC2162 # see below for rationale 6 | 7 | timestamp='2024-05-27' 8 | 9 | # This file is free software; you can redistribute it and/or modify it 10 | # under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, but 15 | # WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | # General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, see . 21 | # 22 | # As a special exception to the GNU General Public License, if you 23 | # distribute this file as part of a program that contains a 24 | # configuration script generated by Autoconf, you may include it under 25 | # the same distribution terms that you use for the rest of that 26 | # program. This Exception is an additional permission under section 7 27 | # of the GNU General Public License, version 3 ("GPLv3"). 28 | 29 | 30 | # Please send patches to . 31 | # 32 | # Configuration subroutine to validate and canonicalize a configuration type. 33 | # Supply the specified configuration type as an argument. 34 | # If it is invalid, we print an error message on stderr and exit with code 1. 35 | # Otherwise, we print the canonical config type on stdout and succeed. 36 | 37 | # You can get the latest version of this script from: 38 | # https://git.savannah.gnu.org/cgit/config.git/plain/config.sub 39 | 40 | # This file is supposed to be the same for all GNU packages 41 | # and recognize all the CPU types, system types and aliases 42 | # that are meaningful with *any* GNU software. 43 | # Each package is responsible for reporting which valid configurations 44 | # it does not support. The user should be able to distinguish 45 | # a failure to support a valid configuration from a meaningless 46 | # configuration. 47 | 48 | # The goal of this file is to map all the various variations of a given 49 | # machine specification into a single specification in the form: 50 | # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM 51 | # or in some cases, the newer four-part form: 52 | # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM 53 | # It is wrong to echo any other type of specification. 54 | 55 | # The "shellcheck disable" line above the timestamp inhibits complaints 56 | # about features and limitations of the classic Bourne shell that were 57 | # superseded or lifted in POSIX. However, this script identifies a wide 58 | # variety of pre-POSIX systems that do not have POSIX shells at all, and 59 | # even some reasonably current systems (Solaris 10 as case-in-point) still 60 | # have a pre-POSIX /bin/sh. 61 | 62 | me=`echo "$0" | sed -e 's,.*/,,'` 63 | 64 | usage="\ 65 | Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS 66 | 67 | Canonicalize a configuration name. 68 | 69 | Options: 70 | -h, --help print this help, then exit 71 | -t, --time-stamp print date of last modification, then exit 72 | -v, --version print version number, then exit 73 | 74 | Report bugs and patches to ." 75 | 76 | version="\ 77 | GNU config.sub ($timestamp) 78 | 79 | Copyright 1992-2024 Free Software Foundation, Inc. 80 | 81 | This is free software; see the source for copying conditions. There is NO 82 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." 83 | 84 | help=" 85 | Try '$me --help' for more information." 86 | 87 | # Parse command line 88 | while test $# -gt 0 ; do 89 | case $1 in 90 | --time-stamp | --time* | -t ) 91 | echo "$timestamp" ; exit ;; 92 | --version | -v ) 93 | echo "$version" ; exit ;; 94 | --help | --h* | -h ) 95 | echo "$usage"; exit ;; 96 | -- ) # Stop option processing 97 | shift; break ;; 98 | - ) # Use stdin as input. 99 | break ;; 100 | -* ) 101 | echo "$me: invalid option $1$help" >&2 102 | exit 1 ;; 103 | 104 | *local*) 105 | # First pass through any local machine types. 106 | echo "$1" 107 | exit ;; 108 | 109 | * ) 110 | break ;; 111 | esac 112 | done 113 | 114 | case $# in 115 | 0) echo "$me: missing argument$help" >&2 116 | exit 1;; 117 | 1) ;; 118 | *) echo "$me: too many arguments$help" >&2 119 | exit 1;; 120 | esac 121 | 122 | # Split fields of configuration type 123 | saved_IFS=$IFS 124 | IFS="-" read field1 field2 field3 field4 <&2 133 | exit 1 134 | ;; 135 | *-*-*-*) 136 | basic_machine=$field1-$field2 137 | basic_os=$field3-$field4 138 | ;; 139 | *-*-*) 140 | # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two 141 | # parts 142 | maybe_os=$field2-$field3 143 | case $maybe_os in 144 | cloudabi*-eabi* \ 145 | | kfreebsd*-gnu* \ 146 | | knetbsd*-gnu* \ 147 | | kopensolaris*-gnu* \ 148 | | linux-* \ 149 | | managarm-* \ 150 | | netbsd*-eabi* \ 151 | | netbsd*-gnu* \ 152 | | nto-qnx* \ 153 | | os2-emx* \ 154 | | rtmk-nova* \ 155 | | storm-chaos* \ 156 | | uclinux-gnu* \ 157 | | uclinux-uclibc* \ 158 | | windows-* ) 159 | basic_machine=$field1 160 | basic_os=$maybe_os 161 | ;; 162 | android-linux) 163 | basic_machine=$field1-unknown 164 | basic_os=linux-android 165 | ;; 166 | *) 167 | basic_machine=$field1-$field2 168 | basic_os=$field3 169 | ;; 170 | esac 171 | ;; 172 | *-*) 173 | case $field1-$field2 in 174 | # Shorthands that happen to contain a single dash 175 | convex-c[12] | convex-c3[248]) 176 | basic_machine=$field2-convex 177 | basic_os= 178 | ;; 179 | decstation-3100) 180 | basic_machine=mips-dec 181 | basic_os= 182 | ;; 183 | *-*) 184 | # Second component is usually, but not always the OS 185 | case $field2 in 186 | # Do not treat sunos as a manufacturer 187 | sun*os*) 188 | basic_machine=$field1 189 | basic_os=$field2 190 | ;; 191 | # Manufacturers 192 | 3100* \ 193 | | 32* \ 194 | | 3300* \ 195 | | 3600* \ 196 | | 7300* \ 197 | | acorn \ 198 | | altos* \ 199 | | apollo \ 200 | | apple \ 201 | | atari \ 202 | | att* \ 203 | | axis \ 204 | | be \ 205 | | bull \ 206 | | cbm \ 207 | | ccur \ 208 | | cisco \ 209 | | commodore \ 210 | | convergent* \ 211 | | convex* \ 212 | | cray \ 213 | | crds \ 214 | | dec* \ 215 | | delta* \ 216 | | dg \ 217 | | digital \ 218 | | dolphin \ 219 | | encore* \ 220 | | gould \ 221 | | harris \ 222 | | highlevel \ 223 | | hitachi* \ 224 | | hp \ 225 | | ibm* \ 226 | | intergraph \ 227 | | isi* \ 228 | | knuth \ 229 | | masscomp \ 230 | | microblaze* \ 231 | | mips* \ 232 | | motorola* \ 233 | | ncr* \ 234 | | news \ 235 | | next \ 236 | | ns \ 237 | | oki \ 238 | | omron* \ 239 | | pc533* \ 240 | | rebel \ 241 | | rom68k \ 242 | | rombug \ 243 | | semi \ 244 | | sequent* \ 245 | | siemens \ 246 | | sgi* \ 247 | | siemens \ 248 | | sim \ 249 | | sni \ 250 | | sony* \ 251 | | stratus \ 252 | | sun \ 253 | | sun[234]* \ 254 | | tektronix \ 255 | | tti* \ 256 | | ultra \ 257 | | unicom* \ 258 | | wec \ 259 | | winbond \ 260 | | wrs) 261 | basic_machine=$field1-$field2 262 | basic_os= 263 | ;; 264 | zephyr*) 265 | basic_machine=$field1-unknown 266 | basic_os=$field2 267 | ;; 268 | *) 269 | basic_machine=$field1 270 | basic_os=$field2 271 | ;; 272 | esac 273 | ;; 274 | esac 275 | ;; 276 | *) 277 | # Convert single-component short-hands not valid as part of 278 | # multi-component configurations. 279 | case $field1 in 280 | 386bsd) 281 | basic_machine=i386-pc 282 | basic_os=bsd 283 | ;; 284 | a29khif) 285 | basic_machine=a29k-amd 286 | basic_os=udi 287 | ;; 288 | adobe68k) 289 | basic_machine=m68010-adobe 290 | basic_os=scout 291 | ;; 292 | alliant) 293 | basic_machine=fx80-alliant 294 | basic_os= 295 | ;; 296 | altos | altos3068) 297 | basic_machine=m68k-altos 298 | basic_os= 299 | ;; 300 | am29k) 301 | basic_machine=a29k-none 302 | basic_os=bsd 303 | ;; 304 | amdahl) 305 | basic_machine=580-amdahl 306 | basic_os=sysv 307 | ;; 308 | amiga) 309 | basic_machine=m68k-unknown 310 | basic_os= 311 | ;; 312 | amigaos | amigados) 313 | basic_machine=m68k-unknown 314 | basic_os=amigaos 315 | ;; 316 | amigaunix | amix) 317 | basic_machine=m68k-unknown 318 | basic_os=sysv4 319 | ;; 320 | apollo68) 321 | basic_machine=m68k-apollo 322 | basic_os=sysv 323 | ;; 324 | apollo68bsd) 325 | basic_machine=m68k-apollo 326 | basic_os=bsd 327 | ;; 328 | aros) 329 | basic_machine=i386-pc 330 | basic_os=aros 331 | ;; 332 | aux) 333 | basic_machine=m68k-apple 334 | basic_os=aux 335 | ;; 336 | balance) 337 | basic_machine=ns32k-sequent 338 | basic_os=dynix 339 | ;; 340 | blackfin) 341 | basic_machine=bfin-unknown 342 | basic_os=linux 343 | ;; 344 | cegcc) 345 | basic_machine=arm-unknown 346 | basic_os=cegcc 347 | ;; 348 | cray) 349 | basic_machine=j90-cray 350 | basic_os=unicos 351 | ;; 352 | crds | unos) 353 | basic_machine=m68k-crds 354 | basic_os= 355 | ;; 356 | da30) 357 | basic_machine=m68k-da30 358 | basic_os= 359 | ;; 360 | decstation | pmax | pmin | dec3100 | decstatn) 361 | basic_machine=mips-dec 362 | basic_os= 363 | ;; 364 | delta88) 365 | basic_machine=m88k-motorola 366 | basic_os=sysv3 367 | ;; 368 | dicos) 369 | basic_machine=i686-pc 370 | basic_os=dicos 371 | ;; 372 | djgpp) 373 | basic_machine=i586-pc 374 | basic_os=msdosdjgpp 375 | ;; 376 | ebmon29k) 377 | basic_machine=a29k-amd 378 | basic_os=ebmon 379 | ;; 380 | es1800 | OSE68k | ose68k | ose | OSE) 381 | basic_machine=m68k-ericsson 382 | basic_os=ose 383 | ;; 384 | gmicro) 385 | basic_machine=tron-gmicro 386 | basic_os=sysv 387 | ;; 388 | go32) 389 | basic_machine=i386-pc 390 | basic_os=go32 391 | ;; 392 | h8300hms) 393 | basic_machine=h8300-hitachi 394 | basic_os=hms 395 | ;; 396 | h8300xray) 397 | basic_machine=h8300-hitachi 398 | basic_os=xray 399 | ;; 400 | h8500hms) 401 | basic_machine=h8500-hitachi 402 | basic_os=hms 403 | ;; 404 | harris) 405 | basic_machine=m88k-harris 406 | basic_os=sysv3 407 | ;; 408 | hp300 | hp300hpux) 409 | basic_machine=m68k-hp 410 | basic_os=hpux 411 | ;; 412 | hp300bsd) 413 | basic_machine=m68k-hp 414 | basic_os=bsd 415 | ;; 416 | hppaosf) 417 | basic_machine=hppa1.1-hp 418 | basic_os=osf 419 | ;; 420 | hppro) 421 | basic_machine=hppa1.1-hp 422 | basic_os=proelf 423 | ;; 424 | i386mach) 425 | basic_machine=i386-mach 426 | basic_os=mach 427 | ;; 428 | isi68 | isi) 429 | basic_machine=m68k-isi 430 | basic_os=sysv 431 | ;; 432 | m68knommu) 433 | basic_machine=m68k-unknown 434 | basic_os=linux 435 | ;; 436 | magnum | m3230) 437 | basic_machine=mips-mips 438 | basic_os=sysv 439 | ;; 440 | merlin) 441 | basic_machine=ns32k-utek 442 | basic_os=sysv 443 | ;; 444 | mingw64) 445 | basic_machine=x86_64-pc 446 | basic_os=mingw64 447 | ;; 448 | mingw32) 449 | basic_machine=i686-pc 450 | basic_os=mingw32 451 | ;; 452 | mingw32ce) 453 | basic_machine=arm-unknown 454 | basic_os=mingw32ce 455 | ;; 456 | monitor) 457 | basic_machine=m68k-rom68k 458 | basic_os=coff 459 | ;; 460 | morphos) 461 | basic_machine=powerpc-unknown 462 | basic_os=morphos 463 | ;; 464 | moxiebox) 465 | basic_machine=moxie-unknown 466 | basic_os=moxiebox 467 | ;; 468 | msdos) 469 | basic_machine=i386-pc 470 | basic_os=msdos 471 | ;; 472 | msys) 473 | basic_machine=i686-pc 474 | basic_os=msys 475 | ;; 476 | mvs) 477 | basic_machine=i370-ibm 478 | basic_os=mvs 479 | ;; 480 | nacl) 481 | basic_machine=le32-unknown 482 | basic_os=nacl 483 | ;; 484 | ncr3000) 485 | basic_machine=i486-ncr 486 | basic_os=sysv4 487 | ;; 488 | netbsd386) 489 | basic_machine=i386-pc 490 | basic_os=netbsd 491 | ;; 492 | netwinder) 493 | basic_machine=armv4l-rebel 494 | basic_os=linux 495 | ;; 496 | news | news700 | news800 | news900) 497 | basic_machine=m68k-sony 498 | basic_os=newsos 499 | ;; 500 | news1000) 501 | basic_machine=m68030-sony 502 | basic_os=newsos 503 | ;; 504 | necv70) 505 | basic_machine=v70-nec 506 | basic_os=sysv 507 | ;; 508 | nh3000) 509 | basic_machine=m68k-harris 510 | basic_os=cxux 511 | ;; 512 | nh[45]000) 513 | basic_machine=m88k-harris 514 | basic_os=cxux 515 | ;; 516 | nindy960) 517 | basic_machine=i960-intel 518 | basic_os=nindy 519 | ;; 520 | mon960) 521 | basic_machine=i960-intel 522 | basic_os=mon960 523 | ;; 524 | nonstopux) 525 | basic_machine=mips-compaq 526 | basic_os=nonstopux 527 | ;; 528 | os400) 529 | basic_machine=powerpc-ibm 530 | basic_os=os400 531 | ;; 532 | OSE68000 | ose68000) 533 | basic_machine=m68000-ericsson 534 | basic_os=ose 535 | ;; 536 | os68k) 537 | basic_machine=m68k-none 538 | basic_os=os68k 539 | ;; 540 | paragon) 541 | basic_machine=i860-intel 542 | basic_os=osf 543 | ;; 544 | parisc) 545 | basic_machine=hppa-unknown 546 | basic_os=linux 547 | ;; 548 | psp) 549 | basic_machine=mipsallegrexel-sony 550 | basic_os=psp 551 | ;; 552 | pw32) 553 | basic_machine=i586-unknown 554 | basic_os=pw32 555 | ;; 556 | rdos | rdos64) 557 | basic_machine=x86_64-pc 558 | basic_os=rdos 559 | ;; 560 | rdos32) 561 | basic_machine=i386-pc 562 | basic_os=rdos 563 | ;; 564 | rom68k) 565 | basic_machine=m68k-rom68k 566 | basic_os=coff 567 | ;; 568 | sa29200) 569 | basic_machine=a29k-amd 570 | basic_os=udi 571 | ;; 572 | sei) 573 | basic_machine=mips-sei 574 | basic_os=seiux 575 | ;; 576 | sequent) 577 | basic_machine=i386-sequent 578 | basic_os= 579 | ;; 580 | sps7) 581 | basic_machine=m68k-bull 582 | basic_os=sysv2 583 | ;; 584 | st2000) 585 | basic_machine=m68k-tandem 586 | basic_os= 587 | ;; 588 | stratus) 589 | basic_machine=i860-stratus 590 | basic_os=sysv4 591 | ;; 592 | sun2) 593 | basic_machine=m68000-sun 594 | basic_os= 595 | ;; 596 | sun2os3) 597 | basic_machine=m68000-sun 598 | basic_os=sunos3 599 | ;; 600 | sun2os4) 601 | basic_machine=m68000-sun 602 | basic_os=sunos4 603 | ;; 604 | sun3) 605 | basic_machine=m68k-sun 606 | basic_os= 607 | ;; 608 | sun3os3) 609 | basic_machine=m68k-sun 610 | basic_os=sunos3 611 | ;; 612 | sun3os4) 613 | basic_machine=m68k-sun 614 | basic_os=sunos4 615 | ;; 616 | sun4) 617 | basic_machine=sparc-sun 618 | basic_os= 619 | ;; 620 | sun4os3) 621 | basic_machine=sparc-sun 622 | basic_os=sunos3 623 | ;; 624 | sun4os4) 625 | basic_machine=sparc-sun 626 | basic_os=sunos4 627 | ;; 628 | sun4sol2) 629 | basic_machine=sparc-sun 630 | basic_os=solaris2 631 | ;; 632 | sun386 | sun386i | roadrunner) 633 | basic_machine=i386-sun 634 | basic_os= 635 | ;; 636 | sv1) 637 | basic_machine=sv1-cray 638 | basic_os=unicos 639 | ;; 640 | symmetry) 641 | basic_machine=i386-sequent 642 | basic_os=dynix 643 | ;; 644 | t3e) 645 | basic_machine=alphaev5-cray 646 | basic_os=unicos 647 | ;; 648 | t90) 649 | basic_machine=t90-cray 650 | basic_os=unicos 651 | ;; 652 | toad1) 653 | basic_machine=pdp10-xkl 654 | basic_os=tops20 655 | ;; 656 | tpf) 657 | basic_machine=s390x-ibm 658 | basic_os=tpf 659 | ;; 660 | udi29k) 661 | basic_machine=a29k-amd 662 | basic_os=udi 663 | ;; 664 | ultra3) 665 | basic_machine=a29k-nyu 666 | basic_os=sym1 667 | ;; 668 | v810 | necv810) 669 | basic_machine=v810-nec 670 | basic_os=none 671 | ;; 672 | vaxv) 673 | basic_machine=vax-dec 674 | basic_os=sysv 675 | ;; 676 | vms) 677 | basic_machine=vax-dec 678 | basic_os=vms 679 | ;; 680 | vsta) 681 | basic_machine=i386-pc 682 | basic_os=vsta 683 | ;; 684 | vxworks960) 685 | basic_machine=i960-wrs 686 | basic_os=vxworks 687 | ;; 688 | vxworks68) 689 | basic_machine=m68k-wrs 690 | basic_os=vxworks 691 | ;; 692 | vxworks29k) 693 | basic_machine=a29k-wrs 694 | basic_os=vxworks 695 | ;; 696 | xbox) 697 | basic_machine=i686-pc 698 | basic_os=mingw32 699 | ;; 700 | ymp) 701 | basic_machine=ymp-cray 702 | basic_os=unicos 703 | ;; 704 | *) 705 | basic_machine=$1 706 | basic_os= 707 | ;; 708 | esac 709 | ;; 710 | esac 711 | 712 | # Decode 1-component or ad-hoc basic machines 713 | case $basic_machine in 714 | # Here we handle the default manufacturer of certain CPU types. It is in 715 | # some cases the only manufacturer, in others, it is the most popular. 716 | w89k) 717 | cpu=hppa1.1 718 | vendor=winbond 719 | ;; 720 | op50n) 721 | cpu=hppa1.1 722 | vendor=oki 723 | ;; 724 | op60c) 725 | cpu=hppa1.1 726 | vendor=oki 727 | ;; 728 | ibm*) 729 | cpu=i370 730 | vendor=ibm 731 | ;; 732 | orion105) 733 | cpu=clipper 734 | vendor=highlevel 735 | ;; 736 | mac | mpw | mac-mpw) 737 | cpu=m68k 738 | vendor=apple 739 | ;; 740 | pmac | pmac-mpw) 741 | cpu=powerpc 742 | vendor=apple 743 | ;; 744 | 745 | # Recognize the various machine names and aliases which stand 746 | # for a CPU type and a company and sometimes even an OS. 747 | 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) 748 | cpu=m68000 749 | vendor=att 750 | ;; 751 | 3b*) 752 | cpu=we32k 753 | vendor=att 754 | ;; 755 | bluegene*) 756 | cpu=powerpc 757 | vendor=ibm 758 | basic_os=cnk 759 | ;; 760 | decsystem10* | dec10*) 761 | cpu=pdp10 762 | vendor=dec 763 | basic_os=tops10 764 | ;; 765 | decsystem20* | dec20*) 766 | cpu=pdp10 767 | vendor=dec 768 | basic_os=tops20 769 | ;; 770 | delta | 3300 | delta-motorola | 3300-motorola | motorola-delta | motorola-3300) 771 | cpu=m68k 772 | vendor=motorola 773 | ;; 774 | # This used to be dpx2*, but that gets the RS6000-based 775 | # DPX/20 and the x86-based DPX/2-100 wrong. See 776 | # https://oldskool.silicium.org/stations/bull_dpx20.htm 777 | # https://www.feb-patrimoine.com/english/bull_dpx2.htm 778 | # https://www.feb-patrimoine.com/english/unix_and_bull.htm 779 | dpx2 | dpx2[23]00 | dpx2[23]xx) 780 | cpu=m68k 781 | vendor=bull 782 | ;; 783 | dpx2100 | dpx21xx) 784 | cpu=i386 785 | vendor=bull 786 | ;; 787 | dpx20) 788 | cpu=rs6000 789 | vendor=bull 790 | ;; 791 | encore | umax | mmax) 792 | cpu=ns32k 793 | vendor=encore 794 | ;; 795 | elxsi) 796 | cpu=elxsi 797 | vendor=elxsi 798 | basic_os=${basic_os:-bsd} 799 | ;; 800 | fx2800) 801 | cpu=i860 802 | vendor=alliant 803 | ;; 804 | genix) 805 | cpu=ns32k 806 | vendor=ns 807 | ;; 808 | h3050r* | hiux*) 809 | cpu=hppa1.1 810 | vendor=hitachi 811 | basic_os=hiuxwe2 812 | ;; 813 | hp3k9[0-9][0-9] | hp9[0-9][0-9]) 814 | cpu=hppa1.0 815 | vendor=hp 816 | ;; 817 | hp9k2[0-9][0-9] | hp9k31[0-9]) 818 | cpu=m68000 819 | vendor=hp 820 | ;; 821 | hp9k3[2-9][0-9]) 822 | cpu=m68k 823 | vendor=hp 824 | ;; 825 | hp9k6[0-9][0-9] | hp6[0-9][0-9]) 826 | cpu=hppa1.0 827 | vendor=hp 828 | ;; 829 | hp9k7[0-79][0-9] | hp7[0-79][0-9]) 830 | cpu=hppa1.1 831 | vendor=hp 832 | ;; 833 | hp9k78[0-9] | hp78[0-9]) 834 | # FIXME: really hppa2.0-hp 835 | cpu=hppa1.1 836 | vendor=hp 837 | ;; 838 | hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) 839 | # FIXME: really hppa2.0-hp 840 | cpu=hppa1.1 841 | vendor=hp 842 | ;; 843 | hp9k8[0-9][13679] | hp8[0-9][13679]) 844 | cpu=hppa1.1 845 | vendor=hp 846 | ;; 847 | hp9k8[0-9][0-9] | hp8[0-9][0-9]) 848 | cpu=hppa1.0 849 | vendor=hp 850 | ;; 851 | i*86v32) 852 | cpu=`echo "$1" | sed -e 's/86.*/86/'` 853 | vendor=pc 854 | basic_os=sysv32 855 | ;; 856 | i*86v4*) 857 | cpu=`echo "$1" | sed -e 's/86.*/86/'` 858 | vendor=pc 859 | basic_os=sysv4 860 | ;; 861 | i*86v) 862 | cpu=`echo "$1" | sed -e 's/86.*/86/'` 863 | vendor=pc 864 | basic_os=sysv 865 | ;; 866 | i*86sol2) 867 | cpu=`echo "$1" | sed -e 's/86.*/86/'` 868 | vendor=pc 869 | basic_os=solaris2 870 | ;; 871 | j90 | j90-cray) 872 | cpu=j90 873 | vendor=cray 874 | basic_os=${basic_os:-unicos} 875 | ;; 876 | iris | iris4d) 877 | cpu=mips 878 | vendor=sgi 879 | case $basic_os in 880 | irix*) 881 | ;; 882 | *) 883 | basic_os=irix4 884 | ;; 885 | esac 886 | ;; 887 | miniframe) 888 | cpu=m68000 889 | vendor=convergent 890 | ;; 891 | *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) 892 | cpu=m68k 893 | vendor=atari 894 | basic_os=mint 895 | ;; 896 | news-3600 | risc-news) 897 | cpu=mips 898 | vendor=sony 899 | basic_os=newsos 900 | ;; 901 | next | m*-next) 902 | cpu=m68k 903 | vendor=next 904 | ;; 905 | np1) 906 | cpu=np1 907 | vendor=gould 908 | ;; 909 | op50n-* | op60c-*) 910 | cpu=hppa1.1 911 | vendor=oki 912 | basic_os=proelf 913 | ;; 914 | pa-hitachi) 915 | cpu=hppa1.1 916 | vendor=hitachi 917 | basic_os=hiuxwe2 918 | ;; 919 | pbd) 920 | cpu=sparc 921 | vendor=tti 922 | ;; 923 | pbb) 924 | cpu=m68k 925 | vendor=tti 926 | ;; 927 | pc532) 928 | cpu=ns32k 929 | vendor=pc532 930 | ;; 931 | pn) 932 | cpu=pn 933 | vendor=gould 934 | ;; 935 | power) 936 | cpu=power 937 | vendor=ibm 938 | ;; 939 | ps2) 940 | cpu=i386 941 | vendor=ibm 942 | ;; 943 | rm[46]00) 944 | cpu=mips 945 | vendor=siemens 946 | ;; 947 | rtpc | rtpc-*) 948 | cpu=romp 949 | vendor=ibm 950 | ;; 951 | sde) 952 | cpu=mipsisa32 953 | vendor=sde 954 | basic_os=${basic_os:-elf} 955 | ;; 956 | simso-wrs) 957 | cpu=sparclite 958 | vendor=wrs 959 | basic_os=vxworks 960 | ;; 961 | tower | tower-32) 962 | cpu=m68k 963 | vendor=ncr 964 | ;; 965 | vpp*|vx|vx-*) 966 | cpu=f301 967 | vendor=fujitsu 968 | ;; 969 | w65) 970 | cpu=w65 971 | vendor=wdc 972 | ;; 973 | w89k-*) 974 | cpu=hppa1.1 975 | vendor=winbond 976 | basic_os=proelf 977 | ;; 978 | none) 979 | cpu=none 980 | vendor=none 981 | ;; 982 | leon|leon[3-9]) 983 | cpu=sparc 984 | vendor=$basic_machine 985 | ;; 986 | leon-*|leon[3-9]-*) 987 | cpu=sparc 988 | vendor=`echo "$basic_machine" | sed 's/-.*//'` 989 | ;; 990 | 991 | *-*) 992 | saved_IFS=$IFS 993 | IFS="-" read cpu vendor <&2 1474 | exit 1 1475 | ;; 1476 | esac 1477 | ;; 1478 | esac 1479 | 1480 | # Here we canonicalize certain aliases for manufacturers. 1481 | case $vendor in 1482 | digital*) 1483 | vendor=dec 1484 | ;; 1485 | commodore*) 1486 | vendor=cbm 1487 | ;; 1488 | *) 1489 | ;; 1490 | esac 1491 | 1492 | # Decode manufacturer-specific aliases for certain operating systems. 1493 | 1494 | if test x"$basic_os" != x 1495 | then 1496 | 1497 | # First recognize some ad-hoc cases, or perhaps split kernel-os, or else just 1498 | # set os. 1499 | obj= 1500 | case $basic_os in 1501 | gnu/linux*) 1502 | kernel=linux 1503 | os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'` 1504 | ;; 1505 | os2-emx) 1506 | kernel=os2 1507 | os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'` 1508 | ;; 1509 | nto-qnx*) 1510 | kernel=nto 1511 | os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` 1512 | ;; 1513 | *-*) 1514 | saved_IFS=$IFS 1515 | IFS="-" read kernel os <&2 2174 | fi 2175 | ;; 2176 | *) 2177 | echo "Invalid configuration '$1': OS '$os' not recognized" 1>&2 2178 | exit 1 2179 | ;; 2180 | esac 2181 | 2182 | case $obj in 2183 | aout* | coff* | elf* | pe*) 2184 | ;; 2185 | '') 2186 | # empty is fine 2187 | ;; 2188 | *) 2189 | echo "Invalid configuration '$1': Machine code format '$obj' not recognized" 1>&2 2190 | exit 1 2191 | ;; 2192 | esac 2193 | 2194 | # Here we handle the constraint that a (synthetic) cpu and os are 2195 | # valid only in combination with each other and nowhere else. 2196 | case $cpu-$os in 2197 | # The "javascript-unknown-ghcjs" triple is used by GHC; we 2198 | # accept it here in order to tolerate that, but reject any 2199 | # variations. 2200 | javascript-ghcjs) 2201 | ;; 2202 | javascript-* | *-ghcjs) 2203 | echo "Invalid configuration '$1': cpu '$cpu' is not valid with os '$os$obj'" 1>&2 2204 | exit 1 2205 | ;; 2206 | esac 2207 | 2208 | # As a final step for OS-related things, validate the OS-kernel combination 2209 | # (given a valid OS), if there is a kernel. 2210 | case $kernel-$os-$obj in 2211 | linux-gnu*- | linux-android*- | linux-dietlibc*- | linux-llvm*- \ 2212 | | linux-mlibc*- | linux-musl*- | linux-newlib*- \ 2213 | | linux-relibc*- | linux-uclibc*- | linux-ohos*- ) 2214 | ;; 2215 | uclinux-uclibc*- | uclinux-gnu*- ) 2216 | ;; 2217 | managarm-mlibc*- | managarm-kernel*- ) 2218 | ;; 2219 | windows*-msvc*-) 2220 | ;; 2221 | -dietlibc*- | -llvm*- | -mlibc*- | -musl*- | -newlib*- | -relibc*- \ 2222 | | -uclibc*- ) 2223 | # These are just libc implementations, not actual OSes, and thus 2224 | # require a kernel. 2225 | echo "Invalid configuration '$1': libc '$os' needs explicit kernel." 1>&2 2226 | exit 1 2227 | ;; 2228 | -kernel*- ) 2229 | echo "Invalid configuration '$1': '$os' needs explicit kernel." 1>&2 2230 | exit 1 2231 | ;; 2232 | *-kernel*- ) 2233 | echo "Invalid configuration '$1': '$kernel' does not support '$os'." 1>&2 2234 | exit 1 2235 | ;; 2236 | *-msvc*- ) 2237 | echo "Invalid configuration '$1': '$os' needs 'windows'." 1>&2 2238 | exit 1 2239 | ;; 2240 | kfreebsd*-gnu*- | knetbsd*-gnu*- | netbsd*-gnu*- | kopensolaris*-gnu*-) 2241 | ;; 2242 | vxworks-simlinux- | vxworks-simwindows- | vxworks-spe-) 2243 | ;; 2244 | nto-qnx*-) 2245 | ;; 2246 | os2-emx-) 2247 | ;; 2248 | rtmk-nova-) 2249 | ;; 2250 | *-eabi*- | *-gnueabi*-) 2251 | ;; 2252 | none--*) 2253 | # None (no kernel, i.e. freestanding / bare metal), 2254 | # can be paired with an machine code file format 2255 | ;; 2256 | -*-) 2257 | # Blank kernel with real OS is always fine. 2258 | ;; 2259 | --*) 2260 | # Blank kernel and OS with real machine code file format is always fine. 2261 | ;; 2262 | *-*-*) 2263 | echo "Invalid configuration '$1': Kernel '$kernel' not known to work with OS '$os'." 1>&2 2264 | exit 1 2265 | ;; 2266 | esac 2267 | 2268 | # Here we handle the case where we know the os, and the CPU type, but not the 2269 | # manufacturer. We pick the logical manufacturer. 2270 | case $vendor in 2271 | unknown) 2272 | case $cpu-$os in 2273 | *-riscix*) 2274 | vendor=acorn 2275 | ;; 2276 | *-sunos* | *-solaris*) 2277 | vendor=sun 2278 | ;; 2279 | *-cnk* | *-aix*) 2280 | vendor=ibm 2281 | ;; 2282 | *-beos*) 2283 | vendor=be 2284 | ;; 2285 | *-hpux*) 2286 | vendor=hp 2287 | ;; 2288 | *-mpeix*) 2289 | vendor=hp 2290 | ;; 2291 | *-hiux*) 2292 | vendor=hitachi 2293 | ;; 2294 | *-unos*) 2295 | vendor=crds 2296 | ;; 2297 | *-dgux*) 2298 | vendor=dg 2299 | ;; 2300 | *-luna*) 2301 | vendor=omron 2302 | ;; 2303 | *-genix*) 2304 | vendor=ns 2305 | ;; 2306 | *-clix*) 2307 | vendor=intergraph 2308 | ;; 2309 | *-mvs* | *-opened*) 2310 | vendor=ibm 2311 | ;; 2312 | *-os400*) 2313 | vendor=ibm 2314 | ;; 2315 | s390-* | s390x-*) 2316 | vendor=ibm 2317 | ;; 2318 | *-ptx*) 2319 | vendor=sequent 2320 | ;; 2321 | *-tpf*) 2322 | vendor=ibm 2323 | ;; 2324 | *-vxsim* | *-vxworks* | *-windiss*) 2325 | vendor=wrs 2326 | ;; 2327 | *-aux*) 2328 | vendor=apple 2329 | ;; 2330 | *-hms*) 2331 | vendor=hitachi 2332 | ;; 2333 | *-mpw* | *-macos*) 2334 | vendor=apple 2335 | ;; 2336 | *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) 2337 | vendor=atari 2338 | ;; 2339 | *-vos*) 2340 | vendor=stratus 2341 | ;; 2342 | esac 2343 | ;; 2344 | esac 2345 | 2346 | echo "$cpu-$vendor${kernel:+-$kernel}${os:+-$os}${obj:+-$obj}" 2347 | exit 2348 | 2349 | # Local variables: 2350 | # eval: (add-hook 'before-save-hook 'time-stamp) 2351 | # time-stamp-start: "timestamp='" 2352 | # time-stamp-format: "%:y-%02m-%02d" 2353 | # time-stamp-end: "'" 2354 | # End: 2355 | --------------------------------------------------------------------------------