Automated build system code (mainly for backup purposes - it won't work on anyone else's machine without a few bits of reconfiguration)

This was SVN commit r1641.
This commit is contained in:
Ykkrosh 2005-01-05 19:38:45 +00:00
parent 618c6cbbe7
commit ff34bf40b1
2 changed files with 498 additions and 0 deletions

View File

@ -0,0 +1,230 @@
# Build script - does the actual building of the project
use strict;
use warnings;
use constant EXIT_BUILDCOMPLETE => 0;
use constant EXIT_NOTCOMPILED => 1;
use constant EXIT_FAILED => 2;
use constant EXIT_ABORTED => 3;
my $svn_trunk = 'c:\0ad\trunk';
my $temp_trunk = 'r:\trunk';
my $output_dir = 'c:\0ad\builds';
my $log_dir = 'c:\0ad\autobuild';
my $sevenz = '"C:\Program Files\7-Zip\7z.exe"';
#my $vcbuild = '"C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\vcbuild"';
# except I need to call vcvars32.bat then "vcbuild.exe /useenv", since the VS registry settings don't always exist
my $vcbuild = '"C:\0ad\autobuild\vcbuild_env.bat"';
eval { # catch deaths
# Capture all output
open STDOUT, '>', "$log_dir\\build_stdout_temp.txt";
open STDERR, '>', "$log_dir\\build_stderr_temp.txt";
open BUILDLOG, '>', "$log_dir\\buildlog_temp.txt" or die $!;
our ($username, $password);
do 'login_details.pl' or die "Cannot find login details: $! / $@";
# login_details.pl contains:
# $::username = "philip";
# $::password = "something";
# (but with a valid password, which I'm not going to tell you)
chdir $svn_trunk or die $!;
if (grep { $_ eq '--commitlatest' } @ARGV)
{
add_to_buildlog("Committing latest code");
### Find the latest revision number ###
opendir my $dir, $output_dir or die $!;
my @revs = grep /^\d+$/, readdir $dir;
die unless @revs;
my $rev = (sort { $b <=> $a } @revs)[0];
add_to_buildlog("Committing ps.exe for revision $rev");
### Copy ps.exe over the SVN copy ###
`copy $output_dir\\$rev\\ps.exe $svn_trunk\\binaries\\system\\`;
die $? if $?;
### Commit ps.exe ###
my $svn_output = `svn commit binaries\\system\\ps.exe --username $username --password $password --message "Automated build." 2>&1`;
add_to_buildlog($svn_output);
die $? if $?;
# Just exit, and don't overwrite the earlier logs
exit(EXIT_NOTCOMPILED);
}
### Update from SVN ###
my $svn_output = `svn update --username $username --password $password 2>&1`;
add_to_buildlog($svn_output);
die $? if $?;
allow_abort();
$svn_output =~ /^(?:Updated to|At) revision (\d+)\.$/m or die;
my $svn_revision = $1;
if ($svn_output =~ m~^. (source(?!/tools)|build|libraries)~)
{
# The source has been updated.
}
else
{
# Nothing's changed. Build anyway?
if (grep { $_ eq '--force' } @ARGV)
{
# Yes
}
else
{
add_to_buildlog("*** Build $svn_revision not needed - no source changes ***");
# Just exit, and don't overwrite the earlier logs
exit(EXIT_NOTCOMPILED);
}
}
### Check whether we've already built this ###
if (-e "$output_dir\\$svn_revision")
{
add_to_buildlog("*** Build $svn_revision already exists ***");
# Just exit, and don't overwrite the earlier logs
exit(EXIT_NOTCOMPILED);
}
### Clean the RAM disk ###
`rmdir /q /s $temp_trunk 2>&1`;
# ignore failures - the RAM disk might have been recently reset
### Copy all the necessary files onto it ###
for (qw(source libraries build))
{
`xcopy /e $svn_trunk\\$_ $temp_trunk\\$_\\ 2>&1`;
die "xcopy $_: $?" if $?;
allow_abort();
}
### Create the workspace files ###
chdir "$temp_trunk\\build\\workspaces" or die $!;
my $updateworkspaces_output = `update-workspaces.bat 2>&1`;
add_to_buildlog($updateworkspaces_output);
die $? if $?;
### Create target directories for built files ###
mkdir "$temp_trunk\\binaries" or die $!;
mkdir "$temp_trunk\\binaries\\system" or die $!;
mkdir "$temp_trunk\\binaries\\data" or die $!;
allow_abort();
### Do the Testing build ###
my $build_output1 = `$vcbuild /time vc2003\\pyrogenesis.sln Testing 2>&1`;
add_to_buildlog($build_output1);
die $? if ($? and $? != 32768); # 32768 seems to be returned when it succeeds
allow_abort();
### Copy the output ###
`mkdir $output_dir\\temp`;
# ignore failures - this might already exist if the last build was aborted
`copy $temp_trunk\\binaries\\system\\ps_test.exe $output_dir\\temp\\`;
die $? if $?;
`copy $temp_trunk\\binaries\\data\\ps_test.pdb $output_dir\\temp\\`;
die $? if $?;
### Clean up unnecessary files to save space ###
`rmdir /q /s $temp_trunk\\build\\workspaces\\vc2003\\obj\\Testing 2>&1`;
die $? if $?;
`rmdir /q /s $temp_trunk\\binaries 2>&1`;
die $? if $?;
allow_abort();
### Recreate targets for built files ###
mkdir "$temp_trunk\\binaries" or die $!;
mkdir "$temp_trunk\\binaries\\system" or die $!;
mkdir "$temp_trunk\\binaries\\data" or die $!;
### Do the Release build ###
my $build_output2 = `$vcbuild /time vc2003\\pyrogenesis.sln Release 2>&1`;
add_to_buildlog($build_output2);
die $? if ($? and $? != 32768);
### Copy the output ###
`copy $temp_trunk\\binaries\\system\\ps.exe $output_dir\\temp\\`;
die $? if $?;
### Store the output permanently ###
rename "$output_dir\\temp", "$output_dir\\$svn_revision" or die $!;
### and make a compressed archive ###
chdir "$output_dir\\$svn_revision" or die $!;
`$sevenz a -mx9 -bd -sfx7zC.sfx ..\\$svn_revision.exe`;
die $? if $?;
# (TODO: delete the non-archived data when it's not needed, to save disk space)
}; # end of eval
if ($@)
{
warn $@;
quit(EXIT_FAILED);
}
else
{
quit(EXIT_BUILDCOMPLETE);
}
# Exit, after copying the current log files over the previous ones
sub quit
{
close BUILDLOG;
rename "$log_dir\\buildlog_temp.txt", "$log_dir\\buildlog.txt" or die $!;
close STDOUT;
rename "$log_dir\\build_stdout_temp.txt", "$log_dir\\build_stdout.txt" or die $!;
close STDERR;
rename "$log_dir\\build_stderr_temp.txt", "$log_dir\\build_stderr.txt" or die $!;
exit($_[0]);
}
sub add_to_buildlog
{
print BUILDLOG "$_[0]\n--------------------------------------------------------------------------------\n";
}
# Call this at strategic moments, to allow builds to be aborted (when e.g. there's another revision just come in that needs to be built instead)
sub allow_abort
{
if (-e "$log_dir\\build_abort")
{
add_to_buildlog("*** Build aborted ***\n");
unlink "$log_dir\\build_abort";
quit(EXIT_ABORTED);
}
}

