#-- #------------------------------------------------------------------------------- # Name : P4Triggers.rb # # Author : Tony Smith # # Description : A library for use when writing Perforce triggers. Methods # and classes common to all my example triggers are to be # found in here. # #------------------------------------------------------------------------------- #++ # A class for holding info about a change. This is intended to be populated # be the output of "p4 describe" rather than "p4 change -o". The difference # is mostly in the case of the hash keys, but that's significant enough. class P4Change # Constructor. Pass the hash returned by P4#run_describe( "-s" ) in # tagged mode. def initialize( hash ) @change = hash[ "change" ] @user = hash[ "user" ] @client = hash[ "client" ] @desc = hash[ "desc" ] @time = Time.at( hash[ "time" ].to_i ) @status = hash[ "status" ] @files = Array.new @jobs = Hash.new if ( hash.has_key?( "depotFile" ) ) hash[ "depotFile" ].each_index do |i| name = hash[ "depotFile" ][ i ] type = hash[ "type" ][ i ] rev = hash[ "rev" ][ i ] act = hash[ "action" ][ i ] df = P4DepotFile.new( name ) dr = df.new_revision dr.type = type dr.revno = rev.to_i dr.action = act @files.push( df ) end end if ( hash.has_key?( "job" ) ) hash[ "job" ].each_index do |i| job = hash[ "job" ][ i ] status = hash[ "jobstat" ][ i ] @jobs[ job ] = status end end end attr_reader :change, :user, :client, :desc, :time, :status, :files, :jobs # Shorthand iterator for looking at the files in the change def each_file( &block ) @files.each { |f| yield( f ) } end # Shorthand iterator for looking at the jobs fixed by the change def each_job( &block ) @jobs.each { |j| yield( j ) } end end # A class for encapsulating file types - type checking is quite common in # many triggers so it's nice to have it available to all. class FileType # Provide a regexp that the filetype must match and a message for the # types that match the regexp. i.e. # # FileType.new( /u?binary/, "binary/ubinary" ) # def initialize( regexp, message ) @re = regexp @msg = message end attr_reader :msg # Test to see if the given filetype matches this type using the # regexp. def match( type ) @re.match( type ) end end # A class for restructuring the output of "p4 describe -s" into a P4Change # object class P4 def run_describe( *args ) h = run( "describe", args ).shift return h unless h.kind_of?( Hash ) return P4Change.new( h ) end end # A class providing a generic framework for all triggers. It provides # rudimentary error handling so all errors get logged to stderr, and we # also have a method for reporting a more friendly message to the user. # It is intended that all trigger scripts will derive their own subclass # of P4Trigger and override/expand it as necessary. # # You must override the validate() method if you want your trigger to # work. The default implementation accepts *everything* so make sure you # provide a method to reject bad changes or you will have accomplished # nothing. # class P4Trigger # Constructor. def initialize @change = nil @p4 = P4.new @p4.parse_forms @p4.exception_level = 1 begin @p4.connect rescue P4Exception error_message() raise end end attr_reader :p4, :change # The main execution phase of the trigger. We assume since this is a # pre-submit trigger that there will be a changelist involved. The # steps for most triggers are common: # # 1. Find out about the changelist # 2. Enforce the rules # # We try to generalise this process so this class calls get_change() # for step 1, and validate() for step 2. Subclasses may override these # methods to tailor the trigger's behaviour # # parse_change() returns the correct exit status for the trigger so you # would normally use it like this: # # exit( trig.parse_change( change_no ) ) # def parse_change( change_no ) begin if ( ! change_no ) raise( "No changelist number supplied to trigger script.\n" + "Please check your trigger configuration." ) end get_change( change_no ) return ( validate() ? 0 : 1 ) rescue # Report errors to stderr, so they go into the Perforce server's # logfile and report a simpler message to stdout $stderr.puts( "\nError during trigger execution:\n\n" ) if ( $!.kind_of?( P4Exception ) ) p4.errors.each { |e| $stderr.puts( e ) } else $stderr.puts( $!.to_s ) end $stderr.puts( "\nStack Trace:\n" ) $stderr.puts( $!.backtrace ) $stdout.puts( error_message() ) # Now we return false to the caller so they can return with # the correct exit status return 1 end end # The default implementation of get_change. Returns a hash describing # the change based on an execution of "p4 describe -s". The hash is # also saved in the @change member which subclasses may access in their # validate() method. For most triggers this will be sufficient. def get_change( change_no ) @change = p4.run_describe( "-s", change_no ) end # The default implementation of validate(). Very simple, it accepts # everything. You are expected to override this method with one of # your own. When you do so, you can use the @change member to get # at the details of the change you're validating. def validate() true end # # Method to send a message to the user. Just writes to stdout, but it's # nice to encapsulate that here. # def message( string ) $stdout.print( string ) end # Default message for when things go wrong def error_message() "An error was encountered during trigger execution. Please\n" + "contact your Perforce administrator and ask them to\n" + "investigate the cause of this error\n" end end
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#8 | 6437 | Tony Smith |
Update P4Ruby Library scripts to support Perforce P4Ruby 2007.3 rather than my old public depot P4Ruby. |
||
#7 | 5807 | Tony Smith |
Add optional parameter to P4Trigger constructor allowing scripts to specify their API level. A following change will set this level for specific scripts. |
||
#6 | 4685 | Tony Smith |
Move the connect() down into the parse_change() method from the constructor. This gives users time to change the configuration of the P4 object before they connect. Useful for setting usernames etc. |
||
#5 | 4684 | Tony Smith | Minor bugfixes found while working on an example for a customer. | ||
#4 | 4656 | Tony Smith |
Rework the defaultclient.rb trigger to be more robust and more efficient. This involved a certain amount of rework to the P4Triggers.rb module to include functionality that will be useful to other spec triggers - there's a new class P4Trigger::FormFile that helps scripts manage their interaction with the temporary formfile supplied by the server. |
||
#3 | 4555 | Tony Smith |
Improve error reporting in trigger framework. Now Perforce warnings will also generate trigger errors, and the error messages will be sent to stdout and stderr. Stderr will also get a stack trace. |
||
#2 | 3637 | Tony Smith | Add RDoc documentation to the sample triggers. | ||
#1 | 3634 | Tony Smith |
Kick off a library of P4Ruby resources with some sample trigger implementations based on Jeff Bowles and Wes Peters' scripts. These are not strict ports of their scripts to P4Ruby, but are roughly in the same area. |