From 2b9dd215d588a1b9aa8eaa398e567414afaafce8 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Sat, 19 Mar 2016 17:26:08 -0400 Subject: [PATCH] * function.c (func_file): Support reading from files. * NEWS: Add information about reading files. * make.texi (File Function): Describe reading files. * tests/scripts/functions/file: Test new features for $(file ...) --- NEWS | 4 +++ doc/make.texi | 47 ++++++++++++++++----------- function.c | 62 +++++++++++++++++++++++++++++------- tests/scripts/functions/file | 43 +++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 29 deletions(-) diff --git a/NEWS b/NEWS index 3a62d694..a2db0109 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,10 @@ http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=106&set successful or not "0" if not successful. The variable value is unset if no != or $(shell ...) function has been invoked. +* The $(file ...) function can now read from a file with $(file } which indicates overwrite -mode, or @code{>>} which indicates append mode. The @var{filename} -indicates the file to be written to. There may optionally be +When the @code{file} function is evaluated all its arguments are +expanded first, then the file indicated by @var{filename} will be +opened in the mode described by @var{op}. + +The operator @var{op} can be @code{>} to indicate the file will be +overwritten with new content, @code{>>} to indicate the current +contents of the file will be appended to, or @code{<} to indicate the +contents of the file will be read in. The @var{filename} specifies +the file to be written to or read from. There may optionally be whitespace between the operator and the file name. -When the @code{file} function is expanded all its arguments are -expanded first, then the file indicated by @var{filename} will be -opened in the mode described by @var{op}. Finally @var{text} will be -written to the file. If @var{text} does not already end in a newline, -even if empty, a final newline will be written. If the @var{text} -argument is not given, nothing will be written. The result of -evaluating the @code{file} function is always the empty string. +When reading files, it is an error to provide a @var{text} value. -It is a fatal error if the file cannot be opened for writing, or if -the write operation fails. +When writing files, @var{text} will be written to the file. If +@var{text} does not already end in a newline a final newline will be +written (even if @var{text} is the empty string). If the @var{text} +argument is not given at all, nothing will be written. For example, the @code{file} function can be useful if your build system has a limited command line size and your recipe runs a command diff --git a/function.c b/function.c index 9f9f0fc3..a80f194f 100644 --- a/function.c +++ b/function.c @@ -2208,27 +2208,67 @@ func_file (char *o, char **argv, const char *funcname UNUSED) } fn = next_token (fn); - fp = fopen (fn, mode); + if (fn[0] == '\0') + O (fatal, *expanding_var, _("file: missing filename")); + + ENULLLOOP (fp, fopen (fn, mode)); if (fp == NULL) - { - const char *err = strerror (errno); - OSS (fatal, reading_file, _("open: %s: %s"), fn, err); - } + OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno)); + if (argv[1]) { int l = strlen (argv[1]); int nl = l == 0 || argv[1][l-1] != '\n'; if (fputs (argv[1], fp) == EOF || (nl && fputc ('\n', fp) == EOF)) - { - const char *err = strerror (errno); - OSS (fatal, reading_file, _("write: %s: %s"), fn, err); - } + OSS (fatal, reading_file, _("write: %s: %s"), fn, strerror (errno)); } - fclose (fp); + if (fclose (fp)) + OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno)); + } + else if (fn[0] == '<') + { + char *preo = o; + FILE *fp; + + fn = next_token (++fn); + if (fn[0] == '\0') + O (fatal, *expanding_var, _("file: missing filename")); + + if (argv[1]) + O (fatal, *expanding_var, _("file: too many arguments")); + + ENULLLOOP (fp, fopen (fn, "r")); + if (fp == NULL) + { + if (errno == ENOENT) + return o; + OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno)); + } + + while (1) + { + char buf[1024]; + size_t l = fread (buf, 1, sizeof (buf), fp); + if (l > 0) + o = variable_buffer_output (o, buf, l); + + if (ferror (fp)) + if (errno != EINTR) + OSS (fatal, reading_file, _("read: %s: %s"), fn, strerror (errno)); + if (feof (fp)) + break; + } + if (fclose (fp)) + OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno)); + + /* Remove trailing newline. */ + if (o > preo && o[-1] == '\n') + if (--o > preo && o[-1] == '\r') + --o; } else - OS (fatal, reading_file, _("Invalid file operation: %s"), fn); + OS (fatal, *expanding_var, _("file: invalid file operation: %s"), fn); return o; } diff --git a/tests/scripts/functions/file b/tests/scripts/functions/file index 55eb58a0..904db790 100644 --- a/tests/scripts/functions/file +++ b/tests/scripts/functions/file @@ -115,4 +115,47 @@ x:;@cat file.out unlink('file.out'); +# Reading files +run_make_test(q! +$(file >file.out,A = foo) +X1 := $(file >file.out,B = bar) +$(eval $(file )', '', + "#MAKEFILE#:1: *** file: missing filename. Stop.\n", 512); + +run_make_test('$(file >>)', '', + "#MAKEFILE#:1: *** file: missing filename. Stop.\n", 512); + +run_make_test('$(file <)', '', + "#MAKEFILE#:1: *** file: missing filename. Stop.\n", 512); + +# Bad call + +run_make_test('$(file foo)', '', + "#MAKEFILE#:1: *** file: invalid file operation: foo. Stop.\n", 512); + 1;