summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2021-12-31 23:20:41 +0100
committerMattias Andrée <maandree@kth.se>2021-12-31 23:20:41 +0100
commit8f2a555f900228b531edba5e44e2e0cd7e8fe61e (patch)
tree51dd5bada07ce28f9022a6410fadcb74a3362310
downloadmakel-8f2a555f900228b531edba5e44e2e0cd7e8fe61e.tar.gz
makel-8f2a555f900228b531edba5e44e2e0cd7e8fe61e.tar.bz2
makel-8f2a555f900228b531edba5e44e2e0cd7e8fe61e.tar.xz
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--.gitignore15
-rw-r--r--LICENSE15
-rw-r--r--Makefile39
-rw-r--r--common.h57
-rw-r--r--config.mk8
-rw-r--r--makelint.c197
-rw-r--r--ui.c47
7 files changed, 378 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..40c3059
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/makelint
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2021 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e67e73d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OBJ =\
+ makelint.o\
+ ui.o
+
+HDR =\
+ common.h
+
+all: makelint
+$(OBJ): $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+makelint: $(OBJ)
+ $(CC) -o $@ $(OBJ) $(LDFLAGS)
+
+install: makelint
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/"
+ cp -- makelint "$(DESTDIR)$(PREFIX)/bin/"
+ cp -- makelint.1 "$(DESTDIR)$(MANPREFIX)/man1/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/makelint"
+ -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/makelint.1"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda
+ -rm -f -- makelint
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+.PHONY: all install uninstall clean
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..b2d0a4e
--- /dev/null
+++ b/common.h
@@ -0,0 +1,57 @@
+/* See LICENSE file for copyright and license details. */
+#include <libsimple.h>
+#include <libsimple-arg.h>
+
+#define EXIT_STYLE 1
+#define EXIT_WARNING 2
+#define EXIT_UNSPECIFIED 3
+#define EXIT_NONCONFIRMING 4
+#define EXIT_UNDEFINED 5
+#define EXIT_CRITICAL 6
+#define EXIT_ERROR 7
+
+
+#define LIST_WARNING_CLASSES(X)\
+ X(WC_MAKEFILE, "makefile", INFORM)\
+ X(WC_EXTRA_MAKEFILE, "extra-makefile", WARN)\
+ X(WC_CMDLINE, "cmdline", WARN)\
+ X(WC_TEXT, "text", WARN)
+
+
+enum action {
+ IGNORE,
+ INFORM,
+ WARN
+};
+
+enum warning_class {
+#define X(ENUM, NAME, ACTION) ENUM,
+ LIST_WARNING_CLASSES(X)
+#undef X
+ NUM_WARNING_CLASS
+};
+
+struct warning_class_data {
+ const char *name;
+ enum action action;
+};
+
+struct line {
+ char *data;
+ size_t len;
+ const char *path;
+ size_t lineno;
+};
+
+
+extern int exit_status;
+
+
+/* ui.c */
+extern struct warning_class_data warning_classes[];
+void xprintwarningf(enum warning_class class, int severity, const char *fmt, ...);
+#define printinfof(CLASS, ...) xprintwarningf(CLASS, EXIT_STYLE, __VA_ARGS__)
+#define warnf_warning(CLASS, ...) xprintwarningf(CLASS, EXIT_WARNING, __VA_ARGS__)
+#define warnf_unspecified(CLASS, ...) xprintwarningf(CLASS, EXIT_UNSPECIFIED, __VA_ARGS__)
+#define warnf_undefined(CLASS, ...) xprintwarningf(CLASS, EXIT_UNDEFINED, __VA_ARGS__)
+void printerrorf(const char *fmt, ...);
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..d354a28
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -g
+LDFLAGS = -lsimple
diff --git a/makelint.c b/makelint.c
new file mode 100644
index 0000000..45f599a
--- /dev/null
+++ b/makelint.c
@@ -0,0 +1,197 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+NUSAGE(EXIT_ERROR, "[-f makefile]");
+
+
+
+int exit_status = 0;
+
+static const char *default_makefiles[] = {
+ "makefile",
+ "Makefile"
+};
+
+
+static int
+open_default_makefile(const char **pathp)
+{
+ int fd;
+ size_t i;
+
+ /* The specification says that the alternatives “shall be
+ * tried”, but it uses the phrase “if neither … are found”,
+ * implying that make(1) shall fail if a file cannot be
+ * opened and only try the next alternative if it failed
+ * becomes the file does not exist.
+ */
+
+ for (i = 0; i < ELEMSOF(default_makefiles); i++) {
+ *pathp = default_makefiles[i];
+ fd = open(*pathp, O_RDONLY);
+ if (fd >= 0) {
+ printinfof(WC_MAKEFILE, "found standard makefile to use: %s", *pathp);
+ goto find_existing_fallbacks;
+ } else if (errno != ENOENT) {
+ eprintf("found standard makefile to use, but failed to open: %s:", *pathp);
+ }
+ }
+
+ printerrorf("couldn't find any makefile to use, portable "
+ "alternatives are ./makefile and ./Makefile");
+
+find_existing_fallbacks:
+ for (; i < ELEMSOF(default_makefiles); i++)
+ if (!access(default_makefiles[i], F_OK))
+ warnf_warning(WC_EXTRA_MAKEFILE, "found additional standard makefile: %s", *pathp);
+
+ return fd;
+}
+
+
+static void
+cmdline_opt_f(const char *arg, const char **makefile_pathp)
+{
+ static int warning_emitted = 0;
+
+ if (*makefile_pathp && !warning_emitted) {
+ warning_emitted = 1;
+ warnf_unspecified(WC_CMDLINE, "the -f option has been specified multiple times, "
+ "they are processed in order, but the behaviour is "
+ "otherwise unspecified");
+ printinfof(WC_CMDLINE, "this implementation will use the last "
+ "option and discard earlier options");
+ }
+
+ *makefile_pathp = arg;
+}
+
+
+static struct line *
+load_file(int fd, const char *fname, size_t *nlinesp)
+{
+ struct line *lines;
+ char *buf = NULL, *p;
+ size_t size = 0;
+ size_t len = 0;
+ size_t i;
+ ssize_t r;
+
+ /* getline(3) may seem like the best way to read line by line,
+ * however, it may terminate before the end of the line is
+ * reached, which we would have to deal with, additionally,
+ * we want to check for null bytes. Therefore we will keep
+ * this simple and use read(3) and scan manually; and as a
+ * bonus we can leave the file descriptor open, and let the
+ * caller than created it close it.
+ */
+
+ i = 0;
+ *nlinesp = 0;
+ for (;;) {
+ if (len == size)
+ buf = erealloc(buf, size += 2048);
+ r = read(fd, &buf[len], size - len);
+ if (r > 0)
+ len += (size_t)r;
+ else if (!r)
+ break;
+ else if (errno == EINTR)
+ continue;
+ else
+ eprintf("read %s:", fname);
+
+ for (; i < len; i++) {
+ if (buf[i] == '\n') {
+ *nlinesp += 1;
+ buf[i] = '\0';
+ } else if (buf[i] == '\0') {
+ /* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
+ warnf_undefined(WC_TEXT, "%s:%zu: file contains a NUL byte, this is disallowed, because "
+ "input files are text files, and causes undefined behaviour",
+ fname, *nlinesp + 1);
+ /* make(1) should probably just abort */
+ printinfof(WC_TEXT, "this implementation will replace it with a <space>");
+ buf[i] = ' ';
+ }
+ }
+ }
+
+ if (len && buf[len - 1] != '\0') { /* LF has been converted to NUL above */
+ /* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
+ warnf_undefined(WC_TEXT, "%s:%zu: is non-empty but does not end with a <newline>, which is "
+ "required because input files are text files, and omission of it "
+ "causes undefined behaviour",
+ fname, *nlinesp + 1);
+ /* make(1) should probably just abort */
+ printinfof(WC_TEXT, "this implementation will add the missing <newline>");
+ buf = erealloc(buf, len + 1);
+ buf[len++] = '\0';
+ *nlinesp += 1;
+ }
+
+ lines = *nlinesp ? ecalloc(*nlinesp, sizeof(*lines)) : NULL;
+ for (p = buf, i = 0; i < *nlinesp; i++) {
+ lines[i].lineno = i + 1;
+ lines[i].path = fname;
+ lines[i].len = strlen(p);
+ lines[i].data = ememdup(p, lines[i].len + 1);
+ if (lines[i].len + 1 > 2048) {
+ /* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
+ warnf_undefined(WC_TEXT, "%s:%zu: line is, including the <newline> character, longer than "
+ "2048 bytes which causes undefined behaviour as input files are "
+ "text files and POSIX only guarantees support for lines up to 2048 "
+ "including the <newline> character in text files",
+ fname, *nlinesp + 1);
+ }
+ p += lines[i].len + 1;
+ }
+
+ free(buf);
+ return lines;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ const char *path = NULL;
+ int fd;
+ struct line *lines;
+ size_t nlines;
+
+ libsimple_default_failure_exit = EXIT_ERROR;
+
+ /* make(1) shall support mixing of options and operands (uptil --) */
+ ARGBEGIN {
+ case 'f':
+ cmdline_opt_f(ARG(), &path);
+ break;
+
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc)
+ usage();
+
+ if (!path) {
+ fd = open_default_makefile(&path);
+ } else if (!strcmp(path, "-")) {
+ /* “A pathname of '-' shall denote the standard input” */
+ fd = dup(STDIN_FILENO);
+ if (fd < 0)
+ eprintf("dup <stdin>:");
+ path = "<stdin>";
+ } else {
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ eprintf("open %s O_RDONLY:", path);
+ }
+
+ lines = load_file(fd, path, &nlines);
+ close(fd);
+
+ free(lines);
+ return exit_status;
+}
diff --git a/ui.c b/ui.c
new file mode 100644
index 0000000..e23916e
--- /dev/null
+++ b/ui.c
@@ -0,0 +1,47 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+struct warning_class_data warning_classes[] = {
+#define X(ENUM, NAME, ACTION) {NAME, ACTION},
+ LIST_WARNING_CLASSES(X)
+#undef X
+ {NULL, 0}
+};
+
+
+static void
+vxprintwarningf(enum warning_class class, int severity, const char *fmt, va_list ap)
+{
+ if (warning_classes[class].action != IGNORE) {
+ fprintf(stderr, "%s: [%s] ", argv0,
+ warning_classes[class].action == INFORM ? "info" : "warn");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, " (-w%s)\n", warning_classes[class].name);
+ if (warning_classes[class].action != INFORM)
+ exit_status = MAX(exit_status, severity);
+ }
+}
+
+
+void
+xprintwarningf(enum warning_class class, int severity, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vxprintwarningf(class, severity, fmt, ap);
+ va_end(ap);
+}
+
+
+void
+printerrorf(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s: [error] ", argv0);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ exit(EXIT_CRITICAL);
+}