source-engine/devtools/bin/mksln.pl

389 lines
8.9 KiB
Perl
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
#!perl
use XML::Simple;
use Data::Dumper;
use Getopt::Long;
use File::Basename;
use Cwd 'abs_path';
use Cwd;
use Digest::MD5 qw(md5 md5_hex md5_base64);
GetOptions( "verbose"=>\$verbose,
"projlist"=>\$projlist,
"x360"=>\$x360 );
$sln_name=shift || &PrintArgumentSummaryAndExit;
my $curdir=cwd;
$curdir=~ (m@(^.*/[a-z_]*src[0-9]?)@i) || die "Can't determine srcroot from current directory $curdir";
$srcroot=lc($1);
$linker_tool_name="VCLinkerTool";
$linker_tool_name="VCX360LinkerTool" if ($x360);
@output_only_projects_dependent_upon = ();
&ReadVPCProjects;
if ( $sln_name =~ /^\@(\S+)/)
{
$sln_name=$1;
&ReadGroup($1);
foreach $proj (@PROJS)
{
&AddProject(lc(abs_path($proj)),1);
}
if ( $projlist )
{
&WriteProjectListFile( $sln_name );
}
else
{
&WriteSolutionFile($sln_name);
}
}
else
{
# normal mode
while($_ = shift )
{
if ( /^\@(\S+)/)
{
# accept group names
&ReadGroup($1);
foreach $proj (@PROJS)
{
&AddProject(lc(abs_path($proj)),1);
}
}
elsif ( /^\*(\S+)/)
{
push( @output_only_projects_dependent_upon, lc( $1 ) );
&ReadGroup("everything");
foreach $proj (@PROJS)
{
&AddProject(lc(abs_path($proj)),0);
}
}
else
{
unless(/\.vcproj/) # if no extension specified, assume its a project name from projects.vgc
{
$_=$projpath{lc($_)} if length($projpath{lc($_)});
}
foreach $path (split(/\s+/,$_))
{
$path=~s@\s+@@g;
&AddProject(lc(abs_path($path)),1) if length($path);
}
}
}
if ( $projlist )
{
&WriteProjectListFile( $sln_name );
}
else
{
&WriteSolutionFile($sln_name);
}
}
sub WriteSolutionFile
{
local($sln)=@_;
$sln="$sln.sln" unless ( $sln=~/\./); # add extension if needed
if ( ( -e $sln && ( ! ( -w $sln ) ) ) )
{
print STDERR "$sln is write-protected. Doing p4 edit.\n";
print `p4 edit $sln`;
die "Failed to make $sln writeable" if ( ! ( -w $sln ) );
}
open(SLN,">$sln" ) || die "can't open output $sln";
# generate a guid for the sln
my $sln_guid="8BC9CEB8-8B4A-11D0-8D11-".substr(uc(md5_hex(basename($sln))),0,12);
print SLN "\xef\xbb\xbf\nMicrosoft Visual Studio Solution File, Format Version 9.00\n# Visual Studio 2005\n";
foreach $proj (@PROJECTS)
{
# check dependencies for "*" projects
if ( (! length( $force_project_inclusion{$proj} ) ) &&
( @output_only_projects_dependent_upon ))
{
my $skip_it = 1;
foreach $output_only ( @output_only_projects_dependent_upon )
{
$skip_it = 0 if ( $output_only eq lc( $proj ) );
foreach $lib (split(/,/,$depends_on{$proj}))
{
$skip_it = 0 if ( $output_only eq lc($lib) );
foreach $prvd (split(/,/,$provider{$lib}))
{
$skip_it = 0 if ( $output_only eq $prvd );
}
}
}
next if ( $skip_it );
}
print SLN "Project(\"{",$sln_guid,"}\") = \"$proj\", \"$relpath{$proj}\", \"$guid{$proj}\"\n";
#, now do dependencies
if ( length($depends_on{$proj} ) )
{
print SLN "\tProjectSection(ProjectDependencies) = postProject\n";
foreach $lib (split(/,/,$depends_on{$proj}))
{
if ( length($provider{$lib}) )
{
foreach $prvd (split(/,/,$provider{$lib}))
{
print SLN "\t\t$guid{$prvd} = $guid{$prvd}\n" if ( length($prvd) );
}
}
else
{
print "I don't know who provides $lib for $proj\n" if ( $verbose && length($lib) );
}
}
print SLN "\tEndProjectSection\n";
}
print SLN "EndProject\n";
}
print SLN "Global\n";
print SLN "\tGlobalSection(SolutionProperties) = preSolution\n";
print SLN "\t\tHideSolutionNode = FALSE\n";
print SLN "\tEndGlobalSection\n";
print SLN "EndGlobal\n";
close SLN;
}
sub WriteProjectListFile
{
local($txtfile) = @_;
$txtfile = "$txtfile.txt" unless ( $txtfile=~/\./); # add extension if needed
open(SLN,">$txtfile" ) || die "can't open output $txtfile";
foreach $proj (@PROJECTS)
{
# check dependencies for "*" projects
if ( (! length( $force_project_inclusion{$proj} ) ) &&
( @output_only_projects_dependent_upon ))
{
my $skip_it = 1;
foreach $output_only ( @output_only_projects_dependent_upon )
{
$skip_it = 0 if ( $output_only eq lc( $proj ) );
foreach $lib (split(/,/,$depends_on{$proj}))
{
$skip_it = 0 if ( $output_only eq lc($lib) );
foreach $prvd (split(/,/,$provider{$lib}))
{
$skip_it = 0 if ( $output_only eq $prvd );
}
}
}
next if ( $skip_it );
}
push @plist, $proj;
}
# now, we need to satisfy all dependencies
while( $#plist >= 0)
{
@worklist=@plist;
undef @plist;
PROJECT: foreach $proj( @worklist )
{
if ( length($depends_on{$proj} ) )
{
foreach $lib (split(/,/,$depends_on{$proj}))
{
if ( length($provider{$lib}) )
{
foreach $prvd (split(/,/,$provider{$lib}))
{
if ( length( $prvd ) && ( !$already_did{$prvd} ) )
{
push @plist, $proj; # can't do it yet
next PROJECT;
}
}
}
}
}
$already_did{$proj} = 1;
print SLN "$relpath{$proj}\n";
}
}
close SLN;
}
sub PrintArgumentSummaryAndExit
{
print "Format of command is\n";
my $switches="[ -projlist -verbose -x360 ]";
print "\t MKSLN $switches <solutionname.sln > proj1.vcproj proj2.vcproj ...\n";
print "OR\t MKSLN $switches \@vpcgroupname\n";
print "OR\t MKSLN $switches sln_name \@vpcgroupname\n";
print "OR\t MKSLN $switches sln_name *project create a solution including only that project and things dependent on it.\n";
}
sub AddProject
{
local($fname, $force )=@_;
local($/);
print "add project $fname\n" if ( $verbose );
open( VCP_IN, $fname ) || die "can't open $fname";
my $xmltext=<VCP_IN>;
close VCP_IN;
my $xml=XMLin($xmltext, forcearray => [ 'File', 'Filter' ] ); #, keyattr =>[ 'name', 'key', 'id', 'Name']);
my $pname=lc($xml->{Name});
$force_project_inclusion{$pname} = "yes" if ( $force );
return if ($already_processed{$pname} );
$already_processed{$pname}=1;
my $id=$xml->{ProjectGUID};
unless( length($id) )
{
die "project $fname doesn't have a guid. Generated by an old VPC?";
}
$id = "{".$id."}" unless( $id=~ /}/);
$guid{$pname}=$id;
push @PROJECTS,$pname;
$vcprojpath{$pname}=$fname;
#get targetname
my $targetname=$xml->{Name};
# get output target
my $tools=$xml->{Configurations}->{Configuration}[0];
# walk the tool list to see if this project outpus a .lib that something might depend on
my $outputtarget;
foreach $tool (@{$tools->{'Tool'}})
{
if ( $tool->{Name} eq "VCLibrarianTool" )
{
my $outputtarget=lc(basename($tool->{OutputFile}));
$provider{$outputtarget}.=",$pname";
print "$pname provides $outputtarget\n" if ($verbose);
}
if ( $tool->{Name} eq $linker_tool_name )
{
my $outputtarget=$tool->{'ImportLibrary'};
if ( length($outputtarget) )
{
$outputtarget=~s/\$\(TargetName\)/$targetname/i;
$outputtarget=lc(basename($outputtarget));
$outputtarget =~ s/\.lib/_360.lib/ if ( $x360 );
$provider{$outputtarget}=basename($pname);
print "$pname provides $outputtarget\n" if ($verbose);
}
}
}
foreach $filter (@{$xml->{Files}->{Filter}})
{
foreach $file (@{$filter->{File}})
{
my $f = lc($file->{RelativePath});
if ( $f=~/\.lib$/i) # library dependency
{
my $libname=basename($f);
$depends_on{$pname}.=",".$libname;
print "$pname depends on $libname\n" if ($verbose);
}
}
}
# generate relative pathname
$fname=~s@^$srcroot/@@i;
$fname=~s@/@\\@g;
$relpath{$pname}=$fname;
}
sub ReadGroup
{
local($matchgroup)=@_;
my $curmatch=0;
open(GROUPS,"$srcroot/vpc_scripts/groups.vgc") || die "can't open groups.vgc";
while(<GROUPS>)
{
&FixupVPCLine;
if (/^\$Group\s+(.*)$/)
{
my $groups=" $1 ";
$groups=~s@\"@@g;
$curmatch=0;
$curmatch=1 if ( $groups=~/ $matchgroup /i );
}
elsif ( $curmatch && (/^\s*\"([^\"]+)\"/) )
{
my $proj=lc($1);
my $path=$projpath{$proj};
if (length($path))
{
foreach $prj (split(/\s+/, $path))
{
$prj=~s@\s+@@g;
next unless (length($prj));
if ( -e $prj )
{
push @PROJS,$prj;
print "found proj $prj\n" if ($verbose);
}
else
{
print STDERR "can't find $prj\n";
}
}
}
else
{
print STDERR "couldn't find project name $proj (group = $matchgroup )\n";
}
}
else
{
$curmatch = 0 if (/\}/);
}
}
}
sub ReadVPCProjects
{
# group mode. ugh 100x harder to parse vpc than .vcproj
open(PROJS,"$srcroot/vpc_scripts/projects.vgc" ) || die "can't open projects.vgc";
while(<PROJS>)
{
&FixupVPCLine;
if (/^\s*\$Project\s+\"([^\"]+)\"/)
{
$curproj=$1;
}
elsif (/^\s*\"([^\"]+)\.vpc\"/)
{
my $base = $1;
$base="$base"."_x360" if ( $x360 );
$projpath{lc($curproj)}.=" $base.vcproj";
}
}
close PROJS;
}
sub FixupVPCLine
{
s@[\n\r]@@g;
s@//.*$@@g; # kill comments
# use [] skips. need something smarter here. for now, implicit /allgames except hl1 and portalmp
$_=undef if ( /\$HL1/);
$_=undef if ( /\$PORTALMP/);
}