#!/usr/bin/ruby #******************************************************************************* #* A Ruby script to detect changes to different types of spec ( client/label/ #* branch ) and to record the changes by checking the specs in as text files #* into a dedicated area of the depot. Designed to be periodically executed #* via either cron or the Windows scheduler depending on platform. #* #* Note: This script REQUIRES a working build of P4Ruby which you can #* get from the Perforce public depot in #* #* //guest/tony_smith/perforce/API/Ruby/... #* #* You'll also need the Perforce API for your platform to build P4Ruby. #* #******************************************************************************* #******************************************************************************* #* CONFIGURATION SECTION #******************************************************************************* # Depot path under which specs should be versioned. This path should not # already exist. SPEC_PATH = "//depot/specs" # Name of the counter to use to keep track of update runs. One counter is # used for *all* types of spec. SPEC_COUNTER = "specsaver" # Name of the field in your jobspec which is used as the update date JOB_DATE_FIELD = "Date" # Perforce environment setup P4USER = "perforce" P4PORT = "localhost:1666" P4PASSWD = "" P4CLIENT = "specsaver" # Set the client root to a path that the script can use as its workspace CLIENTROOT = "setme" #******************************************************************************* #* END OF CONFIGURATION SECTION #******************************************************************************* require "P4" # Virtual base class for handling all types of spec. For each spec you want # to manage, derive a class which must implement at least the following # methods: # # list_specs - list specs ("p4 clients"/"p4 labels" etc.) # spec_file - Locate spec file in workspace # class SpecMgr def initialize( root, p4tagged, p4untagged ) @root = root @p4t = p4tagged @p4u = p4untagged end def changed?( spec, stamp ) spec[ "Update" ].to_i > stamp end def add_edit_file( path ) fs = @p4t.run_fstat( path ).shift if ( fs ) @p4t.run_edit( path ) else @p4t.run_add( "-t", "text", path ) end end # # Compute the name of a spec from its type. Necessary because the # field for jobs is "Job" whilst for clients it's "client" ... # def type2name( type, spec ) return spec[ type ] if spec.has_key?( type ) return spec[ type.capitalize ] if spec.has_key?( type.capitalize ) raise( RuntimeError, "Can't determine object name from type" ) end # # Save the named spec into its text file version. As we don't want # the spec in parsed form for this we instantiate another P4 instance # briefly here to run the "p4 xxxx -o" in non-tagged mode and the # output of that command is written to the workspace file. # def save_spec( type, spec ) path = spec_file( spec ) add_edit_file( path ) name = type2name( type, spec ) form = eval( %Q{@p4u.fetch_#{type}( "#{name}" )} ) write_ws_file( path, form ) submit( "Updated copy of #{type} #{name}" ) end def write_ws_file( path, form ) File.open( path, "w+" ) do |file| file.write( form ) end end def submit( desc ) @p4t.run_revert( "-a" ) change = @p4t.fetch_change if ( change.has_key?( "Files" ) ) puts( desc ) change[ "Description" ] = desc @p4t.submit_spec( change ) end end # Update the specs of the specified type def update_specs( type, since ) list_specs.each do |spec| save_spec( type, spec ) if ( changed?( spec, since ) ) end end end class GenSpecMgr < SpecMgr def initialize( type, root, p4t, p4u ) @type = type super( root, p4t, p4u ) end def list_specs eval( %Q{@p4t.run_#{@type}s} ) end def spec_file( spec ) @root + "/#{@type}s/" + type2name( @type, spec ) end def update_specs( since ) super( @type, since ) end end # Subclass for clients class ClientMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "client", root, p4t, p4u ) end end # Subclass for labels class LabelMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "label", root, p4t, p4u ) end end # Subclass for users class UserMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "user", root, p4t, p4u ) end end # Subclass for groups. Groups don't have an update date so we have # to depend on revert -a doing the honours. "p4 groups" also doesn't # support tagged output at the moment (2002.1) so what are hashes in # the other classes are just group names in this one. class GroupMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "group", root, p4t, p4u ) end def changed?( group, since ) true end def spec_file( group ) @root + "/#{@type}s/" + group end def type2name( type, group ) group end end # Subclass for depots. "p4 depots" doesn't support tagged mode so we # do this the simple way. class DepotMgr < GenSpecMgr def initialize( root, p4t, p4u ) super( "depot", root, p4t, p4u ) end def changed?( depot, since ) true end def spec_file( depot ) @root + "/#{@type}s/" + depot end def type2name( type, depot ) depot end def list_specs @p4t.run_depots.collect do |rec| rec.split[ 1 ] end end end # Subclass for branches. As the plural of branch is branchES, this needs # a separate subclass of its own. class BranchMgr < SpecMgr def list_specs @p4t.run_branches end def spec_file( spec ) @root + "/branches/" + spec[ "branch" ] end def update_specs( since ) super( "branch", since ) end end # Subclass for jobs class JobMgr < SpecMgr # Get a list of labels from the server. def list_specs( stamp ) @p4t.run_jobs( "-e", "#{JOB_DATE_FIELD} > #{stamp}" ) end # Locate the workspace file for a given spec def spec_file( spec ) @root + "/jobs/" + spec[ "Job" ] end def update_specs( since ) list_specs( since ).each do |spec| save_spec( "job", spec ) end end end # Subclass for one off's like jobspec and protections. These are a little # different as "there can be only one!" We also can't use a timestamp to # see if they've changed so we rely on "p4 revert -a" class ConfigMgr < SpecMgr def update( name, label ) path = @root + "/" + name add_edit_file( path ) form = eval %Q{@p4u.fetch_#{name}()} write_ws_file( path, form ) submit( "Updated copy of #{label}" ) end end # # Main top level class for co-ordinating the run. Here we can trap # Perforce exceptions and look after the overall environment. # class SpecSaver SPECS = [ "branches", "clients", "depots", "groups", "jobs", "labels", "users" ] def initialize # Perforce client for handling tagged output @p4t = P4.new @p4t.parse_forms @p4t.exception_level = 1 @p4t.debug = 0 @p4t.port = P4PORT @p4t.user = P4USER @p4t.password = P4PASSWD @p4t.client = P4CLIENT @p4t.connect # Perforce client for non-tagged output @p4u = P4.new @p4u.exception_level = 1 @p4u.debug = 0 @p4u.port = P4PORT @p4u.user = P4USER @p4u.password = P4PASSWD @p4u.client = P4CLIENT @p4u.connect # Set the client root @root = CLIENTROOT init_client() end # Create if necessary def init_client() begin @p4t.run_info.each do |line| create_client() if ( line =~ /^Client unknown./ ) end init_workspace() @p4t.run_sync rescue P4Exception @p4t.errors.each { |e| puts( e ) } raise end end def init_workspace() Dir.mkdir( @root ) unless ( File.exists?( @root ) ) SPECS.each do |type| dir = @root + "/" + type Dir.mkdir( dir ) unless ( File.exists?( dir ) ) end end # Create the client workspace. def create_client() begin spec = @p4t.fetch_client spec[ "Description" ] = "Client for spec versioning script" spec[ "Root" ] = @root # Don't use String#gsub! it seems to cause problems opt = spec[ "Options" ].gsub( "normdir", "rmdir" ) spec[ "Options" ] = opt spec[ "View" ] = Array.new spec[ "View" ].push( SPEC_PATH + "/... //" + @p4t.client? + "/..." ) @p4t.save_client( spec ) rescue P4Exception @p4t.errors.each { |e| puts( e ) } end end def counter? c = @p4t.run_counter( SPEC_COUNTER ).shift.to_i return c end def counter=( val ) @p4t.run_counter( SPEC_COUNTER, val ) true end def update # Timestamp for this run now = Time.now.to_i # time of last update since = counter? begin ClientMgr.new( @root, @p4t, @p4u ).update_specs( since ) LabelMgr.new( @root, @p4t, @p4u ).update_specs( since ) BranchMgr.new( @root, @p4t, @p4u ).update_specs( since ) JobMgr.new( @root, @p4t, @p4u ).update_specs( since ) UserMgr.new( @root, @p4t, @p4u ).update_specs( since ) GroupMgr.new( @root, @p4t, @p4u ).update_specs( since ) DepotMgr.new( @root, @p4t, @p4u ).update_specs( since ) c = ConfigMgr.new(@root, @p4t, @p4u ) c.update( "protect", "protections" ) c.update( "jobspec", "jobspec" ) c.update( "typemap", "typemap" ) # Save the counter. Any of the Mgr vars will do. self.counter = now rescue P4Exception @p4t.errors.each{ |e| puts( e ) } @p4u.errors.each{ |e| puts( e ) } end end end #******************************************************************************* #* START OF MAIN SCRIPT #******************************************************************************* # Sanity check! if ( CLIENT_ROOT == "setme" ) raise( RuntimeError, "Error: Client root not set. " + "Edit script and set client root before retrying" ) end w = SpecSaver.new w.update