#!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 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=; 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() { &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() { &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/); }