- New code capability: a read-only string cache. Start of solution for

Savannah bug #15182, but not much uses it yet.  Coming shortly.
- Added short-circuiting $(and ..) and $(or ...) functions.
This commit is contained in:
Paul Smith 2006-02-10 05:29:00 +00:00
parent d0c4e92f11
commit 5a7a42cfce
15 changed files with 530 additions and 67 deletions

View File

@ -1,3 +1,28 @@
2006-02-10 Paul D. Smith <psmith@gnu.org>
A new internal capability: the string cache is a read-only cache
of strings, with a hash table interface for fast lookup. Nothing
in the cache will ever be freed, so there's no need for reference
counting, etc. This is the beginning of a full solution for
Savannah bug #15182, but for now we only store makefile names here.
* strcache.c: New file. Implement a read-only string cache.
* make.h: Add prototypes for new functions.
* main.c (initialize_global_hash_tables): Initialize the string cache.
(print_data_base): Print string cache stats.
* read.c (eval_makefile): Use the string cache to store makefile
names. Rewrite the string allocation to be sure we free everything.
2006-02-09 Paul D. Smith <psmith@gnu.org>
* function.c (func_or): Implement a short-circuiting OR function.
(func_and): Implement a short-circuiting AND function.
(function_table_init): Update the table with the new functions.
* doc/make.texi (Conditional Functions): Changed the "if" section
to one on general conditional functions. Added documentation for
$(and ...) and $(or ...) functions.
* NEWS: Note new $(and ...) and $(or ...) functions.
2006-02-08 Boris Kolpackov <boris@kolpackov.net>
* job.h (struct child): Add dontcare bitfield.

View File

@ -24,7 +24,7 @@ endif
make_SOURCES = ar.c arscan.c commands.c default.c dir.c expand.c file.c \
function.c getopt.c getopt1.c implicit.c job.c main.c \
misc.c read.c remake.c $(remote) rule.c signame.c \
variable.c version.c vpath.c hash.c
strcache.c variable.c version.c vpath.c hash.c
EXTRA_make_SOURCES = vmsjobs.c remote-stub.c remote-cstms.c

8
NEWS
View File

@ -95,6 +95,14 @@ Version 3.81beta4
- $(info ...) prints its arguments to stdout. No makefile name or
line number info, etc. is printed.
- $(flavor ...) returns the flavor of a variable.
- $(or ...) provides a short-circuiting OR conditional: each argument
is expanded. The first true (non-empty) argument is returned; no
further arguments are expanded. Expands to empty if there are no
true arguments.
- $(and ...) provides a short-circuiting AND conditional: each
argument is expanded. The first false (empty) argument is
returned; no further arguments are expanded. Expands to the last
argument if all arguments are true.
* Changes made for POSIX compatibility:
- Only touch targets (under -t) if they have at least one command.

View File

