public inbox for gentoo-dev@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-dev] Thoughts on try()
@ 2001-08-08  7:43 Aron Griffis
  2001-08-08 11:00 ` Daniel Robbins
  2001-08-08 15:06 ` Aron Griffis
  0 siblings, 2 replies; 4+ messages in thread
From: Aron Griffis @ 2001-08-08  7:43 UTC (permalink / raw
  To: gentoo-dev

Hello,

This email follows a discussion on #gentoo yesterday.  I'll start with
an executive summary of what follows since this may become long-winded.
Feedback and comments are welcome.

Summary
-------
  try() (in ebuild.sh) attempts to provide ebuilds with an easy
  mechanism for aborting the build in case a single step fails.  Its
  arguments need to be interpreted as a bash command, including
  overriding environment variables, bash builtins, and external
  programs.  Quoting needs to be preserved.

  To accomplish this, it needs to use either bash "eval" or the external
  program "env".  However each of these methods has shortcomings, and
  neither handles pipelines or redirection well.  As a result, I propose
  an alternate method that is more suited to bash syntax: die() and
  assert_true_or_die().

Defining the Problem
--------------------
  Ebuild authors need an easy method for "throwing an exception" in an
  ebuild so that ebuilds can be robust.  Non-zero exit status from
  a command needs to detected, a standard error message given to the
  user, and the build aborted.

  Constraints:

    (1) The "command" should be written as a normal bash command, with
	normal quoting, etc.  In other words, it should be possible to
	tack the method onto a command with minimal effort.

    (2) The "command" can be a bash builtin or external program (or
	function for that matter), and should be able to contain
	overridden environment variables as bash normally allows.

Initial solution: try()
-----------------------
  try() was written to follow the pattern of languages with exception
  handling.  It's mission seems simple enough: Interpret the arguments
  as a bash command, execute the command, abort with an error message if
  the command fails.

  The problem with try() is the incompatibility of the two constraints
  in bash.  Consider the following list of problems with some
  skeleton implementations of try():

    * If try() { eval "$@"; } is used, then the arguments to try()
      must be escaped so that the quoting is preserved.

    * If try() { env "$@"; } is used, then the command can't be a bash
      builtin, because env doesn't understand bash builtins.

    * If try() { bash -c "$@"; } is used, then the effects of the
      builtin won't affect the parent shell, e.g. cd and setting of
      variables.

    * If try() { "$@"; } is used, then bash doesn't evaluate the line
      for prefixed setting of environment variables.

  However this is only the tip of the iceberg!  Here are some more
  problems that use the following try(), functionally similar to the
  one in ebuild.sh:

    try() { env "$@"; if [ $? != 0 ]; then echo "ERROR"; exit 1; fi; }
  
    * Since redirections occur *outside* of the try() function call
      (programs executed inside of the function inherit the file
      descriptors), they won't cause try() to fail.  For example:

	$ try cat nonexistent_file
	ERROR (then shell exits)

	$ try cat < nonexistent_file
	bash: nonexistent_file: No such file or directory

    * Continuing on the redirections thread, consider what happens when
      you redirect the stderr of the command...  The ebuild will bomb
      out, but the error message will be lost because try() no longer
      has access to the output terminal.  (Yes, it could use /dev/tty,
      but shouldn't because that would pose problems for automated
      builds.)

	$ try grep patt * >/dev/null 2>&1
	[no error message from try]

    * try() only applies to the first element of a pipeline.  As
      a result, the following would not be caught by try().

	$ try cat bogus_but_existing_patch | patch -p1
	patch: **** Only garbage was found in the patch input.

  I think try() is a good idea, but the limitations of bash syntax make
  it difficult to implement well.  Therefore I propose a different
  solution...

Proposed replacement: die() and assert_true_or_die()
----------------------------------------------------
  diefunc() {
    local funcname="$1" lineno="$2" exitcode="$3"
    shift 3
    echo &>2
    echo "!!! ERROR: The ebuild did not complete successfully." &>2
    echo "!!! Function $funcname, Line $lineno, Exitcode $exitcode" &>2
    echo "!!! ${*:-(no error message)}" &>2
    echo &>2
    exit 1
  }
  alias die='diefunc "$FUNCNAME" "$LINENO" "$?"'
  alias assert_true_or_die='_retval=$?; [ $_retval = 0 ] || \
                            diefunc "$FUNCNAME" "$LINENO" "$_retval"'

  The aliases are necessary to get FUNCNAME and LINENO from the
  caller rather than the callee (diefunc).

  Note one thing (other than usage) that changes between try() and
  die().  The error output of try() shows the actual command that was
  attempted.  That isn't possible with die() because it isn't being
  passed the command.  However it attempts to make up the shortcoming by
  including the function name, the line number, the exitcode, and
  (bonus) a custom message that can be passed by the caller.

  To convert try-lines to die-lines would be as follows (each line
  followed by its conversion).  These examples are all from the Linux
  kernel ebuild.

    try mv linux linux-${KV}

    mv linux linux-${KV} || die

    ----------------------------------------

    try bzip2 -dc ${DISTDIR}/patch-${KV}.bz2 | patch -p1

    bzip2 -dc ${DISTDIR}/patch-${KV}.bz2 | patch -p1 || die

    # or for checking the first half of the pipeline as well as the
    # second half, since bash only reports exit status from the last
    # element on a pipeline...
    (bzip2 -dc ${DISTDIR}/patch-${KV}.bz2 || die) | patch -p1 || die

    ----------------------------------------

    try cd LVM/${LVMV}

    # include a custom message
    cd LVM/${LVMV} || die "Looks like LVM didn't unpack correctly."

    ----------------------------------------

    try CFLAGS="${CFLAGS} -I${S}/include" ./configure --prefix=/ \
	--mandir=/usr/share/man --with-kernel_dir="${S}"

    # use the assert_true_or_die() version for clarity
    CFLAGS="${CFLAGS} -I${S}/include" ./configure --prefix=/ \
	--mandir=/usr/share/man --with-kernel_dir="${S}" 
    assert_true_or_die "Kernel configuration failed"

    ----------------------------------------

    try make KERNEL_VERSION=${KV} KERNEL_DIR=${S}

    make KERNEL_VERSION=${KV} KERNEL_DIR=${S} || die

Conclusion
----------
  I believe that die() and assert_true_or_die() fulfill the constraints,
  provide a workable replacement for try(), and are more bashish all
  around.  Note there is no need to immediately convert all existing
  ebuilds to die() and assert_true_or_die().  If this proposal is
  accepted (after discussion and potential modifications), then the new
  functions should be used in new ebuilds, and old ebuilds can be
  gradually changed to the new scheme.

  Feedback/questions/comments are welcome!  Thanks for reading.

Aron



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [gentoo-dev] Thoughts on try()
  2001-08-08  7:43 [gentoo-dev] Thoughts on try() Aron Griffis
@ 2001-08-08 11:00 ` Daniel Robbins
  2001-08-08 11:36   ` Dan Armak
  2001-08-08 15:06 ` Aron Griffis
  1 sibling, 1 reply; 4+ messages in thread
From: Daniel Robbins @ 2001-08-08 11:00 UTC (permalink / raw
  To: gentoo-dev

On Wed, Aug 08, 2001 at 09:41:33AM -0400, Aron Griffis wrote:

>   alias die='diefunc "$FUNCNAME" "$LINENO" "$?"'

This is the part I like the most.  I didn't realize that you could
cause $LINENO to be evaluated when die is called; very nice!

>   Feedback/questions/comments are welcome!  Thanks for reading.

All in all an excellent summary of the current problem and a great
proposed solution.  Dan, can you add a wiki item for me to remind
me to add this to Portage?

Best Regards,

-- 
Daniel Robbins					<drobbins@gentoo.org>
Chief Architect/President			http://www.gentoo.org 
Gentoo Technologies, Inc.			



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [gentoo-dev] Thoughts on try()
  2001-08-08 11:00 ` Daniel Robbins
@ 2001-08-08 11:36   ` Dan Armak
  0 siblings, 0 replies; 4+ messages in thread
From: Dan Armak @ 2001-08-08 11:36 UTC (permalink / raw
  To: gentoo-dev

On Wednesday 08 August 2001 19:59, you wrote:
> On Wed, Aug 08, 2001 at 09:41:33AM -0400, Aron Griffis wrote:
> >   alias die='diefunc "$FUNCNAME" "$LINENO" "$?"'
>
> This is the part I like the most.  I didn't realize that you could
> cause $LINENO to be evaluated when die is called; very nice!
>
> >   Feedback/questions/comments are welcome!  Thanks for reading.
>
> All in all an excellent summary of the current problem and a great
> proposed solution.  Dan, can you add a wiki item for me to remind
> me to add this to Portage?
>
OK.


-- 

Dan Armak
Gentoo Linux Developer, Desktop Team
Matan, Israel



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [gentoo-dev] Thoughts on try()
  2001-08-08  7:43 [gentoo-dev] Thoughts on try() Aron Griffis
  2001-08-08 11:00 ` Daniel Robbins
@ 2001-08-08 15:06 ` Aron Griffis
  1 sibling, 0 replies; 4+ messages in thread
From: Aron Griffis @ 2001-08-08 15:06 UTC (permalink / raw
  To: gentoo-dev

Aron Griffis wrote:	[Wed Aug  8 2001,  9:41:33AM EDT]
>   diefunc() {
>     local funcname="$1" lineno="$2" exitcode="$3"
>     shift 3
>     echo &>2
>     echo "!!! ERROR: The ebuild did not complete successfully." &>2
>     echo "!!! Function $funcname, Line $lineno, Exitcode $exitcode" &>2
>     echo "!!! ${*:-(no error message)}" &>2
>     echo &>2
>     exit 1
>   }
>   alias die='diefunc "$FUNCNAME" "$LINENO" "$?"'
>   alias assert_true_or_die='_retval=$?; [ $_retval = 0 ] || \
>                             diefunc "$FUNCNAME" "$LINENO" "$_retval"'

Sorry, let me rewrite this.  All the &> should be >&, also the \ should
be omitted from assert_true_or_die since it's in single quotes.

  diefunc() {
    local funcname="$1" lineno="$2" exitcode="$3"
    shift 3
    echo >&2
    echo "!!! ERROR: The ebuild did not complete successfully." >&2
    echo "!!! Function $funcname, Line $lineno, Exitcode $exitcode" >&2
    echo "!!! ${*:-(no error message)}" >&2
    echo >&2
    exit 1
  }
  alias die='diefunc "$FUNCNAME" "$LINENO" "$?"'
  alias assert_true_or_die='_retval=$?; [ $_retval = 0 ] || 
                            diefunc "$FUNCNAME" "$LINENO" "$_retval"'

That's what I get for not actually *testing* final modifications...  :-|

Aron



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2001-08-08 21:05 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2001-08-08  7:43 [gentoo-dev] Thoughts on try() Aron Griffis
2001-08-08 11:00 ` Daniel Robbins
2001-08-08 11:36   ` Dan Armak
2001-08-08 15:06 ` Aron Griffis

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox