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
54 * compile_builtin() - define builtin rules
61 int glob (const char *s
, const char *c
);
64 void load_builtins (void) {
65 bindrule("Always")->procedure
=
66 bindrule("ALWAYS")->procedure
=
67 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TOUCHED
);
69 bindrule("Depends")->procedure
=
70 bindrule("DEPENDS")->procedure
=
71 parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 0);
73 bindrule("echo")->procedure
=
74 bindrule("Echo")->procedure
=
75 bindrule("ECHO")->procedure
=
76 parse_make(builtin_echo
, P0
, P0
, P0
, C0
, C0
, 0);
78 bindrule("exit")->procedure
=
79 bindrule("Exit")->procedure
=
80 bindrule("EXIT")->procedure
=
81 parse_make(builtin_exit
, P0
, P0
, P0
, C0
, C0
, 0);
83 bindrule("Glob")->procedure
=
84 bindrule("GLOB")->procedure
=
85 parse_make(builtin_glob
, P0
, P0
, P0
, C0
, C0
, 0);
87 bindrule("Includes")->procedure
=
88 bindrule("INCLUDES")->procedure
=
89 parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 1);
91 bindrule("Leaves")->procedure
=
92 bindrule("LEAVES")->procedure
=
93 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_LEAVES
);
95 bindrule("Match")->procedure
=
96 bindrule("MATCH")->procedure
=
97 parse_make(builtin_match
, P0
, P0
, P0
, C0
, C0
, 0);
99 bindrule("NoCare")->procedure
=
100 bindrule("NOCARE")->procedure
=
101 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOCARE
);
103 bindrule("NOTIME")->procedure
=
104 bindrule("NotFile")->procedure
=
105 bindrule("NOTFILE")->procedure
=
106 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOTFILE
);
108 bindrule("NoUpdate")->procedure
=
109 bindrule("NOUPDATE")->procedure
=
110 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOUPDATE
);
112 bindrule("Temporary")->procedure
=
113 bindrule("TEMPORARY")->procedure
=
114 parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TEMP
);
116 bindrule("HdrMacro")->procedure
=
117 bindrule("HDRMACRO")->procedure
=
118 parse_make(builtin_hdrmacro
, P0
, P0
, P0
, C0
, C0
, 0);
120 bindrule("PWD")->procedure
=
121 bindrule("Pwd")->procedure
=
122 parse_make(builtin_pwd
, P0
, P0
, P0
, C0
, C0
, 0);
124 bindrule("SORT")->procedure
=
125 bindrule("Sort")->procedure
=
126 parse_make(builtin_sort
, P0
, P0
, P0
, C0
, C0
, 0);
128 bindrule("COMMAND")->procedure
=
129 bindrule("Command")->procedure
=
130 parse_make(builtin_command
, P0
, P0
, P0
, C0
, C0
, 0);
135 * builtin_depends() - DEPENDS/INCLUDES rule
137 * The DEPENDS builtin rule appends each of the listed sources on the
138 * dependency list of each of the listed targets. It binds both the
139 * targets and sources as TARGETs.
141 LIST
*builtin_depends (PARSE
*parse
, LOL
*args
, int *jmp
) {
142 LIST
*targets
= lol_get(args
, 0);
143 LIST
*sources
= lol_get(args
, 1);
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);
196 for (; l
; l
= list_next(l
)) bindtarget(l
->string
)->flags
|= parse
->num
;
202 * builtin_globbing() - GLOB rule
210 static void builtin_glob_back (void *closure
, const char *file
, int status
, time_t time
) {
211 struct globbing
*globbing
= (struct globbing
*)closure
;
216 /* Null out directory for matching. */
217 /* We wish we had file_dirscan() pass up a PATHNAME. */
218 path_parse(file
, &f
);
221 /* For globbing, we unconditionally ignore current and parent
222 directory items. Since those items always exist, there's no
223 reason why caller of GLOB would want to see them.
224 We could also change file_dirscan, but then paths with embedded
225 "." and ".." won't work anywhere.
227 /* k8: will this break anything? it shouldn't... */
228 if (!strcmp(f
.f_base
.ptr
, ".") || !strcmp(f
.f_base
.ptr
, "..")) return;
230 path_build(&f
, buf
, 0);
231 for (l
= globbing
->patterns
; l
; l
= l
->next
) {
232 if (!glob(l
->string
, buf
)) {
233 globbing
->results
= list_new(globbing
->results
, file
, 0);
240 LIST
*builtin_glob (PARSE
*parse
, LOL
*args
, int *jmp
) {
241 struct globbing globbing
;
242 LIST
*l
= lol_get(args
, 0);
243 LIST
*r
= lol_get(args
, 1);
245 globbing
.results
= L0
;
246 globbing
.patterns
= r
;
248 for (; l
; l
= list_next(l
)) file_dirscan(l
->string
, builtin_glob_back
, &globbing
);
250 return globbing
.results
;
255 * builtin_match() - MATCH rule, regexp matching
257 LIST
*builtin_match (PARSE
*parse
, LOL
*args
, int *jmp
) {
261 /* For each pattern */
262 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
263 regexp
*re
= regcomp(l
->string
);
264 /* For each string to match against */
265 for (r
= lol_get(args
, 1); r
; r
= r
->next
) {
266 if (regexec(re
, r
->string
)) {
268 /* Find highest parameter */
269 for (top
= NSUBEXP
; top
-- > 1;) if (re
->startp
[top
]) break;
270 /* And add all parameters up to highest onto list. */
271 /* Must have parameters to have results! */
272 for (i
= 1; i
<= top
; i
++) {
274 int l
= re
->endp
[i
]-re
->startp
[i
];
275 memcpy(buf
, re
->startp
[i
], l
);
277 result
= list_new(result
, buf
, 0);
288 LIST
*builtin_hdrmacro (PARSE
*parse
, LOL
*args
, int *jmp
) {
289 LIST
*l
= lol_get(args
, 0);
291 for (; l
; l
= list_next(l
)) {
292 TARGET
*t
= bindtarget(l
->string
);
293 /* scan file for header filename macro definitions */
294 if (DEBUG_HEADER
) printf("scanning '%s' for header file macro definitions\n", l
->string
);
302 /* backported from boost-jam */
304 * Return the current working directory.
306 * Usage: pwd = [ PWD ] ;
308 LIST
*builtin_pwd (PARSE
*parse
, LOL
*args
, int *jmp
) {
309 char pwd_buffer
[PATH_MAX
];
311 if (!getcwd(pwd_buffer
, sizeof(pwd_buffer
))) {
312 perror("can not get current directory");
315 return list_new(L0
, pwd_buffer
, 0);
320 /* backported from boost-jam */
321 LIST
*builtin_sort (PARSE
*parse
, LOL
*args
, int *jmp
) {
322 LIST
*arg
= lol_get(args
, 0);
323 /*printf("unsorted: "); list_print(arg); printf("\n");*/
324 arg
= list_sort(arg
);
325 /*printf("sorted: "); list_print(arg); printf("\n");*/
330 /* backported from boost-jam */
331 /* Command shcmd [[ : options ]] */
332 LIST
*builtin_command (PARSE
*parse
, LOL
*args
, int *jmp
) {
336 char buffer
[1024], buf1
[32], *spos
, *epos
;
339 int optExitStatus
= 0;
342 int optTrimRight
= 1;
343 int optStatus1st
= 0;
345 int optSpaceBreak
= 1;
352 /* build shell command */
355 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
356 if (str
.size
) kStringPushBack(&cmd
, ' ');
357 kStringAppend(&cmd
, l
->string
);
359 /* no shell command? */
360 if (!cmd
.size
) { kStringFree(&cmd
); return L0
; }
362 /* for each string in 2nd list: check for arg */
363 for (l
= lol_get(args
, 1); l
; l
= l
->next
) {
364 if (!strcmp("exit-status", l
->string
)) optExitStatus
= 1;
365 else if (!strcmp("exit-code", l
->string
)) optExitStatus
= 1;
366 else if (!strcmp("status-first", l
->string
)) optStatus1st
= 1;
367 else if (!strcmp("code-first", l
->string
)) optStatus1st
= 1;
368 else if (!strcmp("no-output", l
->string
)) optNoOutput
= 1;
369 else if (!strcmp("no-trim", l
->string
)) optTrimLeft
= optTrimRight
= 0;
370 else if (!strcmp("no-trim-left", l
->string
)) optTrimLeft
= 0;
371 else if (!strcmp("no-trim-right", l
->string
)) optTrimRight
= 0;
372 else if (!strcmp("parse-output", l
->string
)) optParseOut
= 1;
373 else if (!strcmp("no-space-break", l
->string
)) optSpaceBreak
= 0;
374 else if (!strcmp("no-tab-break", l
->string
)) optTabBreak
= 0;
375 else if (!strcmp("no-nl-break", l
->string
)) optLFBreak
= 0;
376 else if (!strcmp("no-lf-break", l
->string
)) optLFBreak
= 0;
377 else if (!strcmp("no-cr-break", l
->string
)) optCRBreak
= 0;
378 /* FIXME: don't ignore invalid options */
382 p
= popen(cmd
.value
, "r");
387 while ((ret
= fread(buffer
, sizeof(char), sizeof(buffer
)-1, p
)) > 0) {
390 kStringAppend(&str
, buffer
);
393 exitStatus
= pclose(p
);
395 if (optExitStatus
&& optStatus1st
) {
396 sprintf(buf1
, "%d", exitStatus
);
397 result
= list_new(result
, buf1
, 0);
400 /* trim output if necessary */
405 spos
= str
.value
+str
.size
-1;
406 while (spos
>= str
.value
&& *((unsigned char *)spos
) <= ' ') spos
--;
408 str
.size
= spos
-str
.value
;
411 if (optTrimLeft
) while (*spos
&& *((unsigned char *)spos
) <= ' ') spos
++;
412 result
= list_new(result
, spos
, 0);
415 ret
= 0; /* was anything added? list must has at least one element */
418 /* skip delimiters */
420 unsigned char ch
= (unsigned char)(*spos
);
422 if (!optSpaceBreak
) break;
423 } else if (ch
== '\t') {
424 if (!optTabBreak
) break;
425 } else if (ch
== '\r') {
426 if (!optCRBreak
) break;
427 } else if (ch
== '\n') {
428 if (!optLFBreak
) break;
429 } else if (ch
> ' ') break;
437 if (optSpaceBreak
) break;
438 } else if (ch
== '\t') {
439 if (optTabBreak
) break;
440 } else if (ch
== '\r') {
441 if (optCRBreak
) break;
442 } else if (ch
== '\n') {
443 if (optLFBreak
) break;
444 } else if ((unsigned char)ch
<= ' ') break;
448 kStringAppendRange(&cmd
, spos
, epos
);
449 result
= list_new(result
, cmd
.value
, 0);
451 ret
= 1; spos
= epos
;
453 if (!ret
) { buf1
[0] = '\0'; result
= list_new(result
, buf1
, 0); }
455 } else result
= list_new(result
, str
.value
, 0);
458 /* the command exit result next */
459 if (optExitStatus
&& !optStatus1st
) {
460 sprintf(buf1
, "%d", exitStatus
);
461 result
= list_new(result
, buf1
, 0);