diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/Makefile b/P04_Modularisieren_von_C_Code/show-dependencies/Makefile new file mode 100644 index 0000000..df07b77 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/Makefile @@ -0,0 +1,65 @@ +SNP_SHARED_MAKEFILE := $(if $(SNP_SHARED_MAKEFILE),$(SNP_SHARED_MAKEFILE),"~/snp/shared.mk") + +TARGET := bin/dep2dot +# Add all additional c-files to the SOURCES variable +# BEGIN-STUDENTS-TO-ADD-CODE +SOURCES := src/main.c +# END-STUDENTS-TO-ADD-CODE +TSTSOURCES := tests/tests.c + +include $(SNP_SHARED_MAKEFILE) + + +# DEPFILES := ... define a list of png file names: %.c -> %.c.png +# BEGIN-STUDENTS-TO-ADD-CODE + + +# END-STUDENTS-TO-ADD-CODE + + + +# define dep target as .PHONEY +# BEGIN-STUDENTS-TO-ADD-CODE + + +# BEGIN-STUDENTS-TO-ADD-CODE + + + +# define dep target depending on FULLTARGET and DEPFILES above +# action: echo some text telling that the target is done using $@ - the echo command shall not be echoed before execution +# BEGIN-STUDENTS-TO-ADD-CODE + + +# BEGIN-STUDENTS-TO-ADD-CODE + + + +# define new suffix rule for %.png depending on %.dot +# action: dot -Tpng $< >$@ || $(RM) $@ +# BEGIN-STUDENTS-TO-ADD-CODE + + +# BEGIN-STUDENTS-TO-ADD-CODE + + + +# define new suffix rule for %.dot depending on %.dep +# action: call $(TARGET) $(@:.dot=) <$< >$@ || $(RM) $@ +# BEGIN-STUDENTS-TO-ADD-CODE + + +# BEGIN-STUDENTS-TO-ADD-CODE + + + +# converts any .c file into a .c.dep file by means of GCC -H switch +# note: it removes intermediate files which were created as side effect +%.c.dep: %.c + $(COMPILE.c) -H -o $@.x $< 2>$@ && $(RM) $@.x $@.d + + +# cleanup all results, including the ones od creating the dependencies +dep-clean: clean + $(RM) $(DEPFILES) $(wildcard src/*.dep src/*.dot) + diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/mainpage.dox b/P04_Modularisieren_von_C_Code/show-dependencies/mainpage.dox new file mode 100644 index 0000000..cd6b69a --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/mainpage.dox @@ -0,0 +1,8 @@ +/** + * @mainpage SNP - P04 Modularisation + * + * @section Purpose + * + * This is a lab for splitting functionality into multiple modules. + * + */ diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/src/data.c b/P04_Modularisieren_von_C_Code/show-dependencies/src/data.c new file mode 100644 index 0000000..a9f28f8 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/src/data.c @@ -0,0 +1,149 @@ +/** + * @file + * @brief Implementation of the dependency file access. + */ +#include "data.h" +#include "error.h" +#include +#include +#include +#include +#include + +#define MAX_PATH_LEN 512 ///< @brief Arbitrarily chosen maximum accepted path lenght. +#define MAX_LINE_LEN 512 ///< @brief Arbitrarily chosen maximum accepted line length +#define MAX_DIRS 64 ///< @brief Arbitrarily chosen maximum number of supported individual directories per dependency file. +#define MAX_FILES 256 ///< @brief Arbitrarily chosen maximum number of supported individual denendency entries. + +/** + * @brief Declaration of POSIX (but not C99) function. + * @param s [IN] The string to duplicate on the heap memory. + * @return The duplicated string. + * @remark Since the Makefile calls gcc with -std=c99, non-C99 POSIX and GNU extensions are excluded - the glibc, though, provides the function to the linker. + */ +char *strdup(const char *s); // not stdc99, but available in the glibc + +/** + * @brief Initialized the data structure before the data is to be read from th edependency file. + * @param data [INOUT] The address of the instance to initialize. + */ +static void init(data_t *data) +{ + assert(data); + memset(data, 0, sizeof(data_t)); + data->dirs = malloc(MAX_DIRS * sizeof(dir_t)); + if (!data->dirs) FATAL("no memory left"); + data->files = malloc(MAX_FILES * sizeof(file_t)); + if (!data->files) FATAL("no memory left"); +} + +/** + * @brief Updates the directory list with the given data. + * @param data [INOUT] The instance to update. + * @param path [IN] The file path of a dependency entry as given by the dependency file. + * @return The index of the directory entry (either an existing matching one or a newly added one). + * @remark Extracts the directory part by means of dirname() from the given path and looks up an existing entry or adds a new one. + */ +static size_t get_or_add_dir(data_t *data, const char *path) +{ + assert(data); + assert(path); + // The function dirname() gives no guarantee to not modify the parameter, therefore, need to produce a copy before calling dirname(). + // Likewise, the returned value may refer to the passed paremater, therefore, a copy is made from the return value. + char *dup = strdup(path); + if (!dup) FATAL("no memory left"); + char *name = strdup(dirname(dup)); + if (!name) FATAL("no memory left"); + free(dup); + + // search for a matching entry... + size_t i = 0; + while(i < data->n_dirs && strcmp(data->dirs[i].name, name) != 0) { + i++; + } + if (i >= MAX_DIRS) FATAL("too many directories"); + + if (i == data->n_dirs) { // no match found: add + // handover the allocated name to the owning new directory entry + dir_t dir = { .name = name }; + // append the new directory entry + data->dirs[data->n_dirs] = dir; + data->n_dirs++; + } else { + // release the name since match found, and therefore, no need to keep the allocated name anymore + free(name); + } + return i; +} + +/** + * @brief Add a file entry from the dependency file to the data structure. + * @param data [INOUT] The data container instance. + * @param path [IN] The path of one file entry from the dependency file. + * @param level [IN] The dependency level of the file entry from the dependency file. + * @remark The sequence of entries in the dependency file is relevant - it implies direct dependencies. + */ +static void add_file(data_t *data, const char *path, size_t level) +{ + assert(data); + assert(path); + // The function basename() gives no guarantee to not modify the parameter, therefore, need to produce a copy before calling basename(). + // Likewise, the returned value may refer to the passed paremater, therefore, a copy is made from the return value. + char *dup = strdup(path); + if (!dup) FATAL("no memory left"); + char *name = strdup(basename(dup)); + if (!name) FATAL("no memory left"); + free(dup); + + if (data->n_files >= MAX_FILES) FATAL("too many files"); + // produce a file entry + file_t file = { .name = name, .dir = get_or_add_dir(data, path), .level = level }; + data->files[data->n_files] = file; + data->n_files++; +} + +/** + * @brief Processes one dependency line of the dependency file. + * @param data [INOUT] The data container instance. + * @param line [IN] The line to parse and store in data. + */ +static void process_line(data_t *data, const char line[]) +{ + assert(data); + + size_t len = strlen(line); + assert(len > 0); + assert(line[0] == '.'); + + // read level + size_t i = strspn(line, "."); + size_t level = i; + // skip spaces + i += strspn(line+i, " \t"); + // take rest as path and add the file to the records + add_file(data, line+i, level); +} + +/* + * The public interface. + */ +const data_t data_read_all(const char *root) +{ + data_t data; + init(&data); + // add as first file the root for the given dependencies + add_file(&data, root, 0); + + char line[MAX_LINE_LEN] = { 0 }; + + // read all stdin line and only process dependency lines (those starting on a '.') + clearerr(stdin); + while(fgets(line, MAX_LINE_LEN, stdin)) { + size_t len = strlen(line); + if (len > 0 && line[len-1] == '\n' && line[0] == '.') { // only dependency lines + line[len-1] = '\0'; + process_line(&data, line); + } + } + return data; +} diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/src/data.h b/P04_Modularisieren_von_C_Code/show-dependencies/src/data.h new file mode 100644 index 0000000..7a20b76 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/src/data.h @@ -0,0 +1,66 @@ +/** + * @file + * @brief Access to the GCC produced dependency data (via gcc -H command line option). + */ + +// begin of include guard +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE + + +// includes which are needed in this header file +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE + + + +/** + * @brief Directory container for file entries of the dependency file. + */ +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE + + +/** + * @brief File container for the file entries of the dependency file. + */ +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE + + + +/** + * @brief Overall container for all directories and all files from the dependency file. + */ +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE + + + +/** + * @brief Entry function to read the deendency data from stdin. + * @param root [IN] The name of the root file (the deoendency file does not mention the root file, so, it has to be passed from outside). + * @return The container of the read data from stdin. See the documentation on gcc -H for details on the dependencies, etc. + */ +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE + + + +// end of include guard +// BEGIN-STUDENTS-TO-ADD-CODE + + +// END-STUDENTS-TO-ADD-CODE diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/src/error.h b/P04_Modularisieren_von_C_Code/show-dependencies/src/error.h new file mode 100644 index 0000000..3a38ab6 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/src/error.h @@ -0,0 +1,17 @@ +/** + * @file + * @brief Error handling convenience functions. + */ +#ifndef _ERROR_H_ +#define _ERROR_H_ + +#include +#include + +/** + * @brief Prints the message to stderr and terminates with EXIT_FAILURE. + * @param MSG [IN] The "..." *string literal* to emit as error - no format parameters nor variables supported. + */ +#define FATAL(MSG) do { fprintf(stderr, "ERROR: %s\n", MSG); exit(EXIT_FAILURE); } while(0) + +#endif // _ERROR_H_ diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/src/main.c b/P04_Modularisieren_von_C_Code/show-dependencies/src/main.c new file mode 100644 index 0000000..8cf35f9 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/src/main.c @@ -0,0 +1,36 @@ + /* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Lab P04 dep2dot + */ +#include +#include + +#include "error.h" +#include "data.h" +#include "output.h" + +/** + * @brief main function + * @param argc [in] number of entries in argv + * @param argv [in] program name plus command line arguments + * @returns returns success if valid date is given, failure otherwise + * @remark Prerequisit to convert the resulting DOT file on the shell: sodo apt install graphviz + * @remark Convert: gcc -H ... file.c ... 2>file.dep ; dep2dot file.c file.dot && dot -Tpng file.dot >file.png + */ +int main(int argc, const char *argv[]) +{ + if (argc < 2) FATAL("missing arguments\nusage: dep2dot file.c file.dot # from gcc -H ... file.c ... 2>file.dep\n"); + + output_dot(data_read_all(argv[1])); + + return EXIT_SUCCESS; +} diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/src/output.c b/P04_Modularisieren_von_C_Code/show-dependencies/src/output.c new file mode 100644 index 0000000..088d984 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/src/output.c @@ -0,0 +1,100 @@ +/** + * @file + * @brief Provides output functions for various file formats. + */ +#include "output.h" +#include +#include +#include + +/** + * @brief Writes the node name of the given file. + * @param file [IN] The file for which to write the node name. + * @remark The dependency data contain duplicates of file entries - the node name must be unique for the path and the *basename* of the files. + */ +static void print_node(file_t file) +{ + printf("\"%s (cluster_c%zd)\"", file.name, file.dir); +} + +/** + * @brief Recursively writes the individual direct dependencies for the file given by curr. + * @param files [IN] The array of all files - the sequence is relevant. + * @param len [IN] The lenght of the files array, i.e. the upper limit for curr values and the subsequent index values. + * @param curr [IN] The index into files for the current root for dependencies: curr -> x, curr -> y, ... + * @return Returns the index into files for the next file to process (i.e. curr value for the next call to this function). + * @remark For a given *curr* file, all following files are with depth level + 1 are direct include files. + * @remark All files with a higher level are *indirect* include files, thus *direct* includes from files processed by recursive calls. + * @remark The list of direct includes to the *curr* file terminates with a level equal of less the the *curr* one (or when the list is exchausted). + */ +static size_t dependencies(file_t files[], size_t len, size_t curr) +{ + assert(curr < len); + size_t level = files[curr].level; + size_t file = curr + 1; + while(file < len && files[file].level > level) { + if (files[file].level == level + 1) { + // Write to stdout " file -> include;\n" where file and include are the DOT node names of the respective files + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + + // END-STUDENTS-TO-ADD-CODE + file = dependencies(files, len, file); + } else { + file++; + } + } + return file; +} + +/* + * Public interface + */ +void output_dot(const data_t data) +{ + printf("digraph dep {\n"); + // nodes + printf(" node [shape=box]\n"); + for (size_t file = 0; file < data.n_files; file++) { + // Write to stdout " file [label=\"name\"];\n" where file is the DOT node name and name is the file name + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + // END-STUDENTS-TO-ADD-CODE + } + // directory clusters + for (size_t dir = 0; dir < data.n_dirs; dir++) { + printf(" subgraph cluster_c%zd {\n", dir); + printf(" label=\"%s\"; %s\n", data.dirs[dir].name, strncmp(data.dirs[dir].name, "/usr/", 5) == 0 ? "style=filled; color=lightgrey;" : "color=black;"); + for (size_t file = 0; file < data.n_files; file++) { + if (data.files[file].dir == dir) { + // Write to stdout " file;\n" where file is the DOT node name + // BEGIN-STUDENTS-TO-ADD-CODE + + + + + + // END-STUDENTS-TO-ADD-CODE + } + } + printf(" }\n"); + } + + // dependencies + size_t curr = 0; + do { + curr = dependencies(data.files, data.n_files, curr); + } while(curr < data.n_files); + + printf("}\n"); +} + + diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/src/output.h b/P04_Modularisieren_von_C_Code/show-dependencies/src/output.h new file mode 100644 index 0000000..21f46e3 --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/src/output.h @@ -0,0 +1,12 @@ +/** + * @file + * @brief Provides output functions for various file formats. + */ +// define proper header file here, with include gaurd, etc. +// BEGIN-STUDENTS-TO-ADD-CODE + + + + + +// END-STUDENTS-TO-ADD-CODE diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/tests/dep.input b/P04_Modularisieren_von_C_Code/show-dependencies/tests/dep.input new file mode 100644 index 0000000..d21a54c --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/tests/dep.input @@ -0,0 +1,7 @@ +Test File +. dir1/h1_1 +.. dir1/h1_1_2 +. dir1/h1_2 +. dir2/h2_1 +.. dir1/h1_1 +Done diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/tests/no_dep.input b/P04_Modularisieren_von_C_Code/show-dependencies/tests/no_dep.input new file mode 100644 index 0000000..6565e2d --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/tests/no_dep.input @@ -0,0 +1,2 @@ +Test File +Done diff --git a/P04_Modularisieren_von_C_Code/show-dependencies/tests/tests.c b/P04_Modularisieren_von_C_Code/show-dependencies/tests/tests.c new file mode 100755 index 0000000..119800f --- /dev/null +++ b/P04_Modularisieren_von_C_Code/show-dependencies/tests/tests.c @@ -0,0 +1,140 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Test suite for the given package. + */ +#include +#include +#include +#include +#include +#include +#include "test_utils.h" + +#ifndef TARGET // must be given by the make file --> see test target +#error missing TARGET define +#endif + +/// @brief alias for EXIT_SUCCESS +#define OK EXIT_SUCCESS +/// @brief alias for EXIT_FAILURE +#define FAIL EXIT_FAILURE + +/// @brief The name of the STDOUT text file. +#define OUTFILE "stdout.txt" +/// @brief The name of the STDERR text file. +#define ERRFILE "stderr.txt" + +/// @brief test data file +#define IN_NO_DEP "no_dep.input" +/// @brief test data file +#define IN_DEP "dep.input" + +// setup & cleanup +static int setup(void) +{ + remove_file_if_exists(OUTFILE); + remove_file_if_exists(ERRFILE); + return 0; // success +} + +static int teardown(void) +{ + // Do nothing. + // Especially: do not remove result files - they are removed in int setup(void) *before* running a test. + return 0; // success +} + +// tests +static void test_fail_no_arg(void) +{ + // arrange & act & assert + CU_ASSERT_EQUAL(WEXITSTATUS(system(XSTR(TARGET) " >" OUTFILE " 2>" ERRFILE)), FAIL); +} + +static void test_no_dep(void) +{ + // arrange + const char *out_txt[] = { + "digraph dep {\n", + " node [shape=box]\n", + " \"root (cluster_c0)\" [label=\"root\"];\n", + " subgraph cluster_c0 {\n", + " label=\".\"; color=black;\n", + " \"root (cluster_c0)\";\n", + " }\n", + "}\n", + }; + + // act & assert + CU_ASSERT_EQUAL(WEXITSTATUS(system(XSTR(TARGET) " root <" IN_NO_DEP " >" OUTFILE " 2>" ERRFILE)), OK); + + // assert + + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); +} + +static void test_dep(void) +{ + // arrange + const char *out_txt[] = { + "digraph dep {\n", + " node [shape=box]\n", + " \"root (cluster_c0)\" [label=\"root\"];\n", + " \"h1_1 (cluster_c1)\" [label=\"h1_1\"];\n", + " \"h1_1_2 (cluster_c1)\" [label=\"h1_1_2\"];\n", + " \"h1_2 (cluster_c1)\" [label=\"h1_2\"];\n", + " \"h2_1 (cluster_c2)\" [label=\"h2_1\"];\n", + " \"h1_1 (cluster_c1)\" [label=\"h1_1\"];\n", + " subgraph cluster_c0 {\n", + " label=\".\"; color=black;\n", + " \"root (cluster_c0)\";\n", + " }\n", + " subgraph cluster_c1 {\n", + " label=\"dir1\"; color=black;\n", + " \"h1_1 (cluster_c1)\";\n", + " \"h1_1_2 (cluster_c1)\";\n", + " \"h1_2 (cluster_c1)\";\n", + " \"h1_1 (cluster_c1)\";\n", + " }\n", + " subgraph cluster_c2 {\n", + " label=\"dir2\"; color=black;\n", + " \"h2_1 (cluster_c2)\";\n", + " }\n", + " \"root (cluster_c0)\" -> \"h1_1 (cluster_c1)\";\n", + " \"h1_1 (cluster_c1)\" -> \"h1_1_2 (cluster_c1)\";\n", + " \"root (cluster_c0)\" -> \"h1_2 (cluster_c1)\";\n", + " \"root (cluster_c0)\" -> \"h2_1 (cluster_c2)\";\n", + " \"h2_1 (cluster_c2)\" -> \"h1_1 (cluster_c1)\";\n", + "}\n", + }; + + // act & assert + CU_ASSERT_EQUAL(WEXITSTATUS(system(XSTR(TARGET) " root <" IN_DEP " >" OUTFILE " 2>" ERRFILE)), OK); + + // assert + + assert_lines(OUTFILE, out_txt, sizeof(out_txt)/sizeof(*out_txt)); +} + +/** + * @brief Registers and runs the tests. + * @returns success (0) or one of the CU_ErrorCode (>0) + */ +int main(void) +{ + // setup, run, teardown + TestMainBasic("lab test", setup, teardown + , test_fail_no_arg + , test_no_dep + , test_dep + ); +}