use strict; ################################################### # CONFIG VARS ################################################### # FIXME: load these from a separate file so that each user can have their own config without editingthis file. my $g_P4UserName = "gary"; my $g_LocalBaseDir = "u:\\hl2"; my $g_LocalBranchSubdir = "src5"; my $g_LocalBranchName = "gary_src5"; my $g_LocalBranchClient = "gary_work_src5"; my $g_MainCopyBaseDir = "u:\\hl2"; my $g_LocalBranchMainCopySubdir = "src5_main"; my $g_LocalBranchMainCopyName = "gary_src5_main"; #my $g_LocalBranchMainCopyClient = "gary_work_src5_main"; my $g_MainBaseDir = "u:\\hl2_main"; my $g_MainBranchSubdir = "src_main"; #my $g_MainBranchName = "gary_src_main"; #my $g_MainBranchClient = "gary_work_src_main"; my $g_UseIncredibuildForMain = 1; # FIXME: need to make this work for those that don't work on HL2. my $g_LocalBranchHasHL1Port = 0; my $g_LocalBranchHasCSPort = 0; my $g_LocalBranchHasTF2 = 0; my $g_CheckinFileStampTimes = "c:\\checkin_filetimes.txt"; # either use VSS directly via the command-line, or use Tom's tool. my $g_UseVSS = 0; ################################################### # Helper vars made up of config vars ################################################### my $g_LocalBranchDir = "$g_LocalBaseDir\\$g_LocalBranchSubdir"; my $g_LocalBranchMainCopyDir = "$g_MainCopyBaseDir\\$g_LocalBranchMainCopySubdir"; my $g_MainBranchDir = "$g_MainBaseDir\\$g_MainBranchSubdir"; ################################################### my $g_DebugStub = 0; my $stage = shift; if( $stage != 1 && $stage != 2 && $stage != 3 && $stage ne "syncmain" && $stage ne "syncmainsrc" && $stage ne "synclocal" && $stage ne "synclocalrelease" && $stage ne "synclocaldebug" && $stage ne "synclocalsrc" && $stage ne "synclocalsrcrelease" && $stage ne "sync" && $stage ne "syncmaincontent" ) { print "checkin.pl 1 : to get started with a checkin\n"; print "checkin.pl 2 : second stage of checkin\n"; print "checkin.pl 3 : third stage of checkin\n"; print "checkin.pl syncmain : sync main source and content, then build\n"; print "checkin.pl syncmainsrc : sync main source then build\n"; print "checkin.pl syncmaincontent : sync main content only\n"; print "checkin.pl synclocal : merge personal branch, sync content,\n"; print " then build.\n"; print "checkin.pl synclocalrelease : merge personal branch, sync content,\n"; print " then build (release only).\n"; print "checkin.pl synclocaldebug : merge personal branch, sync content,\n"; print " then build (debug only).\n"; print "checkin.pl synclocalsrc : merge personal branch, then build.\n"; print "checkin.pl synclocalsrcrelease : merge personal branch, then build\n"; print " (release only).\n"; print "checkin.pl sync : merge personal branch, sync main src,\n"; print " sync content for both, and then build\n"; print " the whole thing.\n"; die; } sub RunCmd { my $cmd = shift; print $cmd . "\n"; if( !$g_DebugStub ) { return system $cmd; } } sub CD { my $dir = shift; print "cd $dir\n"; if( !$g_DebugStub ) { chdir $dir; } } sub SSGet { my $vssdir = shift; my $localdir = shift; &CD( $localdir ); &RunCmd( "ss WorkFold $vssdir $localdir" ); print "\n"; my $cmd = "ss get $vssdir -R"; local( *SS ); open SS, "$cmd|"; my $workingdir; while( ) { # FIXME: clean up this output. $_ =~ s/\n//; if( m/^\$/ ) { $workingdir = $_; # print "WORKING DIR: $_\n"; } elsif( m/^getting/i ) { print "GETTING: $workingdir $_\n"; } elsif( m/^replacing local file/i ) { print "REPLACING: $workingdir $_\n"; } elsif( m/^File/ ) { print "ALREADY EXISTS: $workingdir $_\n"; } else { # print "WTF: $_\n"; } } close SS; } sub FastSSGet { my $localdir = shift; my $option = shift; &RunCmd( "\\\\hl2vss\\hl2vss\\win32\\syncfrommirror.bat $option $localdir" ); } sub FileIsWritable { my $file = shift; my @statresult = stat $file; die if !@statresult; my $perms = oct( $statresult[2] ); if( $perms & 2 ) { return 1; } else { return 0; } } sub CompareDirs { my $filesThatHaveChanged = shift; my $filesThatHaveNotChanged = shift; my @out = `robocopy $g_MainBaseDir\\checkinbins\\. $g_MainBaseDir\\. /S /L /V`; my $line; my $cwd; foreach $line ( @out ) { next if( $line =~ /\*EXTRA Dir/ ); next if( $line =~ /\*EXTRA Dir/ ); next if( $line =~ /\*EXTRA File/ ); if( $line =~ m/\s*\d+\s+(\S+\\)/ ) { $cwd = $1; next; } if( $line =~ m/\s+Older\s+\d+\s+(\S+)/ ) { my $testfilename = $cwd . $1; my $filename = $testfilename; $filename =~ s/\\checkinbins//i; my $diffresult = system "diff $testfilename $filename > nil"; if( $diffresult != 0 ) { push @{$filesThatHaveChanged}, $filename; } else { if( &FileIsWritable( $filename ) ) { push @{$filesThatHaveNotChanged}, $filename; } } next; } elsif( $line =~ m/\s+same\s+\d+\s+(\S+)/ ) { my $filename = $cwd . $1; $filename =~ s/\\checkinbins//i; if( &FileIsWritable( $filename ) ) { push @{$filesThatHaveNotChanged}, $filename; } next; } if( $line =~ m/\s+New File\s+\d+\s+(\S+)/ ) { die "$cwd $1 didn't build!\n"; } print "DEBUG: unhandled line: $line\n"; } } sub CheckoutFile { my $file = shift; print "-----------------\nchecking out $file\n"; if( $file =~ /src_main/i ) { # need to use p4 to check this one out. my $dir = $file; $dir =~ s/\\([^\\]*)$//; &CD( $dir ); &RunCmd( "p4 edit $1" ); } else { my $dir = $file; $dir =~ s/\\([^\\]*)$//; &CD( $dir ); $file =~ s,\\,/,g; if( $file =~ m/cstrike/i || $file =~ m/hl1/i ) { $file =~ s,u:/hl2_main/,\$/hl1ports/release/dev/,gi; &RunCmd( "ss WorkFold \$/hl1ports/release/dev $g_MainBaseDir" ); } elsif( $file =~ m/\/tf2/i ) { $file =~ s,u:/hl2_main/,\$/tf2/release/dev/,gi; &RunCmd( "ss WorkFold \$/tf2/release/dev $g_MainBaseDir" ); } else { $file =~ s,u:/hl2_main/,\$/hl2/release/dev/,gi; &RunCmd( "ss WorkFold \$/hl2/release/dev $g_MainBaseDir" ); } print "\n"; &RunCmd( "ss Checkout -G- $file" ); } } sub RevertFile { my $file = shift; print "-----------------\nreverting $file\n"; if( $file =~ /src_main/i ) { # need to use p4 to revert this one my $dir = $file; $dir =~ s/\\([^\\]*)$//; &CD( $dir ); &RunCmd( "p4 sync -f $1" ); } else { my $dir = $file; $dir =~ s/\\([^\\]*)$//; &CD( $dir ); $file =~ s,\\,/,g; my $vssfile = $file; if( $file =~ m/cstrike/i || $file =~ m/hl1/i ) { $vssfile =~ s,u:/hl2_main/,\$/hl1ports/release/dev/,gi; &RunCmd( "ss WorkFold \$/hl1ports/release/dev $g_MainBaseDir" ); } elsif( $file =~ m/\/tf2/i ) { $vssfile =~ s,u:/hl2_main/,\$/tf2/release/dev/,gi; &RunCmd( "ss WorkFold \$/tf2/release/dev $g_MainBaseDir" ); } else { $vssfile =~ s,u:/hl2_main/,\$/hl2/release/dev/,gi; &RunCmd( "ss WorkFold \$/hl2/release/dev $g_MainBaseDir" ); } print "\n"; unlink $file; &RunCmd( "ss Get -I- $vssfile" ); } } sub SyncMainSource { # SYNC MAIN &CD( $g_MainBranchDir ); &RunCmd( "p4 sync" ); } sub SyncMainContent { # SYNC VSS &CD( $g_MainBranchDir ); &RunCmd( "clean.bat" ); if( $g_UseVSS ) { &SSGet( "\$/hl2/release/dev", $g_MainBaseDir ); &SSGet( "\$/hl1ports/release/dev", $g_MainBaseDir ); # NOTE: only get tf2 bin since we aren't testing tf2 right now &SSGet( "\$/tf2/release/dev/tf2/bin", "$g_MainBaseDir/tf2/bin" ); } else { &FastSSGet( $g_MainBaseDir, "all" ); } } sub BuildMain { if( $g_UseIncredibuildForMain ) { $ENV{"USE_INCREDIBUILD"} = "1"; } &CD( $g_MainBranchDir ); &RunCmd( "clean.bat" ); &RunCmd( "build_hl2.bat" ); &RunCmd( "build_hl1_game.bat" ); &RunCmd( "build_cs_game.bat" ); &RunCmd( "build_tf2_game.bat" ); if( $g_UseIncredibuildForMain ) { undef $ENV{"USE_INCREDIBUILD"}; } } sub SyncLocalBranchSource { &CD( $g_LocalBranchDir ); &RunCmd( "p4mf.bat $g_LocalBranchName $g_LocalBranchClient pause" ); # FIXME: This needs to specify the changelist since p4mf makes a new changelist. # &RunCmd( "p4 submit" ); } sub SyncMainCopySource { &CD( $g_LocalBranchMainCopyDir ); &RunCmd( "p4 integrate -d -i -b $g_LocalBranchMainCopyName" ); &RunCmd( "p4 resolve -at ..." ); # Update the changelist and submit &RunCmd( "p4 change -o | sed -e \"s//Merge from main/g\" | p4 submit -i" ); } sub SyncLocalBranchContent { &CD( $g_LocalBranchDir ); # CLEAN LOCAL BRANCH &RunCmd( "clean.bat" ); # SYNC VSS if( $g_UseVSS ) { &SSGet( "\$/hl2/release/dev", $g_LocalBaseDir ); if( $g_LocalBranchHasHL1Port || $g_LocalBranchHasCSPort ) { &SSGet( "\$/hl1ports/release/dev", $g_LocalBaseDir ); } if( $g_LocalBranchHasTF2 ) { &SSGet( "\$/tf2/release/dev", $g_LocalBaseDir ); } } else { if( $g_LocalBranchHasHL1Port || $g_LocalBranchHasCSPort ) { &FastSSGet( $g_LocalBaseDir, "all" ); } else { &FastSSGet( $g_LocalBaseDir, "hl2" ); } # FIXME: screwed on tf2 here. } } sub BuildLocalBranch { &CD( $g_LocalBranchDir ); # BUILD DEBUG if we don't want release only if( !( $stage =~ /release/i ) ) { # CLEAN LOCAL BRANCH &RunCmd( "clean.bat" ); &RunCmd( "build_hl2.bat debug" ); if( $g_LocalBranchHasHL1Port ) { &RunCmd( "build_hl1_game.bat debug" ); } if( $g_LocalBranchHasCSPort ) { &RunCmd( "build_cs_game.bat debug" ); } if( $g_LocalBranchHasTF2 ) { &RunCmd( "build_tf2_game.bat debug" ); } } if( !( $stage =~ /debug/i ) ) { # CLEAN LOCAL BRANCH &RunCmd( "clean.bat" ); # BUILD RELEASE &RunCmd( "build_hl2.bat" ); if( $g_LocalBranchHasHL1Port ) { &RunCmd( "build_hl1_game.bat" ); } if( $g_LocalBranchHasCSPort ) { &RunCmd( "build_cs_game.bat" ); } if( $g_LocalBranchHasTF2 ) { &RunCmd( "build_tf2_game.bat" ); } } } sub GetMainUpToDate { &SyncMainSource(); &SyncMainContent(); &BuildMain(); } sub LockPerforce { while( 1 ) { my $thing = `p4mutex lock main_src 0 $g_P4UserName 207.173.178.12:1666`; print $thing; last if $thing =~ /Success/; sleep 30; } } sub SaveMainTimeStampsBeforeIntegrate { &CD( $g_MainBranchDir ); # Get a list of files that are going to be integrated into main so that we can save off their time stamp info. my @filestointegrate = `p4 integrate -n -r -b $g_LocalBranchName`; my $file; local( * TIMESTAMPS ); open TIMESTAMPS, ">$g_CheckinFileStampTimes"; foreach $file ( @filestointegrate ) { $file =~ s,//ValveGames/main/src/([^#]*)\#.*,$1,gi; $file =~ s/\n//; $file =~ s,\\,/,g; my $localfilename = "$g_MainBranchDir/$file"; my @statinfo = stat $localfilename; next if !@statinfo; my $mtime = $statinfo[9]; print TIMESTAMPS $file . "|" . $mtime . "\n"; } close TIMESTAMPS; } sub SetMainTimeStampsOnRevertedFiles { &CD( $g_MainBranchDir ); # Get a list of files that we might have to revert times on if they aren't in the changelist. local( *TIMESTAMPS ); open TIMESTAMPS, "<$g_CheckinFileStampTimes"; my @timestamps = ; my %filetotimestamp; my $i; for( $i = 0; $i < scalar( @timestamps ); $i++ ) { $timestamps[$i] =~ s/\n//; $timestamps[$i] =~ m/^(.*)\|(.*)$/; $filetotimestamp{$1} = $2; } close TIMESTAMPS; my $key; foreach $key( keys( %filetotimestamp ) ) { print "before: \'$key\" \"$filetotimestamp{$key}\"\n"; } local( *CHANGELIST ); open CHANGELIST, "p4 change -o|"; while( ) { if( m,//ValveGames/main/src/(.*)\s+\#,i ) { if( defined $filetotimestamp{$1} ) { undef $filetotimestamp{$1}; } } } close CHANGELIST; foreach $key( keys( %filetotimestamp ) ) { if( defined $filetotimestamp{$key} ) { my $filename = $g_MainBranchDir . "/" . $key; $filename =~ s,/,\\,g; my @statresults; if( @statresults = stat $filename ) { my $mode = $statresults[2]; my $atime = $statresults[8]; my $mtime = $statresults[9]; print "reverting timestamp for $filename\n"; chmod 0666, $filename || die $!; utime $atime, $filetotimestamp{$key}, $filename || die $!; chmod $mode, $filename || die $!; } } } } if( $stage eq "synclocal" || $stage eq "synclocalrelease" || $stage eq "synclocaldebug" || $stage eq "sync" ) { &SyncLocalBranchSource(); &SyncMainCopySource(); &SyncLocalBranchContent(); &BuildLocalBranch(); } if( $stage eq "synclocalsrc" || $stage eq "synclocalsrcrelease" ) { &SyncLocalBranchSource(); &SyncMainCopySource(); &BuildLocalBranch(); } if( $stage eq "syncmainsrc" ) { &SyncMainSource(); &BuildMain(); } if( $stage eq "syncmain" || $stage eq "sync" ) { &GetMainUpToDate(); } if( $stage eq "syncmaincontent" ) { &SyncMainContent(); } if( $stage == 1 ) { # lock p4 # &LockPerforce(); &GetMainUpToDate(); # merge main into local branch &SyncLocalBranchSource(); # TODO: need a way to detect if there are conflicts or not. If there are, pause here. # Make a copy of targets so that we can tell which ones changed. &RunCmd( "robocopy $g_MainBaseDir\\ $g_MainBaseDir\\checkinbins\\ /purge" ); &RunCmd( "robocopy $g_MainBaseDir\\bin\\. $g_MainBaseDir\\checkinbins\\bin\\. /mir" ); &RunCmd( "robocopy $g_MainBaseDir\\hl2\\bin\\. $g_MainBaseDir\\checkinbins\\hl2\\bin\\. /mir" ); &RunCmd( "robocopy $g_MainBaseDir\\hl1\\bin\\. $g_MainBaseDir\\checkinbins\\hl1\\bin\\. /mir" ); &RunCmd( "robocopy $g_MainBaseDir\\cstrike\\bin\\. $g_MainBaseDir\\checkinbins\\cstrike\\bin\\. /mir" ); &RunCmd( "robocopy $g_MainBaseDir\\tf2\\bin\\. $g_MainBaseDir\\checkinbins\\tf2\\bin\\. /mir" ); &RunCmd( "robocopy $g_MainBaseDir\\platform\\servers\\. $g_MainBaseDir\\checkinbins\\platform\\servers\\. /mir" ); &RunCmd( "robocopy $g_MainBranchDir\\lib\\. $g_MainBaseDir\\checkinbins\\$g_MainBranchSubdir\\lib\\. /mir" ); # integrate from personal branch into main. . accept theirs. # TODO: need to check if main has a changelist or not and warn! &CD( $g_MainBranchDir ); &SaveMainTimeStampsBeforeIntegrate(); &RunCmd( "p4 integrate -r -b $g_LocalBranchName" ); &RunCmd( "p4 resolve -at ..." ); # revert unchanged files. my @unchanged = `p4 diff -sr`; my $file; foreach $file ( @unchanged ) { &RunCmd( "p4 revert $file" ); } print "Do \"checkin.pl 2\" when you are done reverting unchanging files and fixing up any other diffs in your main client.\n"; } elsif( $stage == 2 ) { &SetMainTimeStampsOnRevertedFiles(); # build main with the new changes &BuildMain(); # compare what we just built to what we saved off earlier my @filesToCheckOut; my @filesThatHaveNotChanged; &CompareDirs( \@filesToCheckOut, \@filesThatHaveNotChanged ); my $file; # $g_DebugStub = 1; foreach $file ( @filesThatHaveNotChanged ) { &RevertFile( $file ); } foreach $file ( @filesToCheckOut ) { &CheckoutFile( $file ); } print "-----------------\n"; print "Do \"checkin.pl 3\" when you are finished testing to checkin files and release the mutex.\n"; } elsif( $stage == 3 ) { my @filesToCheckOut; my @filesThatHaveNotChanged; &CompareDirs( \@filesToCheckOut, \@filesThatHaveNotChanged ); # TODO: check stuff in here and unlock p4 # TODO: go ahead and sync src_main to main so that they match # TODO: merge main into src again so any changes that you made while checking in are propogated &SyncMainCopySource(); }