@ -224,6 +224,16 @@ Writing the Commands in Rules
* Sequences:: Defining canned sequences of commands.
* Empty Commands:: Defining useful, do-nothing commands.
Command Syntax
* Splitting Lines:: Breaking long command lines for readability.
* Variables in Commands:: Using @code{make} variables in commands.
Command Execution
* Choosing the Shell:: How @code{make} chooses the shell used
to run commands.
Recursive Use of @code{make}
* MAKE Variable:: The special effects of using @samp{$(MAKE)}.
@ -268,8 +278,8 @@ Functions for Transforming Text
* Syntax of Functions:: How to write a function call.
* Text Functions:: General-purpose text manipulation functions.
* File Name Functions:: Functions for manipulating file names.
* Conditional Functions:: Functions that implement conditions.
* Foreach Function:: Repeat some text with controlled variation.
* If Function:: Conditionally expand a value.
* Call Function:: Expand a user-defined function.
* Value Function:: Return the un-expanded value of a variable.
* Eval Function:: Evaluate the arguments as makefile syntax.
@ -6199,8 +6209,8 @@ call, just as a variable might be substituted.
* Syntax of Functions:: How to write a function call.
* Text Functions:: General-purpose text manipulation functions.
* File Name Functions:: Functions for manipulating file names.
* Conditional Functions:: Functions that implement conditions.
* Foreach Function:: Repeat some text with controlled variation.
* If Function:: Conditionally expand a value.
* Call Function:: Expand a user-defined function.
* Value Function:: Return the un-expanded value of a variable.
* Eval Function:: Evaluate the arguments as makefile syntax.
@ -6620,7 +6630,7 @@ used so that the new value is assigned even if the previous value of
@code{CFLAGS} was specified with a command argument (@pxref{Override
Directive, , The @code{override} Directive}).
@node File Name Functions, Foreach Function, Text Functions, Functions
@node File Name Functions, Conditional Functions, Text Functions, Functions
@section Functions for File Names
@cindex functions, for file names
@cindex file name functions
@ -6796,7 +6806,60 @@ the file names to refer to an existing file or directory. Use the
@code{wildcard} function to test for existence.
@end table
@node Foreach Function, If Function, File Name Functions, Functions
@node Conditional Functions, Foreach Function, File Name Functions, Functions
@section Functions for Conditionals
@findex if
@cindex conditional expansion
There are three functions that provide conditional expansion. A key
aspect of these functions is that not all of the arguments are
expanded initially. Only those arguments which need to be expanded,
will be expanded.
@table @code
@item $(if @var{condition},@var{then-part}[,@var{else-part}])
@findex if
The @code{if} function provides support for conditional expansion in a
functional context (as opposed to the GNU @code{make} makefile
conditionals such as @code{ifeq} (@pxref{Conditional Syntax, ,Syntax of
Conditionals}).
The first argument, @var{condition}, first has all preceding and
trailing whitespace stripped, then is expanded. If it expands to any
non-empty string, then the condition is considered to be true. If it
expands to an empty string, the condition is considered to be false.
If the condition is true then the second argument, @var{then-part}, is
evaluated and this is used as the result of the evaluation of the entire
@code{if} function.
If the condition is false then the third argument, @var{else-part}, is
evaluated and this is the result of the @code{if} function. If there is
no third argument, the @code{if} function evaluates to nothing (the
empty string).
Note that only one of the @var{then-part} or the @var{else-part} will be
evaluated, never both. Thus, either can contain side-effects (such as
@code{shell} function calls, etc.)
@item $(or @var{condition1}[,@var{condition2}[,@var{condition3}@dots{}]])
@findex or
The @code{or} function provides a ``short-circuiting'' OR operation.
Each argument is expanded, in order. If an argument expands to a
non-empty string the processing stops and the result of the expansion
is that string. If, after all arguments are expanded, all of them are
false (empty), then the result of the expansion is the empty string.
@item $(and @var{condition1}[,@var{condition2}[,@var{condition3}@dots{}]])
@findex and
The @code{and} function provides a ``short-circuiting'' AND operation.
Each argument is expanded, in order. If an argument expands to an
empty string the processing stops and the result of the expansion is
the empty string. If all arguments expand to a non-empty string then
the result of the expansion is the expansion of the last argument.
@end table
@node Foreach Function, Call Function, Conditional Functions, Functions
@section The @code{foreach} Function
@findex foreach
@cindex words, iterating over
@ -6884,41 +6947,7 @@ might be useful if the value of @code{find_files} references the variable
whose name is @samp{Esta escrito en espanol!} (es un nombre bastante largo,
no?), but it is more likely to be a mistake.
@node If Function, Call Function, Foreach Function, Functions
@section The @code{if} Function
@findex if
@cindex conditional expansion
The @code{if} function provides support for conditional expansion in a
functional context (as opposed to the GNU @code{make} makefile
conditionals such as @code{ifeq} (@pxref{Conditional Syntax, ,Syntax of
Conditionals}).
An @code{if} function call can contain either two or three arguments:
@example
$(if @var{condition},@var{then-part}[,@var{else-part}])
@end example
The first argument, @var{condition}, first has all preceding and
trailing whitespace stripped, then is expanded. If it expands to any
non-empty string, then the condition is considered to be true. If it
expands to an empty string, the condition is considered to be false.
If the condition is true then the second argument, @var{then-part}, is
evaluated and this is used as the result of the evaluation of the entire
@code{if} function.
If the condition is false then the third argument, @var{else-part}, is
evaluated and this is the result of the @code{if} function. If there is
no third argument, the @code{if} function evaluates to nothing (the
empty string).
Note that only one of the @var{then-part} or the @var{else-part} will be
evaluated, never both. Thus, either can contain side-effects (such as
@code{shell} function calls, etc.)
@node Call Function, Value Function, If Function, Functions
@node Call Function, Value Function, Foreach Function, Functions
@section The @code{call} Function
@findex call
@cindex functions, user defined

View File

@ -1230,6 +1230,110 @@ func_if (char *o, char **argv, const char *funcname UNUSED)
return o;
}
/*
$(or condition1[,condition2[,condition3[...]]])
A CONDITION is false iff it evaluates to an empty string. White
space before and after CONDITION are stripped before evaluation.
CONDITION1 is evaluated. If it's true, then this is the result of
expansion. If it's false, CONDITION2 is evaluated, and so on. If none of
the conditions are true, the expansion is the empty string.
Once a CONDITION is true no further conditions are evaluated
(short-circuiting).
*/
static char *
func_or (char *o, char **argv, const char *funcname UNUSED)
{
for ( ; *argv ; ++argv)
{
const char *begp = *argv;
const char *endp = begp + strlen (*argv) - 1;
char *expansion;
int result = 0;
/* Find the result of the condition: if it's false keep going. */
strip_whitespace (&begp, &endp);
if (begp > endp)
continue;
expansion = expand_argument (begp, endp+1);
result = strlen (expansion);
/* If the result is false keep going. */
if (!result)
{
free (expansion);
continue;
}
/* It's true! Keep this result and return. */
o = variable_buffer_output (o, expansion, result);
free (expansion);
break;
}
return o;
}
/*
$(and condition1[,condition2[,condition3[...]]])
A CONDITION is false iff it evaluates to an empty string. White
space before and after CONDITION are stripped before evaluation.
CONDITION1 is evaluated. If it's false, then this is the result of
expansion. If it's true, CONDITION2 is evaluated, and so on. If all of
the conditions are true, the expansion is the result of the last condition.
Once a CONDITION is false no further conditions are evaluated
(short-circuiting).
*/
static char *
func_and (char *o, char **argv, const char *funcname UNUSED)
{
char *expansion;
int result;
while (1)
{
const char *begp = *argv;
const char *endp = begp + strlen (*argv) - 1;
/* An empty condition is always false. */
strip_whitespace (&begp, &endp);
if (begp > endp)
return o;
expansion = expand_argument (begp, endp+1);
result = strlen (expansion);
/* If the result is false, stop here: we're done. */
if (!result)
break;
/* Otherwise the result is true. If this is the last one, keep this
result and quit. Otherwise go on to the next one! */
if (*(++argv))
free (expansion);
else
{
o = variable_buffer_output (o, expansion, result);
break;
}
}
free (expansion);
return o;
}
static char *
func_wildcard (char *o, char **argv, const char *funcname UNUSED)
{
@ -1977,6 +2081,8 @@ static struct function_table_entry function_table_init[] =
{ STRING_SIZE_TUPLE("error"), 0, 1, 1, func_error},
{ STRING_SIZE_TUPLE("warning"), 0, 1, 1, func_error},
{ STRING_SIZE_TUPLE("if"), 2, 3, 0, func_if},
{ STRING_SIZE_TUPLE("or"), 1, 0, 0, func_or},
{ STRING_SIZE_TUPLE("and"), 1, 0, 0, func_and},
{ STRING_SIZE_TUPLE("value"), 0, 1, 1, func_value},
{ STRING_SIZE_TUPLE("eval"), 0, 1, 1, func_eval},
#ifdef EXPERIMENTAL

10
hash.c
View File

@ -126,18 +126,18 @@ hash_find_item (struct hash_table *ht, const void *key)
}
void *
hash_insert (struct hash_table *ht, void *item)
hash_insert (struct hash_table *ht, const void *item)
{
void **slot = hash_find_slot (ht, item);
void *old_item = slot ? *slot : 0;
const void *old_item = slot ? *slot : 0;
hash_insert_at (ht, item, slot);
return ((HASH_VACANT (old_item)) ? 0 : old_item);
return (void *)((HASH_VACANT (old_item)) ? 0 : old_item);
}
void *
hash_insert_at (struct hash_table *ht, void *item, const void *slot)
hash_insert_at (struct hash_table *ht, const void *item, const void *slot)
{
void *old_item = *(void **) slot;
const void *old_item = *(void **) slot;
if (HASH_VACANT (old_item))
{
ht->ht_fill++;

4
hash.h
View File

@ -63,8 +63,8 @@ void hash_load __P((struct hash_table *ht, void *item_table,
unsigned long cardinality, unsigned long size));
void **hash_find_slot __P((struct hash_table *ht, void const *key));
void *hash_find_item __P((struct hash_table *ht, void const *key));
void *hash_insert __P((struct hash_table *ht, void *item));
void *hash_insert_at __P((struct hash_table *ht, void *item, void const *slot));
void *hash_insert __P((struct hash_table *ht, const void *item));
void *hash_insert_at __P((struct hash_table *ht, const void *item, void const *slot));
void *hash_delete __P((struct hash_table *ht, void const *item));
void *hash_delete_at __P((struct hash_table *ht, void const *slot));
void hash_delete_items __P((struct hash_table *ht));

2
main.c
View File

@ -537,6 +537,7 @@ static void
initialize_global_hash_tables (void)
{
init_hash_global_variable_set ();
strcache_init ();
init_hash_files ();
hash_init_directories ();
hash_init_function_table ();
@ -2974,6 +2975,7 @@ print_data_base (void)
print_rule_data_base ();
print_file_data_base ();
print_vpath_data_base ();
strcache_print_stats ("#");
when = time ((time_t *) 0);
printf (_("\n# Finished Make data base on %s\n"), ctime (&when));

9
make.h
View File

@ -382,7 +382,7 @@ extern int unixy_shell;
struct floc
{
char *filenm;
const char *filenm;
unsigned long lineno;
};
#define NILF ((struct floc *)0)
@ -465,6 +465,13 @@ extern void close_stdout PARAMS ((void));
extern char *strip_whitespace PARAMS ((const char **begpp, const char **endpp));
/* String caching */
extern void strcache_init PARAMS ((void));
extern void strcache_print_stats PARAMS ((const char *prefix));
extern int strcache_iscached PARAMS ((const char *str));
extern const char *strcache_add PARAMS ((const char *str));
extern const char *strcache_add_len PARAMS ((const char *str, int len));
extern int strcache_setbufsize PARAMS ((int size));
#ifdef HAVE_VFORK_H
# include <vfork.h>

27
read.c
View File

@ -311,10 +311,12 @@ eval_makefile (char *filename, int flags)
struct dep *deps;
struct ebuffer ebuf;
const struct floc *curfile;
char *expanded = 0;
char *included = 0;
int makefile_errno;
int r;
ebuf.floc.filenm = filename;
ebuf.floc.filenm = strcache_add (filename);
ebuf.floc.lineno = 1;
if (ISDB (DB_VERBOSE))
@ -337,7 +339,7 @@ eval_makefile (char *filename, int flags)
in which case it was already done. */
if (!(flags & RM_NO_TILDE) && filename[0] == '~')
{
char *expanded = tilde_expand (filename);
expanded = tilde_expand (filename);
if (expanded != 0)
filename = expanded;
}
@ -354,16 +356,18 @@ eval_makefile (char *filename, int flags)
register unsigned int i;
for (i = 0; include_directories[i] != 0; ++i)
{
char *name = concat (include_directories[i], "/", filename);
ebuf.fp = fopen (name, "r");
if (ebuf.fp == 0)
free (name);
else
included = concat (include_directories[i], "/", filename);
ebuf.fp = fopen (included, "r");
if (ebuf.fp)
{
filename = name;
filename = included;
break;
}
free (included);
}
/* If we're not using it, we already freed it above. */
if (filename != included)
included = 0;
}
/* Add FILENAME to the chain of read makefiles. */
@ -374,8 +378,6 @@ eval_makefile (char *filename, int flags)
deps->file = lookup_file (filename);
if (deps->file == 0)
deps->file = enter_file (xstrdup (filename));
if (filename != ebuf.floc.filenm)
free (filename);
filename = deps->file->name;
deps->changed = flags;
deps->ignore_mtime = 0;
@ -384,6 +386,11 @@ eval_makefile (char *filename, int flags)
if (flags & RM_DONTCARE)
deps->file->dontcare = 1;
if (expanded)
free (expanded);
if (included)
free (included);
/* If the makefile can't be found at all, give up entirely. */
if (ebuf.fp == 0)

219
strcache.c Normal file
View File

@ -0,0 +1,219 @@
/* Constant string caching for GNU Make.
Copyright (C) 2006 Free Software Foundation, Inc.
This file is part of GNU Make.
GNU Make is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU Make is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Make; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
Boston, MA 02110-1301 USA. */
#include "make.h"
#include <assert.h>
#include "hash.h"
/* The size (in bytes) of each cache buffer. */
#define CACHE_BUFFER_SIZE (4096)
/* A string cached here will never be freed, so we don't need to worry about
reference counting. We just store the string, and then remember it in a
hash so it can be looked up again. */
struct strcache {
struct strcache *next; /* The next block of strings. */
char *end; /* Pointer to the beginning of the free space. */
int count; /* # of strings in this buffer (for stats). */
int bytesfree; /* The amount of the buffer that is free. */
char buffer[1]; /* The buffer comes after this. */
};
static int bufsize = CACHE_BUFFER_SIZE;
static struct strcache *strcache = NULL;
static struct strcache *
new_cache()
{
struct strcache *new;
new = (struct strcache *) xmalloc (sizeof (*new) + bufsize);
new->end = new->buffer;
new->count = 0;
new->bytesfree = bufsize;
new->next = strcache;
strcache = new;
return new;
}
static const char *
add_string(const char *str, int len)
{
struct strcache *best = NULL;
struct strcache *sp;
const char *res;
/* If the string we want is too large to fit into a single buffer, then we're
screwed; nothing will ever fit! Change the maximum size of the cache to
be big enough. */
if (len > bufsize)
bufsize = len * 2;
/* First, find a cache with enough free space. We always look through all
the blocks and choose the one with the best fit (the one that leaves the
least amount of space free). */
for (sp = strcache; sp != NULL; sp = sp->next)
if (sp->bytesfree > len && (!best || best->bytesfree > sp->bytesfree))
best = sp;
/* If nothing is big enough, make a new cache. */
if (!best)
best = new_cache();
assert (best->bytesfree > len);
/* Add the string to the best cache. */
res = best->end;
memcpy (best->end, str, len);
best->end += len;
*(best->end++) = '\0';
best->bytesfree -= len + 1;
++best->count;
return res;
}
/* Hash table of strings in the cache. */
static unsigned long
str_hash_1 (const void *key)
{
return_ISTRING_HASH_1 ((const char *) key);
}
static unsigned long
str_hash_2 (const void *key)
{
return_ISTRING_HASH_2 ((const char *) key);
}
static int
str_hash_cmp (const void *x, const void *y)
{
return_ISTRING_COMPARE ((const char *) x, (const char *) y);
}
static struct hash_table strings;
static const char *
add_hash (const char *str, int len)
{
/* Look up the string in the hash. If it's there, return it. */
char **slot = (char **) hash_find_slot (&strings, str);
const char *key = *slot;
if (!HASH_VACANT (key))
return key;
/* Not there yet so add it to a buffer, then into the hash table. */
key = add_string (str, len);
hash_insert_at (&strings, key, slot);
return key;
}
/* Returns true if the string is in the cache; false if not. */
int
strcache_iscached (const char *str)
{
struct strcache *sp;
for (sp = strcache; sp != 0; sp = sp->next)
if (str >= sp->buffer && str < sp->end)
return 1;
return 0;
}
/* If the string is already in the cache, return a pointer to the cached
version. If not, add it then return a pointer to the cached version.
Note we do NOT take control of the string passed in. */
const char *
strcache_add (const char *str)
{
return add_hash (str, strlen (str));
}
const char *
strcache_add_len (const char *str, int len)
{
char *key = alloca (len + 1);
memcpy (key, str, len);
key[len] = '\0';
return add_hash (key, len);
}
int
strcache_setbufsize(int size)
{
if (size > bufsize)
bufsize = size;
return bufsize;
}
void
strcache_init (void)
{
hash_init (&strings, 1000, str_hash_1, str_hash_2, str_hash_cmp);
}
/* Generate some stats output. */
void
strcache_print_stats (const char *prefix)
{
int numbuffs = 0, numstrs = 0;
int totsize = 0, avgsize, maxsize = 0, minsize = bufsize;
int totfree = 0, avgfree, maxfree = 0, minfree = bufsize;
const struct strcache *sp;
for (sp = strcache; sp != NULL; sp = sp->next)
{
int bf = sp->bytesfree;
int sz = (sp->end - sp->buffer) + bf;
++numbuffs;
numstrs += sp->count;
totsize += sz;
maxsize = (sz > maxsize ? sz : maxsize);
minsize = (sz < minsize ? sz : minsize);
totfree += bf;
maxfree = (bf > maxfree ? bf : maxfree);
minfree = (bf < minfree ? bf : minfree);
}
avgsize = numbuffs ? (int)(totsize / numbuffs) : 0;
avgfree = numbuffs ? (int)(totfree / numbuffs) : 0;
printf ("\n%s # of strings in strcache: %d\n", prefix, numstrs);
printf ("%s # of strcache buffers: %d\n", prefix, numbuffs);
printf ("%s strcache size: total = %d / max = %d / min = %d / avg = %d\n",
prefix, totsize, maxsize, minsize, avgsize);
printf ("%s strcache free: total = %d / max = %d / min = %d / avg = %d\n",
prefix, totfree, maxfree, minfree, avgfree);
}

View File

@ -1,3 +1,13 @@
2006-02-09 Paul D. Smith <psmith@gnu.org>
* run_make_tests.pl (set_more_defaults): Update valgrind support
for newer versions.
* test_driver.pl (toplevel): Skip all hidden files/directories (ones
beginning with ".").
* scripts/functions/andor: Tests for $(and ..) and $(or ...)
functions.
2006-02-08 Boris Kolpackov <boris@kolpackov.net>
* scripts/features/parallelism: Add a test for bug #15641.
@ -471,9 +481,8 @@
2002-07-11 Paul D. Smith <psmith@gnu.org>
* run_make_tests.pl (valid_option): Add support for Valgrind
<http://developer.kde.org/~sewardj/>. Use -valgrind option to the
test suite.
* run_make_tests.pl (valid_option): Add support for Valgrind. Use
-valgrind option to the test suite.
(set_more_defaults): Set up the file descriptor to capture
Valgrind output. We have to unset its close-on-exec flag; we
hardcode the value for F_SETFD (2) rather than load it; hopefully

View File

@ -12,6 +12,7 @@
# (and others)
$valgrind = 0; # invoke make with valgrind
$valgrind_args = '--num-callers=15 --tool=memcheck --leak-check=full';
$pure_log = undef;
require "test_driver.pl";
@ -314,7 +315,8 @@ sub set_more_defaults
open(VALGRIND, "> valgrind.out")
|| die "Cannot open valgrind.out: $!\n";
# -q --leak-check=yes
$make_path = "valgrind --num-callers=15 --logfile-fd=".fileno(VALGRIND)." $make_path";
exists $ENV{VALGRIND_ARGS} and $valgrind_args = $ENV{VALGRIND_ARGS};
$make_path = "valgrind --log-fd=".fileno(VALGRIND)." $valgrind_args $make_path";
# F_SETFD is 2
fcntl(VALGRIND, 2, 0) or die "fcntl(setfd) failed: $!\n";
system("echo Starting on `date` 1>&".fileno(VALGRIND));

View File

@ -0,0 +1,50 @@
# -*-perl-*-
$description = "Test the and & or functions.\n";
$details = "Try various uses of and & or to ensure they all give the correct
results.\n";
# TEST #0
# For $(and ...), it will either be empty or the last value
run_make_test('
NEQ = $(subst $1,,$2)
f =
t = true
all:
@echo 1 $(and ,$t)
@echo 2 $(and $t)
@echo 3 $(and $t,)
@echo 4 $(and z,true,$f,false)
@echo 5 $(and $t,$f,$(info bad short-circuit))
@echo 6 $(and $(call NEQ,a,b),true)
@echo 7 $(and $(call NEQ,a,a),true)
@echo 8 $(and z,true,fal,se) hi
@echo 9 $(and ,true,fal,se)there
@echo 10 $(and $(e) ,$t)',
'',
"1\n2 true\n3\n4\n5\n6 true\n7\n8 se hi\n9 there\n10\n");
# TEST #1
# For $(or ...), it will either be empty or the first true value
run_make_test('
NEQ = $(subst $1,,$2)
f =
t = true
all:
@echo 1 $(or , )
@echo 2 $(or $t)
@echo 3 $(or ,$t)
@echo 4 $(or z,true,$f,false)
@echo 5 $(or $t,$(info bad short-circuit))
@echo 6 $(or $(info short-circuit),$t)
@echo 7 $(or $(call NEQ,a,b),true)
@echo 8 $(or $(call NEQ,a,a),true)
@echo 9 $(or z,true,fal,se) hi
@echo 10 $(or ,true,fal,se)there
@echo 11 $(or $(e) ,$f)',
'',
"short-circuit\n1\n2 true\n3 true\n4 z\n5 true\n6 true\n7 b\n8 true\n9 z hi\n10 truethere\n11\n");
1;

View File

@ -148,22 +148,21 @@ sub toplevel
print "Finding tests...\n";
opendir (SCRIPTDIR, $scriptpath)
|| &error ("Couldn't opendir $scriptpath: $!\n");
@dirs = grep (!/^(\.\.?|CVS|RCS)$/, readdir (SCRIPTDIR) );
@dirs = grep (!/^(\..*|CVS|RCS)$/, readdir (SCRIPTDIR) );
closedir (SCRIPTDIR);
foreach $dir (@dirs)
{
next if ($dir =~ /^\.\.?$/ || $dir eq 'CVS' || $dir eq 'RCS'
|| ! -d "$scriptpath/$dir");
next if ($dir =~ /^(\..*|CVS|RCS)$/ || ! -d "$scriptpath/$dir");
push (@rmdirs, $dir);
mkdir ("$workpath/$dir", 0777)
|| &error ("Couldn't mkdir $workpath/$dir: $!\n");
opendir (SCRIPTDIR, "$scriptpath/$dir")
|| &error ("Couldn't opendir $scriptpath/$dir: $!\n");
@files = grep (!/^(\.\.?|CVS|RCS)$/, readdir (SCRIPTDIR) );
@files = grep (!/^(\..*|CVS|RCS|.*~)$/, readdir (SCRIPTDIR) );
closedir (SCRIPTDIR);
foreach $test (@files)
{
next if $test =~ /~$/ || -d $test;
-d $test and next;
push (@TESTS, "$dir/$test");
}
}