13c1a36af985048c726243c6b6730e3f0207f42b
[ccan] / tools / namespacize.c
1 /* Code to move a ccan module into the ccan_ namespace. */
2 #include <errno.h>
3 #include <string.h>
4 #include <stdbool.h>
5 #include <ctype.h>
6 #include <sys/types.h>
7 #include <dirent.h>
8 #include <unistd.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include "ccan/str/str.h"
13 #include "ccan/take/take.h"
14 #include "ccan/rbuf/rbuf.h"
15 #include "ccan/err/err.h"
16 #include "tools.h"
17
18 static bool verbose = false;
19 static int indent = 0;
20 #define verbose(args...)                                                \
21         do { if (verbose) {                                             \
22                         unsigned int _i;                                \
23                         for (_i = 0; _i < indent; _i++) printf(" ");    \
24                         printf(args);                                   \
25                 }                                                       \
26         } while(0)
27 #define verbose_indent() (indent += 2)
28 #define verbose_unindent() (indent -= 2)
29
30 static int unlink_no_errno(const char *filename)
31 {
32         int ret = 0, serrno = errno;
33         if (unlink(filename) < 0)
34                 ret = errno;
35         errno = serrno;
36         return ret;
37 }
38
39 static char **get_dir(const char *dir)
40 {
41         DIR *d;
42         struct dirent *ent;
43         char **names = tal_arr(NULL, char *, 0), *n;
44
45         d = opendir(dir);
46         if (!d)
47                 return NULL;
48
49         while ((ent = readdir(d)) != NULL) {
50                 n = tal_fmt(names, "%s/%s", dir, ent->d_name);
51                 tal_expand(&names, &n, 1);
52         }
53         n = NULL;
54         tal_expand(&names, &n, 1);
55         closedir(d);
56         return names;
57 }
58
59 struct replace
60 {
61         struct replace *next;
62         char *string;
63 };
64
65 static void __attribute__((noreturn)) usage(void)
66 {
67         errx(1, "Usage:\n"
68              "namespacize [--verbose] <dir>\n"
69              "namespacize [--verbose] --adjust <dir>...\n"
70              "The first form converts dir/ to insert 'ccan_' prefixes, and\n"
71              "then adjusts any other ccan directories at the same level which\n"
72              "are effected.\n"
73              "--adjust does an adjustment for each directory, in case a\n"
74              "dependency has been namespacized\n");
75 }
76
77 static void add_replace(struct replace **repl, const char *str)
78 {
79         struct replace *new, *i;
80
81         /* Avoid duplicates. */
82         for (i = *repl; i; i = i->next)
83                 if (streq(i->string, str))
84                         return;
85
86         new = tal(*repl, struct replace);
87         new->next = *repl;
88         new->string = tal_strdup(new, str);
89         *repl = new;
90 }
91
92 static void add_replace_tok(struct replace **repl, const char *s)
93 {
94         struct replace *new;
95         unsigned int len = strspn(s, IDENT_CHARS);
96
97         new = tal(*repl, struct replace);
98         new->next = *repl;
99         new->string = tal_strndup(new, s, len);
100         *repl = new;
101 }
102
103 static void look_for_macros(char *contents, struct replace **repl)
104 {
105         char *p;
106         enum { LINESTART, HASH, DEFINE, NONE } state = LINESTART;
107
108         /* Look for lines of form #define X */
109         for (p = contents; *p; p++) {
110                 if (*p == '\n')
111                         state = LINESTART;
112                 else if (!cisspace(*p)) {
113                         if (state == LINESTART && *p == '#')
114                                 state = HASH;
115                         else if (state==HASH && !strncmp(p, "define", 6)) {
116                                 state = DEFINE;
117                                 p += 5;
118                         } else if (state == DEFINE) {
119                                 unsigned int len;
120
121                                 len = strspn(p, IDENT_CHARS);
122                                 if (len) {
123                                         char *s;
124                                         s = tal_strndup(contents, p, len);
125                                         /* Don't wrap idempotent wrappers */
126                                         if (!strstarts(s, "CCAN_")) {
127                                                 verbose("Found %s\n", s);
128                                                 add_replace(repl, s);
129                                         }
130                                 }
131                                 state = NONE;
132                         } else
133                                 state = NONE;
134                 }
135         }
136 }
137
138 /* Blank out preprocessor lines, and eliminate \ */
139 static void preprocess(char *p)
140 {
141         char *s;
142
143         /* We assume backslashes are only used for macros. */
144         while ((s = strstr(p, "\\\n")) != NULL)
145                 s[0] = s[1] = ' ';
146
147         /* Now eliminate # lines. */
148         if (p[0] == '#') {
149                 unsigned int i;
150                 for (i = 0; p[i] != '\n'; i++)
151                         p[i] = ' ';
152         }
153         while ((s = strstr(p, "\n#")) != NULL) {
154                 unsigned int i;
155                 for (i = 1; s[i] != '\n'; i++)
156                         s[i] = ' ';
157         }
158 }
159
160 static char *get_statement(const void *ctx, char **p)
161 {
162         unsigned brackets = 0;
163         bool seen_brackets = false;
164         char *answer = tal_strdup(ctx, "");
165
166         for (;;) {
167                 if ((*p)[0] == '/' && (*p)[1] == '/')
168                         *p += strcspn(*p, "\n");
169                 else if ((*p)[0] == '/' && (*p)[1] == '*')
170                         *p = strstr(*p, "*/") + 1;
171                 else {
172                         char c = **p;
173                         if (c == ';' && !brackets) {
174                                 (*p)++;
175                                 return answer;
176                         }
177                         /* Compress whitespace into a single ' ' */
178                         if (cisspace(c)) {
179                                 c = ' ';
180                                 while (cisspace((*p)[1]))
181                                         (*p)++;
182                         } else if (c == '{' || c == '(' || c == '[') {
183                                 if (c == '(')
184                                         seen_brackets = true;
185                                 brackets++;
186                         } else if (c == '}' || c == ')' || c == ']')
187                                 brackets--;
188
189                         if (answer[0] != '\0' || c != ' ') {
190                                 tal_append_fmt(&answer, "%c", c);
191                         }
192                         if (c == '}' && seen_brackets && brackets == 0) {
193                                 (*p)++;
194                                 return answer;
195                         }
196                 }
197                 (*p)++;
198                 if (**p == '\0')
199                         return NULL;
200         }
201 }
202
203 /* This hack should handle well-formatted code. */
204 static void look_for_definitions(char *contents, struct replace **repl)
205 {
206         char *stmt, *p = contents;
207
208         preprocess(contents);
209
210         while ((stmt = get_statement(contents, &p)) != NULL) {
211                 int i, len;
212
213                 /* Definition of struct/union? */
214                 if ((strncmp(stmt, "struct", 5) == 0
215                      || strncmp(stmt, "union", 5) == 0)
216                     && strchr(stmt, '{') && stmt[7] != '{')
217                         add_replace_tok(repl, stmt+7);
218
219                 /* Definition of var or typedef? */
220                 for (i = strlen(stmt)-1; i >= 0; i--)
221                         if (strspn(stmt+i, IDENT_CHARS) == 0)
222                                 break;
223
224                 if (i != strlen(stmt)-1) {
225                         add_replace_tok(repl, stmt+i+1);
226                         continue;
227                 }
228
229                 /* function or array declaration? */
230                 len = strspn(stmt, IDENT_CHARS "* ");
231                 if (len > 0 && (stmt[len] == '(' || stmt[len] == '[')) {
232                         if (strspn(stmt + len + 1, IDENT_CHARS) != 0) {
233                                 for (i = len-1; i >= 0; i--)
234                                         if (strspn(stmt+i, IDENT_CHARS) == 0)
235                                                 break;
236                                 if (i != len-1) {
237                                         add_replace_tok(repl, stmt+i+1);
238                                         continue;
239                                 }
240                         } else {
241                                 /* Pointer to function? */
242                                 len++;
243                                 len += strspn(stmt + len, " *");
244                                 i = strspn(stmt + len, IDENT_CHARS);
245                                 if (i > 0 && stmt[len + i] == ')')
246                                         add_replace_tok(repl, stmt+len);
247                         }
248                 }
249         }
250 }
251
252 /* FIXME: Only does main header, should chase local includes. */ 
253 static void analyze_headers(const char *dir, struct replace **repl)
254 {
255         char *hdr, *contents;
256
257         /* Get hold of header, assume that's it. */
258         hdr = tal_fmt(dir, "%s/%s.h", dir, tal_basename(dir, dir));
259         contents = tal_grab_file(dir, hdr, NULL);
260         if (!contents)
261                 err(1, "Reading %s", hdr);
262
263         verbose("Looking in %s for macros\n", hdr);
264         verbose_indent();
265         look_for_macros(contents, repl);
266         verbose_unindent();
267
268         verbose("Looking in %s for symbols\n", hdr);
269         verbose_indent();
270         look_for_definitions(contents, repl);
271         verbose_unindent();
272 }
273
274 static void write_replacement_file(const char *dir, struct replace **repl)
275 {
276         char *replname = tal_fmt(dir, "%s/.namespacize", dir);
277         int fd;
278         struct replace *r;
279
280         fd = open(replname, O_WRONLY|O_CREAT|O_EXCL, 0644);
281         if (fd < 0) {
282                 if (errno == EEXIST)
283                         errx(1, "%s already exists: can't namespacize twice",
284                              replname);
285                 err(1, "Opening %s", replname);
286         }
287
288         for (r = *repl; r; r = r->next) {
289                 if (write(fd,r->string,strlen(r->string)) != strlen(r->string)
290                     || write(fd, "\n", 1) != 1) {
291                         unlink_no_errno(replname);
292                         if (errno == 0)
293                                 errx(1, "Short write to %s: disk full?",
294                                      replname);
295                         errx(1, "Writing to %s", replname);
296                 }
297         }
298
299         close(fd);
300 }
301
302 static void unlink_destroy(char *name)
303 {
304         unlink(name);
305 }
306
307 static char *find_word(char *f, const char *str)
308 {
309         char *p = f;
310
311         while ((p = strstr(p, str)) != NULL) {
312                 /* Check it's not in the middle of a word. */
313                 if (p > f && (cisalnum(p[-1]) || p[-1] == '_')) {
314                         p++;
315                         continue;
316                 }
317                 if (cisalnum(p[strlen(str)]) || p[strlen(str)] == '_') {
318                         p++;
319                         continue;
320                 }
321                 return p;
322         }
323         return NULL;
324 }
325
326 /* This is horribly inefficient but simple. */
327 static const char *rewrite_file(const char *filename,
328                                 const struct replace *repl)
329 {
330         char *newname, *file;
331         int fd;
332
333         verbose("Rewriting %s\n", filename);
334         file = tal_grab_file(filename, filename, NULL);
335         if (!file)
336                 err(1, "Reading file %s", filename);
337
338         for (; repl; repl = repl->next) {
339                 char *p;
340
341                 while ((p = find_word(file, repl->string)) != NULL) {
342                         unsigned int off;
343                         char *new = tal_arr(file, char, strlen(file)+6);
344
345                         off = p - file;
346                         memcpy(new, file, off);
347                         if (cisupper(repl->string[0]))
348                                 memcpy(new + off, "CCAN_", 5);
349                         else
350                                 memcpy(new + off, "ccan_", 5);
351                         strcpy(new + off + 5, file + off);
352                         file = new;
353                 }
354         }
355
356         /* If we exit for some reason, we want this erased. */
357         newname = tal_fmt(autofree(), "%s.tmp", filename);
358         fd = open(newname, O_WRONLY|O_CREAT|O_EXCL, 0644);
359         if (fd < 0)
360                 err(1, "Creating %s", newname);
361
362         tal_add_destructor(newname, unlink_destroy);
363         if (write(fd, file, strlen(file)) != strlen(file)) {
364                 if (errno == 0)
365                         errx(1, "Short write to %s: disk full?", newname);
366                 errx(1, "Writing to %s", newname);
367         }
368         close(fd);
369         return newname;
370 }
371
372 struct adjusted
373 {
374         struct adjusted *next;
375         const char *file;
376         const char *tmpfile;
377 };
378
379 static void setup_adjust_files(const char *dir,
380                                const struct replace *repl,
381                                struct adjusted **adj)
382 {
383         char **files;
384
385         for (files = get_dir(dir); *files; files++) {
386                 if (strends(*files, "/test"))
387                         setup_adjust_files(*files, repl, adj);
388                 else if (strends(*files, ".c") || strends(*files, ".h")) {
389                         struct adjusted *a = tal(dir, struct adjusted);
390                         a->next = *adj;
391                         a->file = *files;
392                         a->tmpfile = rewrite_file(a->file, repl);
393                         *adj = a;
394                 }
395         }
396 }
397
398 /* This is the "commit" stage, so we hope it won't fail. */
399 static void rename_files(const struct adjusted *adj)
400 {
401         while (adj) {
402                 if (!move_file(adj->tmpfile, adj->file))
403                         warn("Could not rename over '%s', we're in trouble",
404                              adj->file);
405                 adj = adj->next;
406         }
407 }
408
409 static void convert_dir(const char *dir)
410 {
411         char *name;
412         struct replace *replace = NULL;
413         struct adjusted *adj = NULL;
414
415         /* Remove any ugly trailing slashes. */
416         name = tal_strdup(NULL, dir);
417         while (strends(name, "/"))
418                 name[strlen(name)-1] = '\0';
419
420         analyze_headers(name, &replace);
421         write_replacement_file(name, &replace);
422         setup_adjust_files(name, replace, &adj);
423         rename_files(adj);
424         tal_free(name);
425         tal_free(replace);
426 }
427
428 static struct replace *read_replacement_file(const char *depdir)
429 {
430         struct replace *repl = NULL;
431         char *replname = tal_fmt(depdir, "%s/.namespacize", depdir);
432         char *file, **line;
433
434         file = tal_grab_file(replname, replname, NULL);
435         if (!file) {
436                 if (errno != ENOENT)
437                         err(1, "Opening %s", replname);
438                 return NULL;
439         }
440
441         for (line = tal_strsplit(file, file, "\n", STR_EMPTY_OK); *line; line++)
442                 add_replace(&repl, *line);
443         return repl;
444 }
445
446 static void adjust_dir(const char *dir)
447 {
448         char *parent = tal_dirname(autofree(), dir);
449         char **deps;
450
451         verbose("Adjusting %s\n", dir);
452         verbose_indent();
453         for (deps = get_deps(parent, dir, "depends", false, compile_info);
454              *deps;
455              deps++) {
456                 char *depdir;
457                 struct adjusted *adj = NULL;
458                 struct replace *repl;
459
460                 depdir = tal_fmt(parent, "%s/%s", parent, *deps);
461                 repl = read_replacement_file(depdir);
462                 if (repl) {
463                         verbose("%s has been namespacized\n", depdir);
464                         setup_adjust_files(parent, repl, &adj);
465                         rename_files(adj);
466                 } else
467                         verbose("%s has not been namespacized\n", depdir);
468                 tal_free(depdir);
469         }
470         verbose_unindent();
471         tal_free(parent);
472 }
473
474 static void adjust_dependents(const char *dir)
475 {
476         char *parent = tal_dirname(NULL, dir);
477         char *base = tal_basename(parent, dir);
478         char **file;
479
480         verbose("Looking for dependents in %s\n", parent);
481         verbose_indent();
482         for (file = get_dir(parent); *file; file++) {
483                 char *info, **deps;
484                 bool isdep = false;
485
486                 if (tal_basename(*file, *file)[0] == '.')
487                         continue;
488
489                 info = tal_fmt(*file, "%s/_info", *file);
490                 if (access(info, R_OK) != 0)
491                         continue;
492
493                 for (deps = get_deps(*file, *file, "depends", false,
494                                      compile_info);
495                      *deps; deps++) {
496                         if (!strstarts(*deps, "ccan/"))
497                                 continue;
498                         if (streq(*deps + strlen("ccan/"), base))
499                                 isdep = true;
500                 }
501                 if (isdep)
502                         adjust_dir(*file);
503                 else
504                         verbose("%s is not dependent\n", *file);
505         }
506         verbose_unindent();
507 }
508
509 int main(int argc, char *argv[])
510 {
511         if (argv[1] && streq(argv[1], "--verbose")) {
512                 verbose = true;
513                 argv++;
514                 argc--;
515         }
516
517         if (argc == 2) {
518                 verbose("Namespacizing %s\n", argv[1]);
519                 verbose_indent();
520                 convert_dir(argv[1]);
521                 adjust_dependents(argv[1]);
522                 verbose_unindent();
523                 return 0;
524         }
525
526         if (argc > 2 && streq(argv[1], "--adjust")) {
527                 unsigned int i;
528
529                 for (i = 2; i < argc; i++)
530                         adjust_dir(argv[i]);
531                 return 0;
532         }
533         usage();
534 }