Files
2025-09-29 00:52:08 +02:00

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