aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2025-01-14 09:52:58 +0000
committerRon Yorston <rmy@pobox.com>2025-01-14 12:22:55 +0000
commit85bbce87d6428fbc6a8bf7126988c4458a033727 (patch)
treea82e077df6058209055843a7fc80ac4100f267be
parent0e8bc6527c9d4b9af17e9b0f759b1ad12ae1bc35 (diff)
downloadbusybox-w32-85bbce87d6428fbc6a8bf7126988c4458a033727.tar.gz
busybox-w32-85bbce87d6428fbc6a8bf7126988c4458a033727.tar.bz2
busybox-w32-85bbce87d6428fbc6a8bf7126988c4458a033727.zip
make: support GNU/BSD suffixes and inference rules
The specification of inference rules in POSIX implies that only suffixes starting with a period and containing no other periods are to be considered. In contrast, both GNU and BSD make allow suffixes to contain an arbitrary number of periods, or none at all. Allow this as an extension. Adds 640-816 bytes. (pdpmake GitHub issue 70)
-rw-r--r--libbb/appletlib.c2
-rw-r--r--miscutils/make.c263
-rwxr-xr-xtestsuite/make.tests30
3 files changed, 224 insertions, 71 deletions
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index d6e042775..b1064d10a 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -933,7 +933,7 @@ int busybox_main(int argc UNUSED_PARAM, char **argv)
933 full_write1_str(" multi-call binary.\n"); /* reuse */ 933 full_write1_str(" multi-call binary.\n"); /* reuse */
934#endif 934#endif
935 full_write1_str( 935 full_write1_str(
936 "BusyBox is copyrighted by many authors between 1998-2024.\n" 936 "BusyBox is copyrighted by many authors between 1998-2025.\n"
937 "Licensed under GPLv2. See source distribution for detailed\n" 937 "Licensed under GPLv2. See source distribution for detailed\n"
938 "copyright notices.\n" 938 "copyright notices.\n"
939 "\n" 939 "\n"
diff --git a/miscutils/make.c b/miscutils/make.c
index 7316408bf..01fc18822 100644
--- a/miscutils/make.c
+++ b/miscutils/make.c
@@ -257,6 +257,11 @@ enum {
257#define MAKE_FAILURE 0x01 257#define MAKE_FAILURE 0x01
258#define MAKE_DIDSOMETHING 0x02 258#define MAKE_DIDSOMETHING 0x02
259 259
260// Return TRUE if c is allowed in a POSIX 2017 macro or target name
261#define ispname(c) (isalpha(c) || isdigit(c) || c == '.' || c == '_')
262// Return TRUE if c is in the POSIX 'portable filename character set'
263#define isfname(c) (ispname(c) || c == '-')
264
260#define HTABSIZE 39 265#define HTABSIZE 39
261 266
262struct globals { 267struct globals {
@@ -320,11 +325,8 @@ struct globals {
320#endif 325#endif
321 326
322static int make(struct name *np, int level); 327static int make(struct name *np, int level);
323 328static struct name *dyndep(struct name *np, struct rule *infrule,
324// Return TRUE if c is allowed in a POSIX 2017 macro or target name 329 const char **ptsuff);
325#define ispname(c) (isalpha(c) || isdigit(c) || c == '.' || c == '_')
326// Return TRUE if c is in the POSIX 'portable filename character set'
327#define isfname(c) (ispname(c) || c == '-')
328 330
329/* 331/*
330 * Utility functions. 332 * Utility functions.
@@ -993,38 +995,27 @@ suffix(const char *name)
993} 995}
994 996
995/* 997/*
996 * Dynamic dependency. This routine applies the suffix rules 998 * Search for an inference rule to convert some suffix ('psuff')
997 * to try and find a source and a set of rules for a missing 999 * to the target suffix 'tsuff'. The basename of the prerequisite
998 * target. NULL is returned on failure. On success the name of 1000 * is 'base'.
999 * the implicit prerequisite is returned and the details are
1000 * placed in the imprule structure provided by the caller.
1001 */ 1001 */
1002static struct name * 1002static struct name *
1003dyndep(struct name *np, struct rule *imprule) 1003dyndep0(char *base, const char *tsuff, struct rule *infrule)
1004{ 1004{
1005 char *suff, *newsuff; 1005 char *psuff;
1006 char *base, *name, *member;
1007 struct name *xp; // Suffixes 1006 struct name *xp; // Suffixes
1008 struct name *sp; // Suffix rule 1007 struct name *sp; // Suffix rule
1009 struct name *pp = NULL; // Implicit prerequisite
1010 struct rule *rp; 1008 struct rule *rp;
1011 struct depend *dp; 1009 struct depend *dp;
1012 bool chain = FALSE; 1010 bool chain = FALSE;
1013 1011
1014 member = NULL;
1015 name = splitlib(np->n_name, &member);
1016
1017 suff = xstrdup(suffix(name));
1018 base = member ? member : name;
1019 *suffix(base) = '\0';
1020
1021 xp = newname(".SUFFIXES"); 1012 xp = newname(".SUFFIXES");
1022 retry: 1013 retry:
1023 for (rp = xp->n_rule; rp; rp = rp->r_next) { 1014 for (rp = xp->n_rule; rp; rp = rp->r_next) {
1024 for (dp = rp->r_dep; dp; dp = dp->d_next) { 1015 for (dp = rp->r_dep; dp; dp = dp->d_next) {
1025 // Generate new suffix rule to try 1016 // Generate new suffix rule to try
1026 newsuff = dp->d_name->n_name; 1017 psuff = dp->d_name->n_name;
1027 sp = findname(auto_concat(newsuff, suff)); 1018 sp = findname(auto_concat(psuff, tsuff));
1028 if (sp && sp->n_rule) { 1019 if (sp && sp->n_rule) {
1029 struct name *ip; 1020 struct name *ip;
1030 int got_ip; 1021 int got_ip;
@@ -1032,9 +1023,8 @@ dyndep(struct name *np, struct rule *imprule)
1032 // Has rule already been used in this chain? 1023 // Has rule already been used in this chain?
1033 if ((sp->n_flag & N_MARK)) 1024 if ((sp->n_flag & N_MARK))
1034 continue; 1025 continue;
1035
1036 // Generate a name for an implicit prerequisite 1026 // Generate a name for an implicit prerequisite
1037 ip = newname(auto_concat(base, newsuff)); 1027 ip = newname(auto_concat(base, psuff));
1038 if ((ip->n_flag & N_DOING)) 1028 if ((ip->n_flag & N_DOING))
1039 continue; 1029 continue;
1040 1030
@@ -1045,20 +1035,19 @@ dyndep(struct name *np, struct rule *imprule)
1045 got_ip = ip->n_tim.tv_sec || (ip->n_flag & N_TARGET); 1035 got_ip = ip->n_tim.tv_sec || (ip->n_flag & N_TARGET);
1046 } else { 1036 } else {
1047 sp->n_flag |= N_MARK; 1037 sp->n_flag |= N_MARK;
1048 got_ip = dyndep(ip, NULL) != NULL; 1038 got_ip = dyndep(ip, NULL, NULL) != NULL;
1049 sp->n_flag &= ~N_MARK; 1039 sp->n_flag &= ~N_MARK;
1050 } 1040 }
1051 1041
1052 if (got_ip) { 1042 if (got_ip) {
1053 // Prerequisite exists or we know how to make it 1043 // Prerequisite exists or we know how to make it
1054 if (imprule) { 1044 if (infrule) {
1055 dp = NULL; 1045 dp = NULL;
1056 newdep(&dp, ip); 1046 newdep(&dp, ip);
1057 imprule->r_dep = dp; 1047 infrule->r_dep = dp;
1058 imprule->r_cmd = sp->n_rule->r_cmd; 1048 infrule->r_cmd = sp->n_rule->r_cmd;
1059 } 1049 }
1060 pp = ip; 1050 return ip;
1061 goto finish;
1062 } 1051 }
1063 } 1052 }
1064 } 1053 }
@@ -1069,9 +1058,77 @@ dyndep(struct name *np, struct rule *imprule)
1069 chain = TRUE; 1058 chain = TRUE;
1070 goto retry; 1059 goto retry;
1071 } 1060 }
1072 finish: 1061 return NULL;
1073 free(suff); 1062}
1063
1064/*
1065 * If 'name' ends with 'suffix' return an allocated string containing
1066 * the name with the suffix removed, else return NULL.
1067 */
1068static char *
1069has_suffix(const char *name, const char *suffix)
1070{
1071 ssize_t delta = strlen(name) - strlen(suffix);
1072 char *base = NULL;
1073
1074 if (delta > 0 && strcmp(name + delta, suffix) == 0) {
1075 base = xstrdup(name);
1076 base[delta] = '\0';
1077 }
1078
1079 return base;
1080}
1081
1082/*
1083 * Dynamic dependency. This routine applies the suffix rules
1084 * to try and find a source and a set of rules for a missing
1085 * target. NULL is returned on failure. On success the name of
1086 * the implicit prerequisite is returned and the rule used is
1087 * placed in the infrule structure provided by the caller.
1088 */
1089static struct name *
1090dyndep(struct name *np, struct rule *infrule, const char **ptsuff)
1091{
1092 const char *tsuff;
1093 char *base, *name, *member;
1094 struct name *pp = NULL; // Implicit prerequisite
1095
1096 member = NULL;
1097 name = splitlib(np->n_name, &member);
1098
1099 // POSIX only allows inference rules with one or two periods.
1100 // As an extension this restriction is lifted, but not for
1101 // targets of the form lib.a(member.o).
1102 if (!posix && member == NULL) {
1103 struct name *xp = newname(".SUFFIXES");
1104 for (struct rule *rp = xp->n_rule; rp; rp = rp->r_next) {
1105 for (struct depend *dp = rp->r_dep; dp; dp = dp->d_next) {
1106 tsuff = dp->d_name->n_name;
1107 base = has_suffix(name, tsuff);
1108 if (base) {
1109 pp = dyndep0(base, tsuff, infrule);
1110 free(base);
1111 if (pp) {
1112 if (ptsuff)
1113 *ptsuff = tsuff;
1114 goto done;
1115 }
1116 }
1117 }
1118 }
1119
1120 } else
1121 {
1122 tsuff = xstrdup(suffix(name));
1123 base = member ? member : name;
1124 *suffix(base) = '\0';
1125
1126 pp = dyndep0(base, tsuff, infrule);
1127 free((void *)tsuff);
1128 }
1129 done:
1074 free(name); 1130 free(name);
1131
1075 return pp; 1132 return pp;
1076} 1133}
1077 1134
@@ -1789,9 +1846,10 @@ readline(FILE *fd, int want_command)
1789} 1846}
1790 1847
1791/* 1848/*
1792 * Return TRUE if the argument is a known suffix. 1849 * Return a pointer to the suffix name if the argument is a known suffix
1850 * or NULL if it isn't.
1793 */ 1851 */
1794static int 1852static const char *
1795is_suffix(const char *s) 1853is_suffix(const char *s)
1796{ 1854{
1797 struct name *np; 1855 struct name *np;
@@ -1802,7 +1860,39 @@ is_suffix(const char *s)
1802 for (rp = np->n_rule; rp; rp = rp->r_next) { 1860 for (rp = np->n_rule; rp; rp = rp->r_next) {
1803 for (dp = rp->r_dep; dp; dp = dp->d_next) { 1861 for (dp = rp->r_dep; dp; dp = dp->d_next) {
1804 if (strcmp(s, dp->d_name->n_name) == 0) { 1862 if (strcmp(s, dp->d_name->n_name) == 0) {
1805 return TRUE; 1863 return dp->d_name->n_name;
1864 }
1865 }
1866 }
1867 return NULL;
1868}
1869
1870/*
1871 * Return TRUE if the argument is formed by concatenating two
1872 * known suffixes.
1873 */
1874static int
1875is_inference_target(const char *s)
1876{
1877 struct name *np;
1878 struct rule *rp1, *rp2;
1879 struct depend *dp1, *dp2;
1880
1881 np = newname(".SUFFIXES");
1882 for (rp1 = np->n_rule; rp1; rp1 = rp1->r_next) {
1883 for (dp1 = rp1->r_dep; dp1; dp1 = dp1->d_next) {
1884 const char *suff1 = dp1->d_name->n_name;
1885 size_t len = strlen(suff1);
1886
1887 if (strncmp(s, suff1, len) == 0) {
1888 for (rp2 = np->n_rule; rp2; rp2 = rp2->r_next) {
1889 for (dp2 = rp2->r_dep; dp2; dp2 = dp2->d_next) {
1890 const char *suff2 = dp2->d_name->n_name;
1891 if (strcmp(s + len, suff2) == 0) {
1892 return TRUE;
1893 }
1894 }
1895 }
1806 } 1896 }
1807 } 1897 }
1808 } 1898 }
@@ -1824,7 +1914,6 @@ enum {
1824static int 1914static int
1825target_type(char *s) 1915target_type(char *s)
1826{ 1916{
1827 char *sfx;
1828 int ret; 1917 int ret;
1829 static const char *s_name = 1918 static const char *s_name =
1830 ".DEFAULT\0" 1919 ".DEFAULT\0"
@@ -1854,9 +1943,6 @@ target_type(char *s)
1854 T_SPECIAL, 1943 T_SPECIAL,
1855 }; 1944 };
1856 1945
1857 if (*s != '.')
1858 return T_NORMAL;
1859
1860 // Check for one of the known special targets 1946 // Check for one of the known special targets
1861 ret = index_in_strings(s_name, s); 1947 ret = index_in_strings(s_name, s);
1862 if (ret >= 0) 1948 if (ret >= 0)
@@ -1864,16 +1950,24 @@ target_type(char *s)
1864 1950
1865 // Check for an inference rule 1951 // Check for an inference rule
1866 ret = T_NORMAL; 1952 ret = T_NORMAL;
1867 sfx = suffix(s); 1953 if (!posix) {
1868 if (is_suffix(sfx)) { 1954 if (is_suffix(s) || is_inference_target(s)) {
1869 if (s == sfx) { // Single suffix rule
1870 ret = T_INFERENCE | T_NOPREREQ | T_COMMAND; 1955 ret = T_INFERENCE | T_NOPREREQ | T_COMMAND;
1871 } else { 1956 }
1872 // Suffix is valid, check that prefix is too 1957 } else
1873 *sfx = '\0'; 1958 {
1874 if (is_suffix(s)) 1959 // In POSIX inference rule targets must contain one or two dots
1960 char *sfx = suffix(s);
1961 if (*s == '.' && is_suffix(sfx)) {
1962 if (s == sfx) { // Single suffix rule
1875 ret = T_INFERENCE | T_NOPREREQ | T_COMMAND; 1963 ret = T_INFERENCE | T_NOPREREQ | T_COMMAND;
1876 *sfx = '.'; 1964 } else {
1965 // Suffix is valid, check that prefix is too
1966 *sfx = '\0';
1967 if (is_suffix(s))
1968 ret = T_INFERENCE | T_NOPREREQ | T_COMMAND;
1969 *sfx = '.';
1970 }
1877 } 1971 }
1878 } 1972 }
1879 return ret; 1973 return ret;
@@ -2587,7 +2681,7 @@ docmds(struct name *np, struct cmd *cp)
2587 2681
2588static int 2682static int
2589make1(struct name *np, struct cmd *cp, char *oodate, char *allsrc, 2683make1(struct name *np, struct cmd *cp, char *oodate, char *allsrc,
2590 char *dedup, struct name *implicit) 2684 char *dedup, struct name *implicit, const char *tsuff)
2591{ 2685{
2592 char *name, *member = NULL, *base = NULL, *prereq = NULL; 2686 char *name, *member = NULL, *base = NULL, *prereq = NULL;
2593 2687
@@ -2603,7 +2697,7 @@ make1(struct name *np, struct cmd *cp, char *oodate, char *allsrc,
2603 char *s; 2697 char *s;
2604 2698
2605 // As an extension, if we're not dealing with an implicit 2699 // As an extension, if we're not dealing with an implicit
2606 // rule set $< to the first out-of-date prerequisite. 2700 // prerequisite set $< to the first out-of-date prerequisite.
2607 if (implicit == NULL) { 2701 if (implicit == NULL) {
2608 if (oodate) { 2702 if (oodate) {
2609 s = strchr(oodate, ' '); 2703 s = strchr(oodate, ' ');
@@ -2614,16 +2708,43 @@ make1(struct name *np, struct cmd *cp, char *oodate, char *allsrc,
2614 } else 2708 } else
2615 prereq = implicit->n_name; 2709 prereq = implicit->n_name;
2616 2710
2617 base = member ? member : name; 2711 if (!posix && member == NULL) {
2618 s = suffix(base); 2712 // As an extension remove the suffix from a target, either
2619 // As an extension, if we're not dealing with an implicit 2713 // that obtained by an inference rule or one of the known
2620 // rule and the target ends with a known suffix, remove it 2714 // suffixes. Not for targets of the form lib.a(member.o).
2621 // and set $* to the stem, else to an empty string. 2715 if (tsuff != NULL) {
2622 if (implicit == NULL && !is_suffix(s)) 2716 base = has_suffix(name, tsuff);
2623 base = NULL; 2717 if (base) {
2624 else 2718 free(name);
2625 *s = '\0'; 2719 name = base;
2720 }
2721 } else {
2722 struct name *xp = newname(".SUFFIXES");
2723 for (struct rule *rp = xp->n_rule; rp; rp = rp->r_next) {
2724 for (struct depend *dp = rp->r_dep; dp; dp = dp->d_next) {
2725 base = has_suffix(name, dp->d_name->n_name);
2726 if (base) {
2727 free(name);
2728 name = base;
2729 goto done;
2730 }
2731 }
2732 }
2733 }
2734 } else
2735 {
2736 base = member ? member : name;
2737 s = suffix(base);
2738 // As an extension, if we're not dealing with an implicit
2739 // prerequisite and the target ends with a known suffix,
2740 // remove it and set $* to the stem, else to an empty string.
2741 if (implicit == NULL && !is_suffix(s))
2742 base = NULL;
2743 else
2744 *s = '\0';
2745 }
2626 } 2746 }
2747 done:
2627 setmacro("<", prereq, 0 | M_VALID); 2748 setmacro("<", prereq, 0 | M_VALID);
2628 setmacro("*", base, 0 | M_VALID); 2749 setmacro("*", base, 0 | M_VALID);
2629 free(name); 2750 free(name);
@@ -2667,11 +2788,12 @@ make(struct name *np, int level)
2667 struct depend *dp; 2788 struct depend *dp;
2668 struct rule *rp; 2789 struct rule *rp;
2669 struct name *impdep = NULL; // implicit prerequisite 2790 struct name *impdep = NULL; // implicit prerequisite
2670 struct rule imprule; 2791 struct rule infrule;
2671 struct cmd *sc_cmd = NULL; // commands for single-colon rule 2792 struct cmd *sc_cmd = NULL; // commands for single-colon rule
2672 char *oodate = NULL; 2793 char *oodate = NULL;
2673 char *allsrc = NULL; 2794 char *allsrc = NULL;
2674 char *dedup = NULL; 2795 char *dedup = NULL;
2796 const char *tsuff = NULL;
2675 struct timespec dtim = {1, 0}; 2797 struct timespec dtim = {1, 0};
2676 int estat = 0; 2798 int estat = 0;
2677 2799
@@ -2690,10 +2812,10 @@ make(struct name *np, int level)
2690 // as an extension, not for phony targets) 2812 // as an extension, not for phony targets)
2691 sc_cmd = getcmd(np); 2813 sc_cmd = getcmd(np);
2692 if (!sc_cmd && (posix || !(np->n_flag & N_PHONY))) { 2814 if (!sc_cmd && (posix || !(np->n_flag & N_PHONY))) {
2693 impdep = dyndep(np, &imprule); 2815 impdep = dyndep(np, &infrule, &tsuff);
2694 if (impdep) { 2816 if (impdep) {
2695 sc_cmd = imprule.r_cmd; 2817 sc_cmd = infrule.r_cmd;
2696 addrule(np, imprule.r_dep, NULL, FALSE); 2818 addrule(np, infrule.r_dep, NULL, FALSE);
2697 } 2819 }
2698 } 2820 }
2699 2821
@@ -2715,7 +2837,7 @@ make(struct name *np, int level)
2715 for (rp = np->n_rule; rp; rp = rp->r_next) { 2837 for (rp = np->n_rule; rp; rp = rp->r_next) {
2716 if (!rp->r_cmd) { 2838 if (!rp->r_cmd) {
2717 if (posix || !(np->n_flag & N_PHONY)) 2839 if (posix || !(np->n_flag & N_PHONY))
2718 impdep = dyndep(np, &imprule); 2840 impdep = dyndep(np, &infrule, &tsuff);
2719 if (!impdep) { 2841 if (!impdep) {
2720 if (doinclude) 2842 if (doinclude)
2721 return 1; 2843 return 1;
@@ -2743,9 +2865,9 @@ make(struct name *np, int level)
2743 // If the rule has no commands use the inference rule. 2865 // If the rule has no commands use the inference rule.
2744 if (!rp->r_cmd) { 2866 if (!rp->r_cmd) {
2745 locdep = impdep; 2867 locdep = impdep;
2746 imprule.r_dep->d_next = rp->r_dep; 2868 infrule.r_dep->d_next = rp->r_dep;
2747 rp->r_dep = imprule.r_dep; 2869 rp->r_dep = infrule.r_dep;
2748 rp->r_cmd = imprule.r_cmd; 2870 rp->r_cmd = infrule.r_cmd;
2749 } 2871 }
2750 // A rule with no prerequisities is executed unconditionally. 2872 // A rule with no prerequisities is executed unconditionally.
2751 if (!rp->r_dep) 2873 if (!rp->r_dep)
@@ -2776,7 +2898,7 @@ make(struct name *np, int level)
2776 if (((np->n_flag & N_PHONY) || timespec_le(&np->n_tim, &dtim))) { 2898 if (((np->n_flag & N_PHONY) || timespec_le(&np->n_tim, &dtim))) {
2777 if (!(estat & MAKE_FAILURE)) { 2899 if (!(estat & MAKE_FAILURE)) {
2778 estat |= make1(np, rp->r_cmd, oodate, allsrc, 2900 estat |= make1(np, rp->r_cmd, oodate, allsrc,
2779 dedup, locdep); 2901 dedup, locdep, tsuff);
2780 dtim = (struct timespec){1, 0}; 2902 dtim = (struct timespec){1, 0};
2781 } 2903 }
2782 free(oodate); 2904 free(oodate);
@@ -2792,7 +2914,7 @@ make(struct name *np, int level)
2792 } 2914 }
2793 } 2915 }
2794 if ((np->n_flag & N_DOUBLE) && impdep) 2916 if ((np->n_flag & N_DOUBLE) && impdep)
2795 free(imprule.r_dep); 2917 free(infrule.r_dep);
2796 2918
2797 np->n_flag |= N_DONE; 2919 np->n_flag |= N_DONE;
2798 np->n_flag &= ~N_DOING; 2920 np->n_flag &= ~N_DOING;
@@ -2801,7 +2923,8 @@ make(struct name *np, int level)
2801 ((np->n_flag & N_PHONY) || (timespec_le(&np->n_tim, &dtim)))) { 2923 ((np->n_flag & N_PHONY) || (timespec_le(&np->n_tim, &dtim)))) {
2802 if (!(estat & MAKE_FAILURE)) { 2924 if (!(estat & MAKE_FAILURE)) {
2803 if (sc_cmd) 2925 if (sc_cmd)
2804 estat |= make1(np, sc_cmd, oodate, allsrc, dedup, impdep); 2926 estat |= make1(np, sc_cmd, oodate, allsrc, dedup,
2927 impdep, tsuff);
2805 else if (!doinclude && level == 0 && !(estat & MAKE_DIDSOMETHING)) 2928 else if (!doinclude && level == 0 && !(estat & MAKE_DIDSOMETHING))
2806 warning("nothing to be done for %s", np->n_name); 2929 warning("nothing to be done for %s", np->n_name);
2807 } else if (!doinclude && !quest) { 2930 } else if (!doinclude && !quest) {
diff --git a/testsuite/make.tests b/testsuite/make.tests
index 376bdcc15..e3103c514 100755
--- a/testsuite/make.tests
+++ b/testsuite/make.tests
@@ -541,6 +541,36 @@ testing "make chained inference rules" \
541' 541'
542cd .. || exit 1; rm -rf make.tempdir 2>/dev/null 542cd .. || exit 1; rm -rf make.tempdir 2>/dev/null
543 543
544# Suffixes with multiple periods are supported
545mkdir make.tempdir && cd make.tempdir || exit 1
546touch x.c.c
547testing "make support multi-period suffixes" \
548 "make -f -" "cat x.c.c >x.o.o\nx\n" "" '
549.SUFFIXES: .c.c .o.o
550x.o.o:
551.c.c.o.o:
552 cat $< >$@
553 @echo $*
554'
555cd .. || exit 1; rm -rf make.tempdir 2>/dev/null
556
557# Suffixes with no periods are supported
558mkdir make.tempdir && cd make.tempdir || exit 1
559touch filex
560testing "make support suffixes without any periods" \
561 "make -f -" "cp filex fileh\nfile\ncp filex filez\nfile\n" "" '
562.SUFFIXES: x h z
563all: fileh filez
564fileh:
565filez: filex
566 cp filex filez
567 @echo $*
568xh:
569 cp $< $@
570 @echo $*
571'
572cd .. || exit 1; rm -rf make.tempdir 2>/dev/null
573
544# make supports *, ? and [] wildcards in targets and prerequisites 574# make supports *, ? and [] wildcards in targets and prerequisites
545mkdir make.tempdir && cd make.tempdir || exit 1 575mkdir make.tempdir && cd make.tempdir || exit 1
546touch -t 202206171201 t1a t2aa t3b 576touch -t 202206171201 t1a t2aa t3b