View File

@ -0,0 +1,268 @@
# Build daemon - receives notifications of commits, runs the build process (ensuring there's only one copy at once), commits the built files, and provides access to all earlier builds and the build log of the latest.
use warnings;
use strict;
# Exit codes used by build.pl:
use constant EXIT_BUILDCOMPLETE => 0;
use constant EXIT_NOTCOMPILED => 1;
use constant EXIT_FAILED => 2;
use constant EXIT_ABORTED => 3;
use POE qw(Component::Server::TCP Filter::HTTPD Filter::Line);
use HTTP::Response;
use Win32::Process;
my $build_process; # stores Win32::Process handle
my $build_required = 0; # set by commits, cleared by builds
my $commit_required = 0; # stores the time when it should happen, or 0 if never
my $build_start_time; # time that the most recent build started
my $last_exit_code; # exit code of the most recent completed build
my $build_active = 0; # 0 => build process not running; 1 => build process running within the past second
use constant COMMIT_DELAY => 60*60; # seconds to wait after a code commit before committing ps.exe
open my $logfile, '>>', 'access_log' or die "Error opening access_log: $!";
$logfile->autoflush();
sub LOG {
my ($pkg, $file, $line) = caller;
my $msg = localtime()." - $file:$line - @_\n";
print $logfile $msg;
print $msg;
}
POE::Component::Server::TCP->new(
Alias => "web_server",
Port => 57470,
ClientFilter => 'POE::Filter::HTTPD',
ClientInput => sub {
my ($kernel, $heap, $request) = @_[KERNEL, HEAP, ARG0];
# Respond to errors in the client's request
if ($request->isa("HTTP::Response")) {
$heap->{client}->put($request);
$kernel->yield("shutdown");
return;
}
LOG $heap->{remote_ip}." - ".$request->uri->as_string;
my $response = HTTP::Response->new(200);
my $url = $request->uri->path;
if ($url eq '/commit_notify.html')
{
$build_required = 1;
abort_build();
$response->push_header('Content-type', 'text/plain');
$response->content("Build initiated.");
}
elsif ($url eq '/abort_build.html')
{
abort_build();
$response->push_header('Content-type', 'text/plain');
$response->content("Build aborted.");
}
elsif ($url eq '/status.html')
{
$response->push_header('Content-type', 'text/html');
my $text = <<EOF;
<html><body>
@{[ $build_active ? "Build in progress - ".(time()-$build_start_time)." seconds elapsed." : "Build not in progress." ]}
<br><br>
Last build status: @{[do{
if ($last_exit_code == EXIT_BUILDCOMPLETE or $last_exit_code == EXIT_NOTCOMPILED) {
"Succeeded";
} elsif ($last_exit_code == EXIT_ABORTED) {
"Aborted"
} else {
"FAILED ($last_exit_code)"
}
}]}.
<br><br>
Build log: (<a href="logs.html">complete logs</a>)
<br><br>
EOF
my $buildlog = do { local $/; my $f; open $f, 'buildlog.txt' and <$f> };
if ($buildlog)
{
$buildlog =~ s/&/&amp;/g;
$buildlog =~ s/</&lt;/g;
$buildlog =~ s/>/&gt;/g;
}
else
{
$buildlog = "(Error opening build log)";
}
$text .= "<pre>$buildlog</pre>";
$text .= qq{</body></html>};
$response->content($text);
}
elsif ($url eq '/logs.html')
{
$response->push_header('Content-type', 'text/plain');
my $text;
for my $n (qw(buildlog build_stdout build_stderr))
{
my $filedata = do { local $/; my $f; open $f, "$n.txt" and <$f> };
$text .= "--------------------------------------------------------------------------------\n";
$text .= "$n\n";
$text .= "--------------------------------------------------------------------------------\n";
$text .= $filedata;
$text .= "\n\n\n";
}
$response->content($text);
}
elsif ($url eq '/filelist.html')
{
my $output = '';
eval {
opendir my $d, "..\\builds" or die $!;
my @revs;
for (grep /^\d+\.exe$/, readdir $d)
{
/^(\d+)/;
push @revs, $1;
}
$output .= qq{<a href="download/$_.exe">$_</a> (}.int( (stat "..\\builds\\$_.exe")[7]/1024 ).qq{k)\n} for sort { $b <=> $a } @revs;
};
if ($@)
{
LOG "filelist failed: $@";
$response->push_header('Content-type', 'text/plain');
$response->content("Internal error.");
}
else
{
$response->push_header('Content-type', 'text/html');
$response->content("<html><body><pre>$output</pre></body></html>");
}
}
elsif ($url =~ m~/download/(\d+).exe~)
{
my $rev = $1;
if (-e "..\\builds\\$rev.exe" and open my $f, "..\\builds\\$rev.exe")
{
binmode $f;
$response->push_header('Content-type', 'application/octet-stream');
$response->content(do{local $/; <$f>});
}
else
{
LOG "Error serving file $url";
$response->push_header('Content-type', 'text/plain');
$response->content("File not found.");
}
}
elsif ($url eq '/favicon.ico')
{
$response = HTTP::Response->new(404);
$response->push_header('Content-type', 'text/html');
$response->content($response->error_as_HTML);
}
else
{
$response->push_header('Content-type', 'text/plain');
$response->content("Unrecognised request.");
}
$heap->{client}->put($response);
$kernel->yield("shutdown");
}
);
POE::Session->create(
inline_states => {
_start => sub {
$_[KERNEL]->delay(tick => 1);
},
tick => sub {
$_[KERNEL]->delay(tick => 1);
if ($build_active and not build_is_running())
{
# Build has just completed.
$last_exit_code = get_exit_code();
$build_active = 0;
undef $build_process;
LOG "Build complete ($last_exit_code)";
if ($last_exit_code == EXIT_BUILDCOMPLETE)
{
$commit_required = $build_start_time + COMMIT_DELAY;
}
else
{
$commit_required = 0;
}
}
if ($build_required and not $build_active)
{
start_build();
}
elsif ($commit_required and time() >= $commit_required)
{
start_build(commit => 1);
$commit_required = 0;
}
}
}
);
LOG "Starting kernel";
$poe_kernel->run();
LOG "Kernel exited";
exit 0;
sub build_is_running
{
my $exit_code = get_exit_code();
return (defined $exit_code and $exit_code == 259);
}
sub get_exit_code
{
return undef if not $build_process;
my $exit_code;
$build_process->GetExitCode($exit_code);
return $exit_code;
}
sub abort_build
{
LOG "Aborting build";
open my $f, '>', 'build_abort';
}
sub start_build
{
my %params = @_;
LOG "Starting build";
unlink 'build_abort';
$build_start_time = time;
Win32::Process::Create(
$build_process,
"c:\\perl\\bin\\perl.exe",
"perl build.pl" . ($params{commit} ? ' --commitlatest' : ''),
0,
CREATE_NO_WINDOW,
"c:\\0ad\\autobuild") or die "Error spawning build script: ".Win32::FormatMessage(Win32::GetLastError());
$build_required = 0;
$commit_required = 0;
$build_active = 1;
}