328 lines
9.5 KiB
Ruby
Executable File
328 lines
9.5 KiB
Ruby
Executable File
#
|
|
# File:: monitor_label.rb
|
|
# Description:: Utility class to monitor a perforce folder
|
|
# for label updates.
|
|
#
|
|
# Author:: David Muir <david.muir@rockstarnorth.com>
|
|
# Author:: Derek Ward <derek.ward@rockstarnorth.com>
|
|
# Date:: 16 June 2009
|
|
#
|
|
# == Example Usage
|
|
#
|
|
# path = '//depot/jimmy/build/independent/...'
|
|
# monitor = SCM::MonitorLabel.new( path, server, client, user, '', 'monitor.xml' )
|
|
#
|
|
# # Start continual polling loop, until monitor.stop called, this loop could
|
|
# # run in its own thread.
|
|
# monitor.poll() do | files, skipped|
|
|
# end
|
|
#
|
|
# # Poll SCM repository once, if you are monitoring multiple repositories
|
|
# # or paths and act on them you likely want this version (e.g. Codebuilder)
|
|
# monitorLabel.poll_once() do | files, skipped|
|
|
# end
|
|
#
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
require 'pipeline/config/projects'
|
|
require 'pipeline/resourcing/convert'
|
|
require 'pipeline/scm/perforce'
|
|
require 'pipeline/scm/perforce_helper'
|
|
require 'pipeline/util/thread'
|
|
require 'rexml/document'
|
|
|
|
# REXML Save Fix
|
|
require 'pipeline/util/rexml_write_fix'
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Entry-Point
|
|
#-----------------------------------------------------------------------------
|
|
|
|
module Pipeline
|
|
module SCM
|
|
|
|
#
|
|
# == Description
|
|
# The MonitorLabelStatus class is an object created by the MonitorLabel class that
|
|
# stores the current date and time the label was last successfully synced.
|
|
#
|
|
class MonitorLabelStatus
|
|
attr_reader :filename
|
|
attr_accessor :label_sync_date_time
|
|
|
|
NODE_ROOT = 'local'
|
|
ATTR_SYNC_DATE_TIME = 'sync_date_time'
|
|
|
|
def initialize( filename )
|
|
@filename = filename
|
|
reload( )
|
|
end
|
|
|
|
#
|
|
# Return the REXML::Element for the status XML file. This allows
|
|
# builder apps to add additional data into the XML file. Be very
|
|
# careful you do not overwrite the root node's changelist attribute,
|
|
# otherwise bad things will happen.
|
|
#
|
|
# Maybe in the future we will present a better interface.
|
|
#
|
|
def xmlroot( )
|
|
@xmldoc.root
|
|
end
|
|
|
|
#
|
|
# Force a reload of the XML disk file.
|
|
#
|
|
def reload( )
|
|
|
|
if ( ::File::exists?( @filename ) ) then
|
|
File.open( @filename ) do |file|
|
|
|
|
@xmldoc = REXML::Document.new( file )
|
|
@label_sync_date_time = @xmldoc.root.attributes[ATTR_SYNC_DATE_TIME]
|
|
end
|
|
else
|
|
@label_sync_date_time = 'not synced'
|
|
|
|
@xmldoc = REXML::Document.new( )
|
|
@xmldoc << REXML::XMLDecl.new
|
|
@xmldoc.add_element( NODE_ROOT )
|
|
@xmldoc.root.add_attribute( ATTR_SYNC_DATE_TIME, @label_sync_date_time.to_s )
|
|
save( )
|
|
end
|
|
end
|
|
|
|
#
|
|
# Save the current status to XML disk file.
|
|
#
|
|
def save( )
|
|
|
|
# Update XML Document
|
|
@xmldoc.root.attributes[ATTR_SYNC_DATE_TIME] = @label_sync_date_time.to_s
|
|
|
|
# Flush to disk
|
|
File.open( @filename, "w+" ) do |file|
|
|
@xmldoc.write( file, 4 )
|
|
end
|
|
end
|
|
end
|
|
|
|
class MonitorLabel
|
|
|
|
DEF_POLL_TIME = 60.0
|
|
|
|
attr_reader :p4
|
|
attr_reader :running
|
|
attr_reader :poll_time
|
|
attr_reader :root_folder
|
|
attr_reader :status
|
|
attr_reader :label
|
|
|
|
#---------------------------------------------------------------------
|
|
# Virtual Attributes
|
|
#---------------------------------------------------------------------
|
|
def current_label_date_time( )
|
|
return ( @status.label_sync_date_time)
|
|
end
|
|
|
|
#
|
|
# MonitorLabel constructor, specifying Perforce server details including
|
|
# root folder to watch (note: append '/...' for recursive watch).
|
|
#
|
|
def initialize( sc_root, # P4 path to monitor
|
|
sc_server, # P4 server:port string
|
|
sc_clientspec, # P4 clientspec name to use
|
|
sc_username, # P4 username
|
|
config_filename,
|
|
label,
|
|
poll_time = DEF_POLL_TIME )
|
|
|
|
@status = MonitorLabelStatus.new( config_filename )
|
|
@poll_time = poll_time
|
|
@c = Pipeline::Config.instance
|
|
@root_folder = sc_root
|
|
@label = label
|
|
|
|
@p4 = SCM::Perforce.new
|
|
@p4.exception_level = P4::RAISE_ERRORS
|
|
@p4.user = sc_username
|
|
@p4.client = sc_clientspec
|
|
@p4.port = sc_server
|
|
|
|
MonitorLabel.log().debug( "Connecting to #{@p4.port}, #{@p4.client}..." )
|
|
@p4.connect
|
|
MonitorLabel.log().debug( "Connected? #{@p4.connected?}" )
|
|
end
|
|
|
|
#
|
|
# Start the monitorLabel with an optional block, continually polling the
|
|
# Perforce server for changes. The block takes four arguments:
|
|
# Perforce object, change list ID, P4 sync output hash, and skipped bool.
|
|
#
|
|
def poll( &block )
|
|
@running = true
|
|
|
|
while ( @running ) do
|
|
|
|
self.poll_once( &block )
|
|
Kernel.sleep( @poll_time )
|
|
end
|
|
end
|
|
|
|
#
|
|
# Start the monitorLabel with an optional block, only polling the Perforce
|
|
# server once for the label. The block takes four arguments:
|
|
# Perforce object, P4 sync output hash, and skipped bool.
|
|
#
|
|
def poll_once( &block )
|
|
|
|
begin
|
|
@p4.connect( )
|
|
|
|
sync_to_label( &block )
|
|
|
|
@p4.disconnect( )
|
|
|
|
rescue Exception => ex
|
|
puts "Exception in MonitorLabel.poll_once(): #{ex.message}"
|
|
puts ex.backtrace.join( "\n" )
|
|
end
|
|
end
|
|
|
|
#
|
|
# Stop the monitorLabel on its next iteration. This might not stop the
|
|
# monitor immediately as it might be blocking in a Kernel.sleep().
|
|
#
|
|
def stop( )
|
|
@running = false
|
|
@p4.disconnect( ) if ( @p4.connected?() )
|
|
end
|
|
|
|
#---------------------------------------------------------------------
|
|
# Class Methods
|
|
#---------------------------------------------------------------------
|
|
|
|
#
|
|
# Return a MonitorLabel instance from a P4 object.
|
|
#
|
|
def MonitorLabel::from_p4( p4, sc_root, config_filename, label, poll_time = DEF_POLL_TIME )
|
|
throw ArgumentError.new( "Invalid P4 object specified (#{p4.class})." ) \
|
|
unless ( p4.is_a?( P4 ) )
|
|
|
|
MonitorLabel.new( sc_root, p4.port, p4.client, p4.user, config_filename, label, poll_time )
|
|
end
|
|
|
|
#
|
|
# Return the MonitorLabel class logger object.
|
|
#
|
|
def MonitorLabel::log()
|
|
|
|
@@log = Log.new( 'monitor_label' ) if @@log.nil?
|
|
@@log
|
|
end
|
|
|
|
def sync_to_label( &block )
|
|
|
|
begin
|
|
@p4.connect() unless ( @p4.connected?() )
|
|
|
|
MonitorLabel.log().debug( "Syncing to Label." )
|
|
|
|
update_to_label( &block )
|
|
|
|
rescue Exception => ex
|
|
puts "Exception in MonitorLabel.sync_to_label(): #{ex.message}"
|
|
puts ex.backtrace.join( "\n" )
|
|
end
|
|
end # sync_to_label
|
|
|
|
#
|
|
# Have we actually got the correct revisions of the files in the label.
|
|
#
|
|
def is_synced_to_label( )
|
|
is_synced = true
|
|
|
|
begin
|
|
# get a list of files in the label, in an array of hashes with useful p4 file data keyed within.
|
|
label_files = @p4.run_fstat("-Ol", "#{@root_folder}@#{@label}")
|
|
|
|
label_files.each do |label_file|
|
|
if (label_file.has_key?("haveRev") and label_file.has_key?("headRev")) then
|
|
if label_file['haveRev'] != label_file['headRev'] then
|
|
is_synced = false
|
|
puts " File not synced : #{label_file['depotFile']} [#{label_file['haveRev']}/#{label_file['headRev']}] for label #{@label}"
|
|
end
|
|
end
|
|
end
|
|
rescue Exception => ex
|
|
puts "Exception in is_synced_to_label #{ex.message}"
|
|
end
|
|
is_synced
|
|
end # is_synced_to_label
|
|
|
|
#
|
|
# Does this label exist
|
|
#
|
|
def label_exists( )
|
|
begin
|
|
label_out = @p4.run_labels("-e", "#{@label}")
|
|
label_out.each { |lbl_out| return true if lbl_out.has_key?("label") and lbl_out["label"]==@label }
|
|
rescue P4Exception => ex
|
|
puts "P4 Exception: #{ex.message}"
|
|
end
|
|
|
|
false
|
|
end # label_exists
|
|
|
|
|
|
#---------------------------------------------------------------------
|
|
# Protected
|
|
#---------------------------------------------------------------------
|
|
protected
|
|
@@log = nil
|
|
|
|
#
|
|
# Sync our root folder to a specific label.
|
|
#
|
|
def update_to_label( )
|
|
MonitorLabel.log().debug( "update_to : #{@root_folder} @#{@label}" )
|
|
MonitorLabel.log().debug( "connected? #{@p4.connected?}" )
|
|
puts "Attempting Update to label #{@label}"
|
|
|
|
label_synced_ok = false
|
|
out = nil
|
|
begin
|
|
throw Exception.new("Label #{label} not found.") unless label_exists
|
|
puts "p4 sync @root_folder@#{@label}"
|
|
out = @p4.run_sync(@root_folder + "@#{@label}")
|
|
puts "output : #{out.length}"
|
|
out.each { |o| puts o.to_s }
|
|
# you know I've just realised that really we should be using a dynamic label name rather than the same name due to
|
|
# possible 'non thread safe' like issues with the use of this label here. thats a bit of work though.
|
|
label_synced_ok = is_synced_to_label()
|
|
puts "label_synced_ok #{label_synced_ok}"
|
|
rescue Exception => ex
|
|
puts "Label sync Exception: #{ex.message}"
|
|
rescue P4Exception => ex
|
|
puts "P4Exception: #{ex.message}"
|
|
end
|
|
|
|
something_synced = ( out.is_a?( Array ) and ( out.size > 0 ) )
|
|
|
|
if label_synced_ok and something_synced then
|
|
@status.label_sync_date_time = Time.now
|
|
@status.save( )
|
|
end
|
|
yield( nil, out, label_synced_ok ) if ( block_given? and something_synced==true )
|
|
|
|
puts "End attempting Update to label something_synced=#{something_synced}"
|
|
end
|
|
end
|
|
|
|
end # SCM module
|
|
end # Pipeline module
|
|
|
|
# monitorlabel.rb
|