ccanlint: don't remove HAVE_STRUCT_TIMESPEC when testing without features.
[ccan] / tools / ccanlint / tests / reduce_features.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <tools/tools.h>
3 #include <ccan/htable/htable_type.h>
4 #include <ccan/foreach/foreach.h>
5 #include <ccan/talloc/talloc.h>
6 #include <ccan/grab_file/grab_file.h>
7 #include <ccan/str/str.h>
8 #include <ccan/str_talloc/str_talloc.h>
9 #include <ccan/hash/hash.h>
10 #include <ccan/read_write_all/read_write_all.h>
11 #include <errno.h>
12 #include <err.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <unistd.h>
17 #include "reduce_features.h"
18
19 bool features_were_reduced;
20
21 static const char *can_run(struct manifest *m)
22 {
23         if (!config_header)
24                 return talloc_strdup(m, "Could not read config.h");
25         return NULL;
26 }
27
28 static size_t option_hash(const char *name)
29 {
30         return hash(name, strlen(name), 0);
31 }
32
33 static const char *option_name(const char *name)
34 {
35         return name;
36 }
37
38 static bool option_cmp(const char *name1, const char *name2)
39 {
40         return streq(name1, name2);
41 }
42
43 HTABLE_DEFINE_TYPE(char, option_name, option_hash, option_cmp, htable_option);
44
45 static struct htable_option *htable_option_new(void)
46 {
47         struct htable_option *opts = malloc(sizeof(*opts));
48         htable_option_init(opts);
49         return opts;
50 }
51
52 static void htable_option_free(struct htable_option *opts)
53 {
54         htable_option_clear(opts);
55         free(opts);
56 }
57
58 static unsigned int add_options(struct htable_option *opts,
59                                 struct pp_conditions *cond)
60 {
61         unsigned int num = 0;
62         if (cond->parent)
63                 num += add_options(opts, cond->parent);
64         if (cond->type == PP_COND_IF || cond->type == PP_COND_IFDEF) {
65                 if (strstarts(cond->symbol, "HAVE_")) {
66                         if (!htable_option_get(opts, cond->symbol)) {
67                                 htable_option_add(opts, cond->symbol);
68                                 num++;
69                         }
70                 }
71         }
72         return num;
73 }
74
75 static struct htable_option *get_used_options(struct manifest *m)
76 {
77         struct list_head *list;
78         struct ccan_file *f;
79         unsigned int i, num;
80         struct htable_option *opts = htable_option_new();
81         struct line_info *info;
82
83         num = 0;
84         foreach_ptr(list, &m->c_files, &m->h_files) {
85                 list_for_each(list, f, list) {
86                         info = get_ccan_line_info(f);
87                         struct pp_conditions *prev = NULL;
88
89                         for (i = 0; i < f->num_lines; i++) {
90                                 if (info[i].cond && info[i].cond != prev) {
91                                         num += add_options(opts, info[i].cond);
92                                         prev = info[i].cond;
93                                 }
94                         }
95                 }
96         }
97
98         if (!num) {
99                 htable_option_free(opts);
100                 opts = NULL;
101         }
102         return opts;
103 }
104
105 static struct htable_option *get_config_options(struct manifest *m)
106 {
107         const char **lines = (const char **)strsplit(m, config_header, "\n");
108         unsigned int i;
109         struct htable_option *opts = htable_option_new();
110
111         for (i = 0; i < talloc_array_length(lines) - 1; i++) {
112                 char *sym;
113
114                 if (!get_token(&lines[i], "#"))
115                         continue;
116                 if (!get_token(&lines[i], "define"))
117                         continue;
118                 sym = get_symbol_token(lines, &lines[i]);
119                 if (!strstarts(sym, "HAVE_"))
120                         continue;
121                 /* Don't override endian... */
122                 if (strends(sym, "_ENDIAN"))
123                         continue;
124                 /* Don't override HAVE_STRUCT_TIMESPEC. */
125                 if (streq(sym, "HAVE_STRUCT_TIMESPEC"))
126                         continue;
127                 if (!get_token(&lines[i], "1"))
128                         continue;
129                 htable_option_add(opts, sym);
130         }
131         return opts;
132 }
133
134 static void do_reduce_features(struct manifest *m,
135                                unsigned int *timeleft, struct score *score)
136 {
137         struct htable_option *options_used, *options_avail, *options;
138         struct htable_option_iter i;
139         int fd;
140         const char *sym;
141         char *hdr;
142
143         /* This isn't really a test, as such. */
144         score->total = 0;
145         score->pass = true;
146
147         options_used = get_used_options(m);
148         if (!options_used) {
149                 return;
150         }
151         options_avail = get_config_options(m);
152
153         options = NULL;
154         for (sym = htable_option_first(options_used, &i);
155              sym;
156              sym = htable_option_next(options_used, &i)) {
157                 if (htable_option_get(options_avail, sym)) {
158                         if (!options)
159                                 options = htable_option_new();
160                         htable_option_add(options, sym);
161                 }
162         }
163         htable_option_free(options_avail);
164         htable_option_free(options_used);
165
166         if (!options)
167                 return;
168
169         /* Now make our own config.h variant, with our own options. */
170         hdr = talloc_strdup(m, "/* Modified by reduce_features */\n");
171         hdr = talloc_append_string(hdr, config_header);
172         for (sym = htable_option_first(options, &i);
173              sym;
174              sym = htable_option_next(options, &i)) {
175                 hdr = talloc_asprintf_append
176                         (hdr, "#undef %s\n#define %s 0\n", sym, sym);
177         }
178         if (mkdir("reduced-features", 0700) != 0 && errno != EEXIST)
179                 err(1, "Creating reduced-features directory");
180
181         fd = open("reduced-features/config.h", O_TRUNC|O_CREAT|O_RDWR, 0600);
182         if (fd < 0)
183                 err(1, "Creating reduced-features/config.h");
184         if (!write_all(fd, hdr, strlen(hdr)))
185                 err(1, "Writing reduced-features/config.h");
186         close(fd);
187         features_were_reduced = true;
188 }
189
190 struct ccanlint reduce_features = {
191         .key = "reduce_features",
192         .name = "Produce config.h with reduced features",
193         .can_run = can_run,
194         .check = do_reduce_features,
195         /* We only want to compile up versions with reduced featuress once
196          * objects for normal tests are built. */
197         .needs = "tests_compile"
198 };
199 REGISTER_TEST(reduce_features);