1/* Dependency generator for Makefile fragments.
2 Copyright (C) 2000-2025 Free Software Foundation, Inc.
3 Contributed by Zack Weinberg, Mar 2000
4
5This program is free software; you can redistribute it and/or modify it
6under the terms of the GNU General Public License as published by the
7Free Software Foundation; either version 3, or (at your option) any
8later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; see the file COPYING3. If not see
17<http://www.gnu.org/licenses/>.
18
19 In other words, you are welcome to use, share and improve this program.
20 You are forbidden to forbid anyone else to use, share and improve
21 what you give them. Help stamp out software-hoarding! */
22
23#include "config.h"
24#include "system.h"
25#include "mkdeps.h"
26#include "internal.h"
27
28/* Not set up to just include std::vector et al, here's a simple
29 implementation. */
30
31/* Keep this structure local to this file, so clients don't find it
32 easy to start making assumptions. */
33class mkdeps
34{
35public:
36 /* T has trivial cctor & dtor. */
37 template <typename T>
38 class vec
39 {
40 private:
41 T *ary;
42 unsigned num;
43 unsigned alloc;
44
45 public:
46 vec ()
47 : ary (NULL), num (0), alloc (0)
48 {}
49 ~vec ()
50 {
51 XDELETEVEC (ary);
52 }
53
54 public:
55 unsigned size () const
56 {
57 return num;
58 }
59 const T &operator[] (unsigned ix) const
60 {
61 return ary[ix];
62 }
63 T &operator[] (unsigned ix)
64 {
65 return ary[ix];
66 }
67 void push (const T &elt)
68 {
69 if (num == alloc)
70 {
71 alloc = alloc ? alloc * 2 : 16;
72 ary = XRESIZEVEC (T, ary, alloc);
73 }
74 ary[num++] = elt;
75 }
76 };
77 struct velt
78 {
79 const char *str;
80 size_t len;
81 };
82
83 mkdeps ()
84 : primary_output (NULL), module_name (NULL), cmi_name (NULL)
85 , is_header_unit (false), is_exported (false), quote_lwm (0)
86 {
87 }
88 ~mkdeps ()
89 {
90 unsigned int i;
91
92 for (i = targets.size (); i--;)
93 free (ptr: const_cast <char *> (targets[i]));
94 free (ptr: const_cast <char *> (primary_output));
95 for (i = fdeps_targets.size (); i--;)
96 free (ptr: const_cast <char *> (fdeps_targets[i]));
97 for (i = deps.size (); i--;)
98 free (ptr: const_cast <char *> (deps[i]));
99 for (i = vpath.size (); i--;)
100 XDELETEVEC (vpath[i].str);
101 for (i = modules.size (); i--;)
102 XDELETEVEC (modules[i]);
103 XDELETEVEC (module_name);
104 free (ptr: const_cast <char *> (cmi_name));
105 }
106
107public:
108 vec<const char *> targets;
109 vec<const char *> deps;
110 const char * primary_output;
111 vec<const char *> fdeps_targets;
112 vec<velt> vpath;
113 vec<const char *> modules;
114
115public:
116 const char *module_name;
117 const char *cmi_name;
118 bool is_header_unit;
119 bool is_exported;
120 unsigned short quote_lwm;
121};
122
123/* Apply Make quoting to STR, TRAIL. Note that it's not possible to
124 quote all such characters - e.g. \n, %, *, ?, [, \ (in some
125 contexts), and ~ are not properly handled. It isn't possible to
126 get this right in any current version of Make. (??? Still true?
127 Old comment referred to 3.76.1.) */
128
129static const char *
130munge (const char *str, const char *trail = nullptr)
131{
132 static unsigned alloc;
133 static char *buf;
134 unsigned dst = 0;
135
136 for (; str; str = trail, trail = nullptr)
137 {
138 unsigned slashes = 0;
139 char c;
140 for (const char *probe = str; (c = *probe++);)
141 {
142 if (alloc < dst + 4 + slashes)
143 {
144 alloc = alloc * 2 + 32;
145 buf = XRESIZEVEC (char, buf, alloc);
146 }
147
148 switch (c)
149 {
150 case '\\':
151 slashes++;
152 break;
153
154 case '$':
155 buf[dst++] = '$';
156 goto def;
157
158 case ' ':
159 case '\t':
160 /* GNU make uses a weird quoting scheme for white space.
161 A space or tab preceded by 2N+1 backslashes
162 represents N backslashes followed by space; a space
163 or tab preceded by 2N backslashes represents N
164 backslashes at the end of a file name; and
165 backslashes in other contexts should not be
166 doubled. */
167 while (slashes--)
168 buf[dst++] = '\\';
169 /* FALLTHROUGH */
170
171 case '#':
172 buf[dst++] = '\\';
173 /* FALLTHROUGH */
174
175 default:
176 def:
177 slashes = 0;
178 break;
179 }
180
181 buf[dst++] = c;
182 }
183 }
184
185 buf[dst] = 0;
186 return buf;
187}
188
189/* If T begins with any of the partial pathnames listed in d->vpathv,
190 then advance T to point beyond that pathname. */
191static const char *
192apply_vpath (class mkdeps *d, const char *t)
193{
194 if (unsigned len = d->vpath.size ())
195 for (unsigned i = len; i--;)
196 {
197 if (!filename_ncmp (s1: d->vpath[i].str, s2: t, n: d->vpath[i].len))
198 {
199 const char *p = t + d->vpath[i].len;
200 if (!IS_DIR_SEPARATOR (*p))
201 goto not_this_one;
202
203 /* Do not simplify $(vpath)/../whatever. ??? Might not
204 be necessary. */
205 if (p[1] == '.' && p[2] == '.' && IS_DIR_SEPARATOR (p[3]))
206 goto not_this_one;
207
208 /* found a match */
209 t = t + d->vpath[i].len + 1;
210 break;
211 }
212 not_this_one:;
213 }
214
215 /* Remove leading ./ in any case. */
216 while (t[0] == '.' && IS_DIR_SEPARATOR (t[1]))
217 {
218 t += 2;
219 /* If we removed a leading ./, then also remove any /s after the
220 first. */
221 while (IS_DIR_SEPARATOR (t[0]))
222 ++t;
223 }
224
225 return t;
226}
227
228/* Public routines. */
229
230class mkdeps *
231deps_init (void)
232{
233 return new mkdeps ();
234}
235
236void
237deps_free (class mkdeps *d)
238{
239 delete d;
240}
241
242/* Adds a target T. We make a copy, so it need not be a permanent
243 string. QUOTE is true if the string should be quoted. */
244void
245deps_add_target (class mkdeps *d, const char *t, int quote)
246{
247 t = xstrdup (apply_vpath (d, t));
248
249 if (!quote)
250 {
251 /* Sometimes unquoted items are added after quoted ones.
252 Swap out the lowest quoted. */
253 if (d->quote_lwm != d->targets.size ())
254 {
255 const char *lowest = d->targets[d->quote_lwm];
256 d->targets[d->quote_lwm] = t;
257 t = lowest;
258 }
259 d->quote_lwm++;
260 }
261
262 d->targets.push (elt: t);
263}
264
265/* Sets the default target if none has been given already. An empty
266 string as the default target in interpreted as stdin. The string
267 is quoted for MAKE. */
268void
269deps_add_default_target (class mkdeps *d, const char *tgt)
270{
271 /* Only if we have no targets. */
272 if (d->targets.size ())
273 return;
274
275 if (tgt[0] == '\0')
276 d->targets.push (elt: xstrdup ("-"));
277 else
278 {
279#ifndef TARGET_OBJECT_SUFFIX
280# define TARGET_OBJECT_SUFFIX ".o"
281#endif
282 const char *start = lbasename (tgt);
283 char *o = (char *) alloca (strlen (start)
284 + strlen (TARGET_OBJECT_SUFFIX) + 1);
285 char *suffix;
286
287 strcpy (dest: o, src: start);
288
289 suffix = strrchr (s: o, c: '.');
290 if (!suffix)
291 suffix = o + strlen (s: o);
292 strcpy (dest: suffix, TARGET_OBJECT_SUFFIX);
293
294 deps_add_target (d, t: o, quote: 1);
295 }
296}
297
298/* Adds a target O. We make a copy, so it need not be a permanent
299 string.
300
301 This is the target associated with the rule that (in a C++ modules build)
302 compiles the source that is being scanned for dynamic dependencies. It is
303 used to associate the structured dependency information with that rule as
304 needed. */
305void
306fdeps_add_target (struct mkdeps *d, const char *o, bool is_primary)
307{
308 o = apply_vpath (d, t: o);
309 if (is_primary)
310 {
311 if (d->primary_output)
312 d->fdeps_targets.push (elt: d->primary_output);
313 d->primary_output = xstrdup (o);
314 } else
315 d->fdeps_targets.push (elt: xstrdup (o));
316}
317
318void
319deps_add_dep (class mkdeps *d, const char *t)
320{
321 gcc_assert (*t);
322
323 t = apply_vpath (d, t);
324
325 d->deps.push (elt: xstrdup (t));
326}
327
328void
329deps_add_vpath (class mkdeps *d, const char *vpath)
330{
331 const char *elem, *p;
332
333 for (elem = vpath; *elem; elem = p)
334 {
335 for (p = elem; *p && *p != ':'; p++)
336 continue;
337 mkdeps::velt elt;
338 elt.len = p - elem;
339 char *str = XNEWVEC (char, elt.len + 1);
340 elt.str = str;
341 memcpy (dest: str, src: elem, n: elt.len);
342 str[elt.len] = '\0';
343 if (*p == ':')
344 p++;
345
346 d->vpath.push (elt);
347 }
348}
349
350/* Add a new module target (there can only be one). M is the module
351 name. */
352
353void
354deps_add_module_target (struct mkdeps *d, const char *m,
355 const char *cmi, bool is_header_unit, bool is_exported)
356{
357 gcc_assert (!d->module_name);
358
359 d->module_name = xstrdup (m);
360 d->is_header_unit = is_header_unit;
361 d->is_exported = is_exported;
362 d->cmi_name = xstrdup (cmi);
363}
364
365/* Add a new module dependency. M is the module name. */
366
367void
368deps_add_module_dep (struct mkdeps *d, const char *m)
369{
370 d->modules.push (elt: xstrdup (m));
371}
372
373/* Write NAME, with a leading space to FP, a Makefile. Advance COL as
374 appropriate, wrap at COLMAX, returning new column number. Iff
375 QUOTE apply quoting. Append TRAIL. */
376
377static unsigned
378make_write_name (const char *name, FILE *fp, unsigned col, unsigned colmax,
379 bool quote = true, const char *trail = NULL)
380{
381 if (quote)
382 name = munge (str: name, trail);
383 unsigned size = strlen (s: name);
384
385 if (col)
386 {
387 if (colmax && col + size> colmax)
388 {
389 fputs (" \\\n", fp);
390 col = 0;
391 }
392 col++;
393 fputs (" ", fp);
394 }
395
396 col += size;
397 fputs (name, fp);
398
399 return col;
400}
401
402/* Write all the names in VEC via make_write_name. */
403
404static unsigned
405make_write_vec (const mkdeps::vec<const char *> &vec, FILE *fp,
406 unsigned col, unsigned colmax, unsigned quote_lwm = 0,
407 const char *trail = NULL)
408{
409 for (unsigned ix = 0; ix != vec.size (); ix++)
410 col = make_write_name (name: vec[ix], fp, col, colmax, quote: ix >= quote_lwm, trail);
411 return col;
412}
413
414/* Write the dependencies to a Makefile. */
415
416static void
417make_write (const cpp_reader *pfile, FILE *fp, unsigned int colmax)
418{
419 const mkdeps *d = pfile->deps;
420
421 unsigned column = 0;
422 if (colmax && colmax < 34)
423 colmax = 34;
424
425 /* Write out C++ modules information if no other `-fdeps-format=`
426 option is given. */
427 cpp_fdeps_format fdeps_format = CPP_OPTION (pfile, deps.fdeps_format);
428 bool write_make_modules_deps = (fdeps_format == FDEPS_FMT_NONE
429 && CPP_OPTION (pfile, deps.modules));
430
431 if (d->deps.size ())
432 {
433 column = make_write_vec (vec: d->targets, fp, col: 0, colmax, quote_lwm: d->quote_lwm);
434 if (write_make_modules_deps && d->cmi_name)
435 column = make_write_name (name: d->cmi_name, fp, col: column, colmax);
436 fputs (":", fp);
437 column++;
438 make_write_vec (vec: d->deps, fp, col: column, colmax);
439 fputs ("\n", fp);
440 if (CPP_OPTION (pfile, deps.phony_targets))
441 for (unsigned i = 1; i < d->deps.size (); i++)
442 fprintf (stream: fp, format: "%s:\n", munge (str: d->deps[i]));
443 }
444
445 if (!write_make_modules_deps)
446 return;
447
448 if (d->modules.size ())
449 {
450 column = make_write_vec (vec: d->targets, fp, col: 0, colmax, quote_lwm: d->quote_lwm);
451 if (d->cmi_name)
452 column = make_write_name (name: d->cmi_name, fp, col: column, colmax);
453 fputs (":", fp);
454 column++;
455 column = make_write_vec (vec: d->modules, fp, col: column, colmax, quote_lwm: 0, trail: ".c++-module");
456 fputs ("\n", fp);
457 }
458
459 if (d->module_name)
460 {
461 if (d->cmi_name)
462 {
463 /* module-name : cmi-name */
464 column = make_write_name (name: d->module_name, fp, col: 0, colmax,
465 quote: true, trail: ".c++-module");
466 const char *module_basename = nullptr;
467 if (d->is_header_unit)
468 {
469 /* Also emit a target for the include name, so for #include
470 <iostream> you'd make iostream.c++-header-unit, regardless of
471 what actual directory iostream lives in. We reconstruct the
472 include name by skipping the directory where we found it. */
473 auto *dir = _cpp_get_file_dir (pfile->main_file);
474 gcc_assert (!strncmp (d->module_name, dir->name, dir->len));
475 module_basename = (d->module_name + dir->len + 1);
476 column = make_write_name (name: module_basename, fp, col: column, colmax,
477 quote: true, trail: ".c++-header-unit");
478 }
479 fputs (":", fp);
480 column++;
481 column = make_write_name (name: d->cmi_name, fp, col: column, colmax);
482 fputs ("\n", fp);
483
484 column = fprintf (stream: fp, format: ".PHONY:");
485 column = make_write_name (name: d->module_name, fp, col: column, colmax,
486 quote: true, trail: ".c++-module");
487 if (module_basename)
488 column = make_write_name (name: module_basename, fp, col: column, colmax,
489 quote: true, trail: ".c++-header-unit");
490 fputs ("\n", fp);
491 }
492
493 if (d->cmi_name && !d->is_header_unit)
494 {
495 /* An order-only dependency.
496 cmi-name :| first-target
497 We can probably drop this this in favour of Make-4.3's grouped
498 targets '&:' */
499 column = make_write_name (name: d->cmi_name, fp, col: 0, colmax);
500 fputs (":|", fp);
501 column++;
502 column = make_write_name (name: d->targets[0], fp, col: column, colmax);
503 fputs ("\n", fp);
504 }
505 }
506
507 if (d->modules.size ())
508 {
509 column = fprintf (stream: fp, format: "CXX_IMPORTS +=");
510 make_write_vec (vec: d->modules, fp, col: column, colmax, quote_lwm: 0, trail: ".c++-module");
511 fputs ("\n", fp);
512 }
513}
514
515/* Write out dependencies according to the selected format (which is
516 only Make at the moment). */
517/* Really we should be opening fp here. */
518
519void
520deps_write (const cpp_reader *pfile, FILE *fp, unsigned int colmax)
521{
522 make_write (pfile, fp, colmax);
523}
524
525/* Write out a a filepath for P1689R5 output. */
526
527static void
528p1689r5_write_filepath (const char *name, FILE *fp)
529{
530 if (cpp_valid_utf8_p (data: name, num_bytes: strlen (s: name)))
531 {
532 fputc ('"', fp);
533 for (const char* c = name; *c; c++)
534 {
535 // Escape control characters.
536 if (ISCNTRL (*c))
537 fprintf (stream: fp, format: "\\u%04x", *c);
538 // JSON escape characters.
539 else if (*c == '"' || *c == '\\')
540 {
541 fputc ('\\', fp);
542 fputc (*c, fp);
543 }
544 // Everything else.
545 else
546 fputc (*c, fp);
547 }
548 fputc ('"', fp);
549 }
550 else
551 {
552 // TODO: print an error
553 }
554}
555
556/* Write a JSON array from a `vec` for P1689R5 output.
557
558 In P1689R5, all array values are filepaths. */
559
560static void
561p1689r5_write_vec (const mkdeps::vec<const char *> &vec, FILE *fp)
562{
563 for (unsigned ix = 0; ix != vec.size (); ix++)
564 {
565 p1689r5_write_filepath (name: vec[ix], fp);
566 if (ix < vec.size () - 1)
567 fputc (',', fp);
568 fputc ('\n', fp);
569 }
570}
571
572/* Write out the P1689R5 format using the module dependency tracking
573 information gathered while scanning and/or compiling.
574
575 Ideally this (and the above `p1689r5_` functions) would use `gcc/json.h`,
576 but since this is `libcpp`, we cannot use `gcc/` code.
577
578 TODO: move `json.h` to libiberty. */
579
580void
581deps_write_p1689r5 (const struct mkdeps *d, FILE *fp)
582{
583 fputs ("{\n", fp);
584
585 fputs ("\"rules\": [\n", fp);
586 fputs ("{\n", fp);
587
588 if (d->primary_output)
589 {
590 fputs ("\"primary-output\": ", fp);
591 p1689r5_write_filepath (name: d->primary_output, fp);
592 fputs (",\n", fp);
593 }
594
595 if (d->fdeps_targets.size ())
596 {
597 fputs ("\"outputs\": [\n", fp);
598 p1689r5_write_vec (vec: d->fdeps_targets, fp);
599 fputs ("],\n", fp);
600 }
601
602 if (d->module_name)
603 {
604 fputs ("\"provides\": [\n", fp);
605 fputs ("{\n", fp);
606
607 fputs ("\"logical-name\": ", fp);
608 p1689r5_write_filepath (name: d->module_name, fp);
609 fputs (",\n", fp);
610
611 fprintf (stream: fp, format: "\"is-interface\": %s\n", d->is_exported ? "true" : "false");
612
613 // TODO: header-unit information
614
615 fputs ("}\n", fp);
616 fputs ("],\n", fp);
617 }
618
619 fputs ("\"requires\": [\n", fp);
620 for (size_t i = 0; i < d->modules.size (); i++)
621 {
622 if (i != 0)
623 fputs (",\n", fp);
624 fputs ("{\n", fp);
625
626 fputs ("\"logical-name\": ", fp);
627 p1689r5_write_filepath (name: d->modules[i], fp);
628 fputs ("\n", fp);
629
630 // TODO: header-unit information
631
632 fputs ("}\n", fp);
633 }
634 fputs ("]\n", fp);
635
636 fputs ("}\n", fp);
637
638 fputs ("],\n", fp);
639
640 fputs ("\"version\": 0,\n", fp);
641 fputs ("\"revision\": 0\n", fp);
642
643 fputs ("}\n", fp);
644}
645
646/* Write out a deps buffer to a file, in a form that can be read back
647 with deps_restore. Returns nonzero on error, in which case the
648 error number will be in errno. */
649
650int
651deps_save (class mkdeps *deps, FILE *f)
652{
653 unsigned int i;
654 size_t size;
655
656 /* The cppreader structure contains makefile dependences. Write out this
657 structure. */
658
659 /* The number of dependences. */
660 size = deps->deps.size ();
661 if (fwrite (&size, sizeof (size), 1, f) != 1)
662 return -1;
663
664 /* The length of each dependence followed by the string. */
665 for (i = 0; i < deps->deps.size (); i++)
666 {
667 size = strlen (s: deps->deps[i]);
668 if (fwrite (&size, sizeof (size), 1, f) != 1)
669 return -1;
670 if (fwrite (deps->deps[i], size, 1, f) != 1)
671 return -1;
672 }
673
674 return 0;
675}
676
677/* Read back dependency information written with deps_save into
678 the deps sizefer. The third argument may be NULL, in which case
679 the dependency information is just skipped, or it may be a filename,
680 in which case that filename is skipped. */
681
682int
683deps_restore (class mkdeps *deps, FILE *fd, const char *self)
684{
685 size_t size;
686 char *buf = NULL;
687 size_t buf_size = 0;
688
689 /* Number of dependences. */
690 if (fread (&size, sizeof (size), 1, fd) != 1)
691 return -1;
692
693 /* The length of each dependence string, followed by the string. */
694 for (unsigned i = size; i--;)
695 {
696 /* Read in # bytes in string. */
697 if (fread (&size, sizeof (size), 1, fd) != 1)
698 return -1;
699
700 if (size >= buf_size)
701 {
702 buf_size = size + 512;
703 buf = XRESIZEVEC (char, buf, buf_size);
704 }
705 if (fread (buf, 1, size, fd) != size)
706 {
707 XDELETEVEC (buf);
708 return -1;
709 }
710 buf[size] = 0;
711
712 /* Generate makefile dependencies from .pch if -nopch-deps. */
713 if (self != NULL && filename_cmp (s1: buf, s2: self) != 0)
714 deps_add_dep (d: deps, t: buf);
715 }
716
717 XDELETEVEC (buf);
718 return 0;
719}
720

source code of libcpp/mkdeps.cc