2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
8 * builtins.c - builtin jam rules
12 * load_builtin() - define builtin rules
16 * builtin_depends() - DEPENDS/INCLUDES rule
17 * builtin_echo() - ECHO rule
18 * builtin_exit() - EXIT rule
19 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
20 * builtin_glob() - GLOB rule
21 * builtin_match() - MATCH rule
22 * builtin_hdrmacro() - HDRMACRO rule
24 * 01/10/01 (seiwald) - split from compile.c
25 * 01/08/01 (seiwald) - new 'Glob' (file expansion) builtin
26 * 03/02/02 (seiwald) - new 'Match' (regexp match) builtin
27 * 04/03/02 (seiwald) - Glob matches only filename, not directory
28 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr()
29 * 10/22/02 (seiwald) - working return/break/continue statements
30 * 11/04/02 (seiwald) - const-ing for string literals
31 * 12/03/02 (seiwald) - fix odd includes support by grafting them onto depends
32 * 01/14/03 (seiwald) - fix includes fix with new internal includes TARGET
53 * compile_builtin() - define builtin rules
60 int glob (const char *s
, const char *c
);
63 void load_builtins (void) {
64 bindrule("Always")->procedure
=
65 bindrule("ALWAYS")->procedure
=
66 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TOUCHED
);
68 bindrule("Depends")->procedure
=
69 bindrule("DEPENDS")->procedure
=
70 parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 0);
72 bindrule("echo")->procedure
=
73 bindrule("Echo")->procedure
=
74 bindrule("ECHO")->procedure
=
75 parse_make(builtin_echo
, P0
, P0
, P0
, C0
, C0
, 0);
77 bindrule("exit")->procedure
=
78 bindrule("Exit")->procedure
=
79 bindrule("EXIT")->procedure
=
80 parse_make(builtin_exit
, P0
, P0
, P0
, C0
, C0
, 0);
82 bindrule("Glob")->procedure
=
83 bindrule("GLOB")->procedure
=
84 parse_make(builtin_glob
, P0
, P0
, P0
, C0
, C0
, 0);
86 bindrule("Includes")->procedure
=
87 bindrule("INCLUDES")->procedure
=
88 parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 1);
90 bindrule("Leaves")->procedure
=
91 bindrule("LEAVES")->procedure
=
92 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_LEAVES
);
94 bindrule("Match")->procedure
=
95 bindrule("MATCH")->procedure
=
96 parse_make(builtin_match
, P0
, P0
, P0
, C0
, C0
, 0);
98 bindrule("NoCare")->procedure
=
99 bindrule("NOCARE")->procedure
=
100 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOCARE
);
102 bindrule("NOTIME")->procedure
=
103 bindrule("NotFile")->procedure
=
104 bindrule("NOTFILE")->procedure
=
105 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOTFILE
);
107 bindrule("NoUpdate")->procedure
=
108 bindrule("NOUPDATE")->procedure
=
109 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOUPDATE
);
111 bindrule("Temporary")->procedure
=
112 bindrule("TEMPORARY")->procedure
=
113 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TEMP
);
115 bindrule("HdrMacro")->procedure
=
116 bindrule("HDRMACRO")->procedure
=
117 parse_make(builtin_hdrmacro
, P0
, P0
, P0
, C0
, C0
, 0);
119 bindrule("PWD")->procedure
=
120 bindrule("Pwd")->procedure
=
121 parse_make(builtin_pwd
, P0
, P0
, P0
, C0
, C0
, 0);
123 bindrule("SORT")->procedure
=
124 bindrule("Sort")->procedure
=
125 parse_make(builtin_sort
, P0
, P0
, P0
, C0
, C0
, 0);
127 bindrule("COMMAND")->procedure
=
128 bindrule("Command")->procedure
=
129 parse_make(builtin_command
, P0
, P0
, P0
, C0
, C0
, 0);
134 * builtin_depends() - DEPENDS/INCLUDES rule
136 * The DEPENDS builtin rule appends each of the listed sources on the
137 * dependency list of each of the listed targets. It binds both the
138 * targets and sources as TARGETs.
140 LIST
*builtin_depends (PARSE
*parse
, LOL
*args
, int *jmp
) {
141 LIST
*targets
= lol_get(args
, 0);
142 LIST
*sources
= lol_get(args
, 1);
143 /*k8: int which = parse->num;*/
146 for (l
= targets
; l
; l
= list_next(l
)) {
147 TARGET
*t
= bindtarget(l
->string
);
148 /* If doing INCLUDES, switch to the TARGET's include */
149 /* TARGET, creating it if needed. The internal include */
150 /* TARGET shares the name of its parent. */
152 if (!t
->includes
) t
->includes
= copytarget(t
);
155 t
->depends
= targetlist(t
->depends
, sources
);
162 * builtin_echo() - ECHO rule
164 * The ECHO builtin rule echoes the targets to the user. No other
167 LIST
* builtin_echo (PARSE
*parse
, LOL
*args
, int *jmp
) {
168 list_print(lol_get(args
, 0));
175 * builtin_exit() - EXIT rule
177 * The EXIT builtin rule echoes the targets to the user and exits
178 * the program with a failure status.
180 LIST
*builtin_exit (PARSE
*parse
, LOL
*args
, int *jmp
) {
181 list_print(lol_get(args
, 0));
183 exit(EXITBAD
); /* yeech */
189 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
191 * Builtin_flags() marks the target with the appropriate flag, for use
192 * by make0(). It binds each target as a TARGET.
194 LIST
*builtin_flags (PARSE
*parse
, LOL
*args
, int *jmp
) {
195 LIST
*l
= lol_get(args
, 0);
197 for (; l
; l
= list_next(l
)) bindtarget(l
->string
)->flags
|= parse
->num
;
203 * builtin_globbing() - GLOB rule
211 static void builtin_glob_back (void *closure
, const char *file
, int status
, time_t time
) {
212 struct globbing
*globbing
= (struct globbing
*)closure
;
217 /* Null out directory for matching. */
218 /* We wish we had file_dirscan() pass up a PATHNAME. */
219 path_parse(file
, &f
);
222 /* For globbing, we unconditionally ignore current and parent
223 directory items. Since those items always exist, there's no
224 reason why caller of GLOB would want to see them.
225 We could also change file_dirscan, but then paths with embedded
226 "." and ".." won't work anywhere.
228 /* k8: will this break anything? it shouldn't... */
229 if (!strcmp(f
.f_base
.ptr
, ".") || !strcmp(f
.f_base
.ptr
, "..")) return;
231 path_build(&f
, buf
, 0);
233 for (l
= globbing
->patterns
; l
; l
= l
->next
) {
234 if (!glob(l
->string
, buf
)) {
235 globbing
->results
= list_new(globbing
->results
, file
, 0);
242 LIST
*builtin_glob (PARSE
*parse
, LOL
*args
, int *jmp
) {
243 struct globbing globbing
;
244 LIST
*l
= lol_get(args
, 0);
245 LIST
*r
= lol_get(args
, 1);
247 globbing
.results
= L0
;
248 globbing
.patterns
= r
;
250 for (; l
; l
= list_next(l
)) file_dirscan(l
->string
, builtin_glob_back
, &globbing
);
252 return globbing
.results
;
257 * builtin_match() - MATCH rule, regexp matching
259 LIST
*builtin_match (PARSE
*parse
, LOL
*args
, int *jmp
) {
263 /* For each pattern */
264 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
265 regexp
*re
= regcomp(l
->string
);
266 /* For each string to match against */
267 for (r
= lol_get(args
, 1); r
; r
= r
->next
) {
268 if (regexec(re
, r
->string
)) {
270 /* Find highest parameter */
271 for (top
= NSUBEXP
; top
-- > 1;) if (re
->startp
[top
]) break;
272 /* And add all parameters up to highest onto list. */
273 /* Must have parameters to have results! */
274 for (i
= 1; i
<= top
; i
++) {
276 int l
= re
->endp
[i
]-re
->startp
[i
];
277 memcpy(buf
, re
->startp
[i
], l
);
279 result
= list_new(result
, buf
, 0);
290 LIST
*builtin_hdrmacro (PARSE
*parse
, LOL
*args
, int *jmp
) {
291 LIST
*l
= lol_get(args
, 0);
293 for (; l
; l
= list_next(l
)) {
294 TARGET
*t
= bindtarget(l
->string
);
295 /* scan file for header filename macro definitions */
296 if (DEBUG_HEADER
) printf("scanning '%s' for header file macro definitions\n", l
->string
);
304 /* backported from boost-jam */
306 * Return the current working directory.
308 * Usage: pwd = [ PWD ] ;
310 LIST
*builtin_pwd (PARSE
*parse
, LOL
*args
, int *jmp
) {
311 char pwd_buffer
[PATH_MAX
];
313 if (!getcwd(pwd_buffer
, sizeof(pwd_buffer
))) {
314 perror("can not get current directory");
317 return list_new(L0
, pwd_buffer
, 0);
322 /* backported from boost-jam */
323 LIST
*builtin_sort (PARSE
*parse
, LOL
*args
, int *jmp
) {
324 LIST
*arg
= lol_get(args
, 0);
325 /*printf("unsorted: "); list_print(arg); printf("\n");*/
326 arg
= list_sort(arg
);
327 /*printf("sorted: "); list_print(arg); printf("\n");*/
332 /* backported from boost-jam */
333 /* Command shcmd [[ : options ]] */
334 LIST
*builtin_command (PARSE
*parse
, LOL
*args
, int *jmp
) {
338 char buffer
[1024], buf1
[32], *spos
, *epos
;
341 int optExitStatus
= 0;
344 int optTrimRight
= 1;
345 int optStatus1st
= 0;
347 int optSpaceBreak
= 1;
354 /* build shell command */
357 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
358 if (str
.size
) KStringPushBack(&cmd
, ' ');
359 KStringAppend(&cmd
, l
->string
);
361 /* no shell command? */
362 if (!cmd
.size
) { KStringFree(&cmd
); return L0
; }
364 /* for each string in 2nd list: check for arg */
365 for (l
= lol_get(args
, 1); l
; l
= l
->next
) {
366 if (!strcmp("exit-status", l
->string
)) optExitStatus
= 1;
367 else if (!strcmp("exit-code", l
->string
)) optExitStatus
= 1;
368 else if (!strcmp("status-first", l
->string
)) optStatus1st
= 1;
369 else if (!strcmp("code-first", l
->string
)) optStatus1st
= 1;
370 else if (!strcmp("no-output", l
->string
)) optNoOutput
= 1;
371 else if (!strcmp("no-trim", l
->string
)) optTrimLeft
= optTrimRight
= 0;
372 else if (!strcmp("no-trim-left", l
->string
)) optTrimLeft
= 0;
373 else if (!strcmp("no-trim-right", l
->string
)) optTrimRight
= 0;
374 else if (!strcmp("parse-output", l
->string
)) optParseOut
= 1;
375 else if (!strcmp("no-space-break", l
->string
)) optSpaceBreak
= 0;
376 else if (!strcmp("no-tab-break", l
->string
)) optTabBreak
= 0;
377 else if (!strcmp("no-nl-break", l
->string
)) optCRBreak
= 0;
378 else if (!strcmp("no-cr-break", l
->string
)) optCRBreak
= 0;
379 else if (!strcmp("no-lf-break", l
->string
)) optLFBreak
= 0;
380 /* FIXME: don't ignore invalid options */
384 p
= popen(cmd
.value
, "r");
389 while ((ret
= fread(buffer
, sizeof(char), sizeof(buffer
)-1, p
)) > 0) {
392 KStringAppend(&str
, buffer
);
395 exitStatus
= pclose(p
);
397 if (optExitStatus
&& optStatus1st
) {
398 sprintf(buf1
, "%d", exitStatus
);
399 result
= list_new(result
, buf1
, 0);
402 /* trim output if necessary */
407 spos
= str
.value
+str
.size
-1;
408 while (spos
>= str
.value
&& *((unsigned char *)spos
) <= ' ') spos
--;
410 str
.size
= spos
-str
.value
;
414 while (*spos
&& *((unsigned char *)spos
) <= ' ') spos
++;
416 result
= list_new(result
, spos
, 0);
419 ret
= 0; /* was anything added? list must hasat least one element */
422 /* skip delimiters */
426 if (!optSpaceBreak
) break;
427 } else if (ch
== '\t') {
428 if (!optTabBreak
) break;
429 } else if (ch
== '\n') {
430 if (!optCRBreak
) break;
431 } else if (ch
== '\r') {
432 if (!optLFBreak
) break;
433 } else if ((unsigned char)ch
> ' ') break;
441 if (optSpaceBreak
) break;
442 } else if (ch
== '\t') {
443 if (optTabBreak
) break;
444 } else if (ch
== '\n') {
445 if (optCRBreak
) break;
446 } else if (ch
== '\r') {
447 if (optLFBreak
) break;
448 } else if ((unsigned char)ch
<= ' ') break;
452 KStringAppendRange(&cmd
, spos
, epos
);
453 result
= list_new(result
, cmd
.value
, 0);
455 ret
= 1; spos
= epos
;
457 if (!ret
) { buf1
[0] = '\0'; result
= list_new(result
, buf1
, 0); }
459 } else result
= list_new(result
, str
.value
, 0);
462 /* the command exit result next */
463 if (optExitStatus
&& !optStatus1st
) {
464 sprintf(buf1
, "%d", exitStatus
);
465 result
= list_new(result
, buf1
, 0);