# New Ticket Created by James Keenan # Please include the string: [perl #44083] # in the subject line of all future correspondence about this issue. # <URL: http://rt.perl.org/rt3/Ticket/Display.html?id=44083 >
--- osname= linux osvers= 2.6.18.3 arch= i486-linux-gnu-thread-multi cc= cc --- Flags: category=utilities severity=medium ack=no --- Please review the patch attached which contains two new files: lib/Parrot/Configure/Trace.pm t/postconfigure/04-trace.t ... and modifications to four existing files: lib/Parrot/Configure/Options.pm lib/Parrot/Configure.pm config/gen/makefiles/root.in MANIFEST Parrot::Configure::Trace is new functionality which may be of use to Parrot developers, particularly those who have occasion to work enables you to trace the evolution of the Parrot::Configure object over the 56 configuration steps. Methods are provided to (a) track the development of a single attribute over the configuration steps, and (b) take a snapshot of the object's state at the conclusion of a particular configuration step. To use Parrot::Configure::Trace, call: perl Configure.pl --configure_trace A file will be written to your sandbox via the Perl Storable module. You can then write programs which create a Parrot::Configure::Trace object and then call methods against it. The new package was developed in the reconfigure/ branch and is passing all its tests there. Enjoy! Thank you very much. kid51 ################### Index: lib/Parrot/Configure/Options.pm =================================================================== --- lib/Parrot/Configure/Options.pm (revision 20099) +++ lib/Parrot/Configure/Options.pm (working copy) @@ -18,6 +18,7 @@ ccflags ccwarn cgoto + configure_trace cxx datadir debugging Index: lib/Parrot/Configure/Trace.pm =================================================================== --- lib/Parrot/Configure/Trace.pm (revision 0) +++ lib/Parrot/Configure/Trace.pm (revision 0) @@ -0,0 +1,436 @@ +# Copyright (C) 2001-2007, The Perl Foundation. +# $Id$ +package Parrot::Configure::Trace; +use strict; +use warnings; +use Carp; +use Storable qw(nstore retrieve); + +sub new { + my $class = shift; + my $argsref = shift || {}; + croak "Constructor correctly failed due to non-hashref argument" + unless ref($argsref) eq 'HASH'; + my $self = bless( [], $class ); + my $sto = $argsref->{storable} || q{.configure_trace.sto}; + eval { @{$self} = @{ retrieve($sto) }; }; + if ($@) { + croak "Unable to retrieve storable file of configuration step data"; + } else { + return $self; + } +} + +sub list_steps { + my $self = shift; + return $self->[0]; +} + +sub index_steps { + my $self = shift; + my @steps = @{ $self->list_steps() }; + my %index = (); + for (my $i = 0; $i <= $#steps; $i++) { + $index{$steps[$i]} = $i + 1; + } + return \%index; +} + +sub trace_options_c { + my ($self, $argsref) = @_; + my @data = @{$self}; + my @c = (); + for (my $step = 1; $step <= $#data; $step++) { + my $value = $data[$step]->{options}->{c}->{$argsref->{attr}}; + if ($argsref->{verbose}) { + push @c, { $self->[0]->[$step - 1] => $value }; + } else { + push @c, $value; + } + } + return [EMAIL PROTECTED]; +} + +sub trace_options_triggers { + my ($self, $argsref) = @_; + my @data = @{$self}; + my @triggers = (); + for (my $step = 1; $step <= $#data; $step++) { + my $value = + $data[$step]->{options}->{triggers}->{$argsref->{trig}}; + if ($argsref->{verbose}) { + push @triggers, { $self->[0]->[$step - 1] => $value }; + } else { + push @triggers, $value; + } + } + return [EMAIL PROTECTED]; +} + +sub trace_data_c { + my ($self, $argsref) = @_; + my @data = @{$self}; + my @c = (); + for (my $step = 1; $step <= $#data; $step++) { + my $value = $data[$step]->{data}->{c}->{$argsref->{attr}}; + if ($argsref->{verbose}) { + push @c, { $self->[0]->[$step - 1] => $value }; + } else { + push @c, $value; + } + } + return [EMAIL PROTECTED]; +} + +sub trace_data_triggers { + my ($self, $argsref) = @_; + my @data = @{$self}; + my @triggers = (); + for (my $step = 1; $step <= $#data; $step++) { + my $value = + $data[$step]->{data}->{triggers}->{$argsref->{trig}}; + if ($argsref->{verbose}) { + push @triggers, { $self->[0]->[$step - 1] => $value }; + } else { + push @triggers, $value; + } + } + return [EMAIL PROTECTED]; +} + +sub get_state_at_step { + my $self = shift; + my $step = shift; + my $state; + if ($step =~ /^\d+$/) { + croak "Must supply positive integer as step number" + unless $step > 0 and $step <= $#{$self->[0]}; + return $self->[$step]; + } else { + my $index = $self->index_steps(); + croak "Must supply valid step name" + unless $index->{$step}; + return $self->[$index->{$step}]; + } +} + +################### DOCUMENTATION ################### + +=head1 NAME + +Parrot::Configure::Trace - Trace development of Parrot::Configure object through the configuration steps + +=head1 SYNOPSIS + +When calling F<perl Configure.pl>: + + $ perl Configure.pl --configure_trace + +After configuration has completed: + + use Parrot::Configure::Trace; + + $obj = Parrot::Configure::Trace->new(); + + $steps_list = $obj->list_steps(); + + $steps_index = $obj->index_steps(); + + $attr = $obj->trace_options_c( { + attr => 'some_attr', + verbose => 1, # optional + } ); + + $attr = $obj->trace_options_triggers( { + trig => 'some_trig', + verbose => 1, # optional + } ); + + $attr = $obj->trace_data_c( { + attr => 'some_attr', + verbose => 1, # optional + } ); + + $attr = $obj->trace_data_triggers( { + trig => 'some_trig', + verbose => 1, # optional + } ); + + $state = $obj->get_state_at_step($step_no); + + $state = $obj->get_state_at_step('some::step'); + +=head1 DESCRIPTION + +This module provides ways to trace the evolution of the data structure within +the Parrot::Configure object over the various steps in the configuration +process. An understanding of this data structure's development may be useful +to Parrot developers working on the configuration process or its results. + +To make use of Parrot::Configure::Trace's methods, first configure with the +C<--configure_trace> option. As configuration proceeds through what are +currently 56 individual steps, the state of the Parrot::Configuration object +is recorded in a Perl array reference. That array ref is stored on disk via +the Storable module in a file called F<.configure_trace.sto> found in the +top-level of your Parrot sandbox directory. + +Once that storable file has been created, you can write programs which +retrieve its data into a Parrot::Configure::Trace object and then call methods +on that object. + +=head1 METHODS + +=head2 C<new()> + + $obj = Parrot::Configure::Trace->new(); + +=over 4 + +=item * Purpose + +Parrot::Configure::Trace constructor. Retrieve configuration data recorded on +disk over the course of the configuration steps and populate a +Parrot::Configure::Trace object with that data. + +=item * Arguments + +None currently required. However, to provide for future extensibility, you +may provide a reference to a hash in which various attributes are set which +will affect the Parrot::Configure::Trace object. Currently, the only such +attribute is C<storable>, whose value is the name of the Storable file holding +configuration data if that file is named something other than +F<.configure_trace.sto>. + +=item * Return Value + +Parrot::Configure::Trace object. + +=item * Comment + +The Parrot::Configure::Trace object is a blessed array reference. Element +C<0> of that array is a reference to an array holding the names of the +individual configuration steps; elements C<1> through C<$#array> hold the +state of the Parrot::Configure object at the conclusion of each step. + +Since the purpose of Parrot::Configure::Trace is to track the B<evolution> of +the Parrot::Configure object through the configuration steps, there is no +point in recording information about those parts of the Parrot::Configure +object which are invariant. The C<steps> element is set in F<Configure.pl> +before the configuration steps are run and does not change during those steps. +Hence, no information about the C<steps> element is recorded and no methods +are provided herein to retrieve that information. Since the C<options> and +(especially) C<data> elements of the Parrot::Configure object do change over +the course of configuration, methods are provided to access that data. + +=back + +=head2 C<list_steps()> + + $steps_list = $obj->list_steps(); + +=over 4 + +=item * Purpose + +Provide list of the names of the configuration steps. + +=item * Arguments + +None. + +=item * Return Value + +Array reference: + + [ + 'init::manifest', + 'init::defaults', + ... + 'gen::config_pm' + ] + +=back + +=head2 C<index_steps()> + + $steps_index = $obj->index_steps(); + +=over 4 + +=item * Purpose + +Provide lookup table showing which step number a given configuration step is. + +=item * Arguments + +None. + +=item * Return Value + +Hash reference: + + { + 'inter::ops' => 19, + 'init::optimize' => 13, + ... + 'init::defaults' => 2, + } + +=back + +=head2 C<trace_options_c()> + +=over 4 + +=item * Purpose + +Provide a list of the values which a given attribute in the C<{options}->{c}> +part of the Parrot::Configure object takes over the course of the +configuration steps. + +=item * Arguments + +Hash reference. Key C<attr> is mandatory; it is the key whose value you wish +to trace over the course of the configuration steps. Key C<verbose> is +optional. + +=item * Return Value + +Array reference. Element C<n> of this array holds the value of the attribute +in the C<{options}->{c}> part of the Parrot::Configure object at configuration +step C<n + 1>. + +If, however, C<verbose> is set, each element C<n> of the array holds a hash +reference where the hash key is the name of configuration step C<n + 1> and +the value is the value of the attribute at step C<n + 1>. + +=back + +=head2 C<trace_data_triggers()> + +=over 4 + +=item * Purpose + +Provide a list of the values which a given attribute in the +C<{options}->{triggers}> part of the Parrot::Configure object takes over the +course of the configuration steps. + +=item * Arguments + +Hash reference. Key C<attr> is mandatory; it is the key whose value you wish +to trace over the course of the configuration steps. Key C<verbose> is +optional. + +=item * Return Value + +Array reference. Element C<n> of this array holds the value of the attribute +in the C<{options}->{triggers}> part of the Parrot::Configure object at +configuration step C<n + 1>. + +If, however, C<verbose> is set, each element C<n> of the array holds a hash +reference where the hash key is the name of configuration step C<n + 1> and +the value is the value of the attribute at step C<n + 1>. + +=back + +=head2 C<trace_data_c()> + +=over 4 + +=item * Purpose + +Provide a list of the values which a given attribute in the C<{data}->{c}> +part of the Parrot::Configure object takes over the course of the +configuration steps. + +=item * Arguments + +Hash reference. Key C<attr> is mandatory; it is the key whose value you wish +to trace over the course of the configuration steps. Key C<verbose> is +optional. + +=item * Return Value + +Array reference. Element C<n> of this array holds the value of the attribute +in the C<{data}->{c}> part of the Parrot::Configure object at configuration +step C<n + 1>. + +If, however, C<verbose> is set, each element C<n> of the array holds a hash +reference where the hash key is the name of configuration step C<n + 1> and +the value is the value of the attribute at step C<n + 1>. + +=back + +=head2 C<trace_data_triggers()> + +=over 4 + +=item * Purpose + +Provide a list of the values which a given attribute in the +C<{data}->{triggers}> part of the Parrot::Configure object takes over the +course of the configuration steps. + +=item * Arguments + +Hash reference. Key C<attr> is mandatory; it is the key whose value you wish +to trace over the course of the configuration steps. Key C<verbose> is +optional. + +=item * Return Value + +Array reference. Element C<n> of this array holds the value of the attribute +in the C<{data}->{triggers}> part of the Parrot::Configure object at +configuration step C<n + 1>. + +If, however, C<verbose> is set, each element C<n> of the array holds a hash +reference where the hash key is the name of configuration step C<n + 1> and +the value is the value of the attribute at step C<n + 1>. + +=back + +=head2 C<get_state_at_step()> + +=over 4 + +=item * Purpose + +Get a snapshot of the data structure in the Parrot::Configure object at the +conclusion of a given configuration step. + +=item * Arguments + +Either a positive integer corresponding to the step number: + + $state = $obj->get_state_at_step(54); + +... or the C<x::y> string corresponding to the step's name in +Parrot::Configure::Step::List. + + $state = $obj->get_state_at_step('gen::makefiles'); + +=item * Return Value + +Hash reference. + +=back + +=head1 AUTHOR + +James E Keenan ([EMAIL PROTECTED]) + +=head1 SEE ALSO + +L<Parrot::Configure>, L<Parrot::Configure::Options>, F<Configure.pl>. + +=cut + +1; + +# Local Variables: +# mode: cperl +# cperl-indent-level: 4 +# fill-column: 100 +# End: +# vim: expandtab shiftwidth=4: Property changes on: lib/Parrot/Configure/Trace.pm ___________________________________________________________________ Name: svn:keywords + Author Date Id Revision Name: svn:eol-style + native Index: lib/Parrot/Configure.pm =================================================================== --- lib/Parrot/Configure.pm (revision 20099) +++ lib/Parrot/Configure.pm (working copy) @@ -19,7 +19,7 @@ =head1 DESCRIPTION This module provides provides a means for registering, executing, and -coordinating one or more Configuration steps. Please see +coordinating one or more configuration steps. Please see F<docs/configuration.pod> for further details about the configuration framework. @@ -39,6 +39,7 @@ use lib qw(config); use Carp qw(carp); +use Storable qw(nstore retrieve); use Parrot::Configure::Data; use Class::Struct; @@ -241,6 +242,11 @@ eval "use $step_name;"; die $@ if $@; + my $conftrace = []; + my $sto = q{.configure_trace.sto}; + if ($self->options->get(q{configure_trace}) and (-e $sto)) { + $conftrace = retrieve($sto); + } my $step = $step_name->new; # RT#43675 This works. but is probably not a good design. @@ -303,6 +309,18 @@ print "." x ( 71 - length($description) - length($result) ); print "$result." unless $step =~ m{^inter/} && $args->{ask}; + if ($self->options->get(q{configure_trace}) ) { + if (! defined $conftrace->[0]) { + $conftrace->[0] = []; + } + push @{$conftrace->[0]}, $step_name; + my $evolved_data = { + options => $self->{options}, + data => $self->{data}, + }; + push @{$conftrace}, $evolved_data; + nstore($conftrace, $sto); + } # reset verbose value for the next step $self->options->set( verbose => $args->{verbose} ); } Index: t/postconfigure/04-trace.t =================================================================== --- t/postconfigure/04-trace.t (revision 0) +++ t/postconfigure/04-trace.t (revision 0) @@ -0,0 +1,223 @@ +#! perl +# Copyright (C) 2007, The Perl Foundation. +# $Id$ +# 04-trace.t + +use strict; +use warnings; +use Carp; +use Data::Dumper; +use Test::More; +if ( + (-e qq{./lib/Parrot/Config/Generated.pm}) + and + (-e qq{./.configure_trace.sto}) + ) { + plan tests => 36; +} else { + plan skip_all => q{Tests irrelevant unless configuration has completed.};; +} +use lib qw( . lib ../lib ../../lib ); +use_ok( 'Parrot::Configure::Trace' ); +use Parrot::Configure::Step::List qw( get_steps_list ); + +my $obj; + +eval { + $obj = Parrot::Configure::Trace->new( [ + storable => '.configure_trace.sto', + ] ); +}; +like($@, qr/^Constructor correctly failed due to non-hashref argument/, + "Correctly failed due to argument other than hash ref"); + +eval { + $obj = Parrot::Configure::Trace->new( { + storable => 'somestrangename.sto', + } ); +}; +like($@, qr/^Unable to retrieve storable file of configuration step data/, + "Correctly failed due to non-existent config data file"); + +ok($obj = Parrot::Configure::Trace->new(), + "Constructor returned true"); +isa_ok($obj, q{Parrot::Configure::Trace}); + +my $steps = $obj->list_steps(); +my $steps_number = scalar(@{$steps}); +is(ref($steps), q{ARRAY}, + "list_steps() correctly returned array ref"); + +# Sanity check! +is_deeply($steps, [ get_steps_list() ], + "list_steps() returned same as Parrot::Configure::Step::List::get_steps_list()"); + +my $index = $obj->index_steps(); +is(ref($index), q{HASH}, + "index_steps() correctly returned hash ref"); +is(scalar(keys %{$index}), $steps_number, + "list_steps() and index_steps() return same number of elements"); + +my ($attr, $trig, $bad); + +$attr = $obj->trace_options_c( { + attr => 'yacc', +} ); +is(ref($attr), q{ARRAY}, + "trace_options_c() correctly returned array ref"); +is(scalar(@{$attr}), $steps_number, + "trace_options_c() and list_steps() return same number of elements"); + +$attr = $obj->trace_options_c( { + attr => 'yacc', + verbose => 1, +} ); +is(ref($attr), q{ARRAY}, + "trace_options_c() correctly returned array ref"); +is(scalar(@{$attr}), $steps_number, + "trace_options_c() and list_steps() return same number of elements"); +$bad = 0; +foreach my $el (@{$attr}) { + $bad++ unless ref($el) eq 'HASH'; +} +is($bad, 0, + "With 'verbose', each element in array returned by trace_options_c() is hash ref"); + +$trig = $obj->trace_options_triggers( { + trig => 'yacc', +} ); +is(ref($trig), q{ARRAY}, + "trace_options_triggers() correctly returned array ref"); +is(scalar(@{$trig}), $steps_number, + "trace_options_triggers() and list_steps() return same number of elements"); + +$trig = $obj->trace_options_triggers( { + trig => 'yacc', + verbose => 1, +} ); +is(ref($trig), q{ARRAY}, + "trace_options_triggers() correctly returned array ref"); +is(scalar(@{$trig}), $steps_number, + "trace_options_triggers() and list_steps() return same number of elements"); +$bad = 0; +foreach my $el (@{$trig}) { + $bad++ unless ref($el) eq 'HASH'; +} +is($bad, 0, + "With 'verbose', each element in array returned by trace_options_triggers() is hash ref"); + +$attr = $obj->trace_data_c( { + attr => 'yacc', +} ); +is(ref($attr), q{ARRAY}, + "trace_data_c() correctly returned array ref"); +is(scalar(@{$attr}), $steps_number, + "trace_data_c() and list_steps() return same number of elements"); + +$attr = $obj->trace_data_c( { + attr => 'yacc', + verbose => 1, +} ); +is(ref($attr), q{ARRAY}, + "trace_data_c() correctly returned array ref"); +is(scalar(@{$attr}), $steps_number, + "trace_data_c() and list_steps() return same number of elements"); +$bad = 0; +foreach my $el (@{$attr}) { + $bad++ unless ref($el) eq 'HASH'; +} +is($bad, 0, + "With 'verbose', each element in array returned by trace_data_c() is hash ref"); + +$trig = $obj->trace_data_triggers( { + trig => 'yacc', +} ); +is(ref($trig), q{ARRAY}, + "trace_data_triggers() correctly returned array ref"); +is(scalar(@{$trig}), $steps_number, + "trace_data_triggers() and list_steps() return same number of elements"); + +$trig = $obj->trace_data_triggers( { + trig => 'yacc', + verbose => 1, +} ); +is(ref($trig), q{ARRAY}, + "trace_data_triggers() correctly returned array ref"); +is(scalar(@{$trig}), $steps_number, + "trace_data_triggers() and list_steps() return same number of elements"); +$bad = 0; +foreach my $el (@{$trig}) { + $bad++ unless ref($el) eq 'HASH'; +} +is($bad, 0, + "With 'verbose', each element in array returned by trace_data_triggers() is hash ref"); + +my @state; +ok($state[0] = $obj->get_state_at_step(54), + "get_state_at_step() returned true"); +ok($state[1] = $obj->get_state_at_step('gen::makefiles'), + "get_state_at_step() returned true"); +is_deeply($state[0], $state[1], + "Numeric and string arguments gave same result"); + +my $state; +eval { $state = $obj->get_state_at_step(0); }; +like($@, qr/^Must supply positive integer as step number/, + "Correctly failed due to non-positive argument"); + +eval { $state = $obj->get_state_at_step(1000000); }; +like($@, qr/^Must supply positive integer as step number/, + "Correctly failed due to non-existent step"); + +eval { $state = $obj->get_state_at_step(q{init::something}); }; +like($@, qr/^Must supply valid step name/, + "Correctly failed due to non-existent step"); + [EMAIL PROTECTED] = (); +#$state[1] = $obj->get_state_at_step(1); +#$state[56] = $obj->get_state_at_step(56); +#print STDERR Dumper ($state[1], $state[56]); +#my @task; +#for (1,56) { +# $task[$_] = $state[$_]->{steps}; +#} +#is_deeply($task[1], $task[56], "same task"); + +pass("Completed all tests in $0"); + +################### DOCUMENTATION ################### + +=head1 NAME + +04-trace.t - test Parrot::Configure::Trace + +=head1 SYNOPSIS + + % prove t/postconfigure/04-trace.t + +=head1 DESCRIPTION + +The files in this directory test functionality used by F<Configure.pl>. +Certain of the modules C<use>d by F<Configure.pl> have functionality which is +only meaningful I<after> F<Configure.pl> has actually been run and +Parrot::Config::Generated has been created. So certain tests need to be run +when your Parrot filesystem is in a "pre-F<make>, post-F<Configure.pl>" state. + +The tests in this file test Parrot::Configure::Trace methods. + +=head1 AUTHOR + +James E Keenan + +=head1 SEE ALSO + +Parrot::Configure::Trace, Parrot::Configure, Parrot::Configure::Options, F<Configure.pl>. + +=cut + +# Local Variables: +# mode: cperl +# cperl-indent-level: 4 +# fill-column: 100 +# End: +# vim: expandtab shiftwidth=4: Property changes on: t/postconfigure/04-trace.t ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:keywords + Author Date Id Revision Name: svn:eol-style + native Index: config/gen/makefiles/root.in =================================================================== --- config/gen/makefiles/root.in (revision 20099) +++ config/gen/makefiles/root.in (working copy) @@ -307,7 +307,8 @@ $(GEN_MODULES) \ $(GEN_LIBRARY) \ $(SRC_DIR)/jit_emit.h \ - runtime/parrot/include/parrotlib.pbc + runtime/parrot/include/parrotlib.pbc \ + .configure_trace.sto ############################################################################### Index: MANIFEST =================================================================== --- MANIFEST (revision 20099) +++ MANIFEST (working copy) @@ -1,7 +1,7 @@ # ex: set ro: # $Id$ # -# generated by tools/dev/mk_manifest_and_skip.pl Sun Jul 22 14:01:51 2007 UT +# generated by tools/dev/mk_manifest_and_skip.pl Sun Jul 22 14:03:47 2007 UT # # See tools/dev/install_files.pl for documentation on the # format of this file. @@ -2288,6 +2288,7 @@ lib/Parrot/Configure/Step.pm [devel] lib/Parrot/Configure/Step/Base.pm [devel] lib/Parrot/Configure/Step/List.pm [devel] +lib/Parrot/Configure/Trace.pm [devel] lib/Parrot/Distribution.pm [devel] lib/Parrot/Docs/Directory.pm [devel] lib/Parrot/Docs/File.pm [devel] @@ -3117,6 +3118,7 @@ t/postconfigure/01-data_slurp.t [] t/postconfigure/02-revision_no_DEVELOPING.t [] t/postconfigure/03-revision.t [] +t/postconfigure/04-trace.t [] t/run/README [] t/run/exit.t [] t/run/options.t [] --- Summary of my parrot 0.4.14 (r20097) configuration: configdate='Sun Jul 22 13:47:34 2007 GMT' Platform: osname=linux, archname=i686-linux jitcapable=1, jitarchname=i386-linux, jitosname=LINUX, jitcpuarch=i386 execcapable=1 perl=/usr/local/bin/perl Compiler: cc='cc', ccflags=' -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE', Linker and Libraries: ld='cc', ldflags=' -L/usr/local/lib', cc_ldflags='', libs='-lnsl -ldl -lm -lcrypt -lutil -lpthread -lrt' Dynamic Linking: share_ext='.so', ld_share_flags='-shared -L/usr/local/lib -fPIC', load_ext='.so', ld_load_flags='-shared -L/usr/local/lib -fPIC' Types: iv=long, intvalsize=4, intsize=4, opcode_t=long, opcode_t_size=4, ptrsize=4, ptr_alignment=1 byteorder=1234, nv=double, numvalsize=8, doublesize=8 --- Environment: HOME =/home/jimk LANG (unset) LANGUAGE (unset) LD_LIBRARY_PATH (unset) LOGDIR (unset) PATH =/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/usr/local/mysql/bin:/home/jimk/bin:/home/jimk/bin/perl SHELL =/bin/bash