From 4b12516d42b6ec003d22cc78746992ebc0abc367 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 3 Oct 2015 19:44:20 +0200 Subject: [PATCH] Replace upmake and dependent modules with a monolithic script Remove the Perl modules composing upmake from wxWidgets repository, it's too difficult to maintain them both here and at https://github.com/vadz/upmake and just keep the wxWidgets-specific upmake_script.pl here and generate build/upmake_script itself from it using fatpack. This also updates upmake to the latest 0.3 version, as a side effect. --- .gitattributes | 3 + build/tools/upmake/lib/Text/Upmake.pm | 78 - .../tools/upmake/lib/Text/Upmake/Bakefile0.pm | 97 -- build/tools/upmake/lib/Text/Upmake/MSBuild.pm | 252 ---- build/tools/upmake/t/01_read_files_list.t | 24 - build/tools/upmake/t/02_update_bakefile_0.t | 47 - build/tools/upmake/t/03_update_msbuild.t | 50 - .../upmake/t/04_update_msbuild_filters.t | 67 - build/upmake | 1341 ++++++++++++++++- build/upmake_script.pl | 184 +++ 10 files changed, 1527 insertions(+), 616 deletions(-) delete mode 100644 build/tools/upmake/lib/Text/Upmake.pm delete mode 100644 build/tools/upmake/lib/Text/Upmake/Bakefile0.pm delete mode 100644 build/tools/upmake/lib/Text/Upmake/MSBuild.pm delete mode 100644 build/tools/upmake/t/01_read_files_list.t delete mode 100644 build/tools/upmake/t/02_update_bakefile_0.t delete mode 100644 build/tools/upmake/t/03_update_msbuild.t delete mode 100644 build/tools/upmake/t/04_update_msbuild_filters.t create mode 100755 build/upmake_script.pl diff --git a/.gitattributes b/.gitattributes index ce3d889b3b..fd833611ca 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,6 @@ config.guess eol=lf config.sub eol=lf configure eol=lf configure.in eol=lf + +# Ignore changes in the generated files. +build/upmake -diff diff --git a/build/tools/upmake/lib/Text/Upmake.pm b/build/tools/upmake/lib/Text/Upmake.pm deleted file mode 100644 index 2df09b5936..0000000000 --- a/build/tools/upmake/lib/Text/Upmake.pm +++ /dev/null @@ -1,78 +0,0 @@ -package Text::Upmake; - -use strict; -use warnings; -use autodie; - -use Exporter qw(import); - -our @EXPORT = qw(read_files_list upmake); - -=head1 NAME - -Text::Upmake - Update make files. - -=head1 SYNOPSIS - -=head1 AUTHOR - -Vadim Zeitlin - -=cut - -# Reads the file containing the file lists definitions and returns a hash ref -# with variable names as keys and refs to arrays of the file names as values. -# -# Takes an (open) file handle as argument. -sub read_files_list -{ - my ($fh) = @_; - - my ($var, %vars); - while (<$fh>) { - chomp; - s/#.*$//; - s/^\s+//; - s/\s+$//; - next if !$_; - - if (/^(\w+)\s*=$/) { - $var = $1; - } else { - die "Unexpected contents outside variable definition at line $.\n" - unless defined $var; - push @{$vars{$var}}, $_; - } - } - - return \%vars; -} - -# Update the file with the given name in place using the specified function and -# passing it the rest of the arguments. -# -# This is meant to be used with update_xxx() below. -sub upmake -{ - my ($fname, $updater, @args) = @_; - - my $fname_new = "$fname.upmake.new"; # TODO make it more unique - - open my $in, '<', $fname; - open my $out, '>', $fname_new; - - my $changed = $updater->($in, $out, @args); - - close $in; - close $out; - - if ($changed) { - rename $fname_new, $fname; - } else { - unlink $fname_new; - } - - $changed -} - -1; diff --git a/build/tools/upmake/lib/Text/Upmake/Bakefile0.pm b/build/tools/upmake/lib/Text/Upmake/Bakefile0.pm deleted file mode 100644 index 3ee1c55e1c..0000000000 --- a/build/tools/upmake/lib/Text/Upmake/Bakefile0.pm +++ /dev/null @@ -1,97 +0,0 @@ -package Text::Upmake::Bakefile0; - -use Exporter qw(import); -our @EXPORT = qw(update_bakefile_0); - -=head1 NAME - -Text::Upmake::Bakefile0 - Update bakefile-0.x files list. - -=head1 SYNOPSIS - -This is used exclusively to update wxWidgets C and is probably not -useful outside of wxWidgets project. - - use Text::Upmake::Bakefile0; - Text::Upmake::upmake('bakefiles/files.bkl', \&update_bakefile_0, $vars); - -=head1 SEE ALSO - -Text::Upmake - -=head1 AUTHOR - -Vadim Zeitlin - -=cut - -# Update file with variable definitions in bakefile-0 format with the data -# from the hash ref containing all the file lists. -# -# Takes the (open) file handles of the files to read and to write and the file -# lists hash ref as arguments. -# -# Returns 1 if any changes were made. -# -# The caller must take care of actually renaming the second file to the first -# one. -sub update_bakefile_0 -{ - my ($in, $out, $vars) = @_; - - # Variable whose contents is being currently replaced. - my $var; - - # Hash with files defined for the specified variable as keys and 0 or 1 - # depending on whether we have seen them in the input file as values. - my %files; - - # Set to 1 if we made any changes. - my $changed = 0; - while (<$in>) { - chomp; - - if (// && exists $vars->{$1}) { - $var = $1; - %files = map { $_ => 0 } @{$vars->{$var}}; - } elsif (defined $var) { - local $_ = $_; - s///; - s/^\s+//; - s/\s+$//; - if (m{}) { - # Check if we have any new files. - # - # TODO Insert them in alphabetical order. - while (my ($file, $seen) = each(%files)) { - if (!$seen) { - # This file was wasn't present in the input, add it. - # TODO Use proper indentation. - print $out " $file\n"; - - $changed = 1; - } - } - - undef $var; - } elsif ($_) { - if (not exists $files{$_}) { - # This file was removed. - $changed = 1; - next; - } - - if ($files{$_}) { - warn qq{Duplicate file "$_" in the definition of the } . - qq{variable "$var" at line $.\n} - } else { - $files{$_} = 1; - } - } - } - - print $out "$_\n"; - } - - $changed -} diff --git a/build/tools/upmake/lib/Text/Upmake/MSBuild.pm b/build/tools/upmake/lib/Text/Upmake/MSBuild.pm deleted file mode 100644 index 9ec1120866..0000000000 --- a/build/tools/upmake/lib/Text/Upmake/MSBuild.pm +++ /dev/null @@ -1,252 +0,0 @@ -package Text::Upmake::MSBuild; - -use Exporter qw(import); -our @EXPORT = qw(update_msbuild update_msbuild_filters); - -=head1 NAME - -Text::Upmake::MSBuild - Update list of sources and headers in MSBuild projects. - -=head1 SYNOPSIS - -Given an MSBuild project C and its associated filters file -C, the functions in this module can be used to update -the list of files in them to correspond to the given ones. - - use Text::Upmake::Bakefile0; - Text::Upmake::upmake('projects.vcxproj', \&update_msbuild, \@sources, \@headers); - Text::Upmake::upmake('projects.vcxproj.filters', \&update_msbuild_filters, \@sources, \@headers); - -=head1 SEE ALSO - -Text::Upmake - -=head1 AUTHOR - -Vadim Zeitlin - -=cut - -# Update sources and headers in an MSBuild project. -# -# Parameters: input and output file handles and array references to the sources -# and the headers to be used in this project. -# -# Returns 1 if any changes were made. -sub update_msbuild -{ - my ($in, $out, $sources, $headers) = @_; - - # Hashes mapping the sources/headers names to 1 if they have been seen in - # the project or 0 otherwise. - my %sources = map { $_ => 0 } @$sources; - my %headers = map { $_ => 0 } @$headers; - - # Reference to the hash corresponding to the files currently being - # processed. - my $files; - - # Set to 1 when we are inside any tag. - my $in_group = 0; - - # Set to 1 when we are inside an item group containing sources or headers - # respectively. - my ($in_sources, $in_headers) = 0; - - # Set to 1 if we made any changes. - my $changed = 0; - while (my $line_with_eol = <$in>) { - (my $line = $line_with_eol) =~ s/\r?\n?$//; - - if ($line =~ /^\s*$/) { - $in_group = 1; - } elsif ($line =~ m{^\s*$}) { - if (defined $files) { - my $kind = $in_sources ? 'Compile' : 'Include'; - - # Check if we have any new files. - # - # TODO Insert them in alphabetical order. - while (my ($file, $seen) = each(%$files)) { - if (!$seen) { - # Convert path separator to the one used by MSBuild. - $file =~ s@/@\\@g; - - print $out qq{ \n}; - - $changed = 1; - } - } - - $in_sources = $in_headers = 0; - } - - $in_group = 0; - } elsif ($in_group) { - if ($line =~ m{^\s*Compile|Include) Include="(?[^"]+)"\s*(?/)?>$}) { - if ($+{kind} eq 'Compile') { - warn "Mix of sources and headers at line $.\n" if $in_headers; - $in_sources = 1; - $files = \%sources; - } else { - warn "Mix of headers and sources at line $.\n" if $in_sources; - $in_headers = 1; - $files = \%headers; - } - - my $closed_tag = defined $+{slash}; - - # Normalize the path separator, we always use Unix ones but the - # project files use Windows one. - my $file = $+{file}; - $file =~ s@\\@/@g; - - if (not exists $files->{$file}) { - # This file was removed. - $changed = 1; - - if (!$closed_tag) { - # We have just the opening tag, ignore - # everything until the next - while (<$in>) { - last if m{^\s*$}; - } - } - - # In any case skip either this line containing the full - # tag or the line with the closing tag. - next; - } else { - if ($files->{$file}) { - warn qq{Duplicate file "$file" in the project at line $.\n}; - } else { - $files->{$file} = 1; - } - } - } - } - - print $out $line_with_eol; - } - - $changed -} - -# Update sources and headers in an MSBuild filters file. -# -# Parameters: input and output file handles, array references to the sources -# and the headers to be used in this project and a callback used to determine -# the filter for the new files. -# -# Returns 1 if any changes were made. -sub update_msbuild_filters -{ - my ($in, $out, $sources, $headers, $filter_cb) = @_; - - # Hashes mapping the sources/headers names to the text representing them in - # the input file if they have been seen in it or nothing otherwise. - my %sources = map { $_ => undef } @$sources; - my %headers = map { $_ => undef } @$headers; - - # Reference to the hash corresponding to the files currently being - # processed. - my $files; - - # Set to 1 when we are inside any tag. - my $in_group = 0; - - # Set to 1 when we are inside an item group containing sources or headers - # respectively. - my ($in_sources, $in_headers) = 0; - - # Set to 1 if we made any changes. - my $changed = 0; - while (my $line_with_eol = <$in>) { - (my $line = $line_with_eol) =~ s/\r?\n?$//; - - if ($line =~ /^\s*?$/) { - $in_group = 1; - } elsif ($line =~ m{^\s*?$}) { - if (defined $files) { - # Output the group contents now, all at once, inserting any new - # files: we must do it like this to ensure that they are - # inserted in alphabetical order. - my $kind = $in_sources ? 'Compile' : 'Include'; - - foreach my $file (sort keys %$files) { - if (defined $files->{$file}) { - print $out $files->{$file}; - } else { - my $filter = defined $filter_cb ? $filter_cb->($file) : undef; - - # Convert path separator to the one used by MSBuild. - $file =~ s@/@\\@g; - - my $indent = ' ' x 2; - - print $out qq{$indent$indent\n$indent$indent$indent$filter\n$indent$indent\n"; - } else { - print $out " />\n"; - } - - $changed = 1; - } - } - - $in_sources = $in_headers = 0; - $files = undef; - } - - $in_group = 0; - } elsif ($in_group && - $line =~ m{^\s*Compile|Include) Include="(?[^"]+)"\s*(?/)?>?$}) { - my $kind = $+{kind}; - if ($kind eq 'Compile') { - warn "Mix of sources and headers at line $.\n" if $in_headers; - $in_sources = 1; - $files = \%sources; - } else { - warn "Mix of headers and sources at line $.\n" if $in_sources; - $in_headers = 1; - $files = \%headers; - } - - my $closed_tag = defined $+{slash}; - - # Normalize the path separator, we always use Unix ones but the - # project files use Windows one. - my $file = $+{file}; - $file =~ s@\\@/@g; - - my $text = $line_with_eol; - if (!$closed_tag) { - # We have just the opening tag, get everything - # until the next . - while (<$in>) { - $text .= $_; - last if m{^\s*\r?\n?$}; - } - } - - if (not exists $files->{$file}) { - # This file was removed. - $changed = 1; - } else { - if ($files->{$file}) { - warn qq{Duplicate file "$file" in the project at line $.\n}; - } else { - $files->{$file} = $text; - } - } - - # Don't output this line yet, wait until the end of the group. - next - } - - print $out $line_with_eol; - } - - $changed -} diff --git a/build/tools/upmake/t/01_read_files_list.t b/build/tools/upmake/t/01_read_files_list.t deleted file mode 100644 index 042232be92..0000000000 --- a/build/tools/upmake/t/01_read_files_list.t +++ /dev/null @@ -1,24 +0,0 @@ -use strict; -use warnings; -use autodie; -use Test::More; - -BEGIN { use_ok('Text::Upmake'); } - -my $vars = read_files_list(*DATA); -is_deeply($vars->{VAR1}, [qw(file1 file2)], 'VAR1 has expected value'); -is_deeply($vars->{VAR2}, [qw(file3 file4)], 'VAR2 has expected value'); - -done_testing() - -__DATA__ -# Some comments - -VAR1 = - file1 - # comment between the files - file2 -VAR2 = - file3 - file4 # comment - # another comment diff --git a/build/tools/upmake/t/02_update_bakefile_0.t b/build/tools/upmake/t/02_update_bakefile_0.t deleted file mode 100644 index 76957bf2b7..0000000000 --- a/build/tools/upmake/t/02_update_bakefile_0.t +++ /dev/null @@ -1,47 +0,0 @@ -use strict; -use warnings; -use autodie; -use Test::More; - -BEGIN { use_ok('Text::Upmake::Bakefile0'); } - -my $vars = { - VAR1 => [qw(file1 file2 fileNew)], - VAR2 => [qw(file3 file4 file5 fileNew2)], - }; - -open my $out, '>', \my $outstr; -update_bakefile_0(*DATA, $out, $vars); - -note("Result: $outstr"); - -like($outstr, qr/file1/, 'existing file was preserved'); -like($outstr, qr/fileNew$/m, 'new file was added'); -unlike($outstr, qr/fileOld/, 'old file was removed'); -like($outstr, qr/fileNew2/, 'another new file was added'); -like($outstr, qr/file3\s+file4/s, 'files remain in correct order'); - -done_testing() - -__DATA__ - - - - - - - file1 - - file2 - - - - file3 - file4 - file5 - fileOld - - - diff --git a/build/tools/upmake/t/03_update_msbuild.t b/build/tools/upmake/t/03_update_msbuild.t deleted file mode 100644 index ceda9ceb65..0000000000 --- a/build/tools/upmake/t/03_update_msbuild.t +++ /dev/null @@ -1,50 +0,0 @@ -use strict; -use warnings; -use autodie; -use Test::More; - -use Text::Upmake; -BEGIN { use_ok('Text::Upmake::MSBuild'); } - -my $sources = [qw(file1.cpp file2.cpp fileNew.cpp)]; -my $headers = [qw(file1.h file2.h fileNew.h)]; - -open my $out, '>', \my $outstr; -update_msbuild(*DATA, $out, $sources, $headers); - -note("Result: $outstr"); - -like($outstr, qr/file1\.cpp/, 'existing source file was preserved'); -like($outstr, qr/fileNew\.cpp/m, 'new source file was added'); -unlike($outstr, qr/fileOld\.cpp/, 'old source file was removed'); -unlike($outstr, qr/file3\.h/, 'old header was removed'); -like($outstr, qr/fileNew\.h/, 'new header was added'); - -done_testing() - -__DATA__ - - - - - Debug - Win32 - - - - - - - - Create - - - - - - - - - - - diff --git a/build/tools/upmake/t/04_update_msbuild_filters.t b/build/tools/upmake/t/04_update_msbuild_filters.t deleted file mode 100644 index 3dc21c6e58..0000000000 --- a/build/tools/upmake/t/04_update_msbuild_filters.t +++ /dev/null @@ -1,67 +0,0 @@ -use strict; -use warnings; -use autodie; -use Test::More; - -use Text::Upmake; -BEGIN { use_ok('Text::Upmake::MSBuild'); } - -my $sources = [qw(file1.cpp file2.cpp file4.cpp fileNew.cpp)]; -my $headers = [qw(file1.h file2.h fileNew.h)]; - -sub filter_cb -{ - my ($file) = @_; - - return 'New Sources' if $file =~ /New\./; - - undef -} - -open my $out, '>', \my $outstr; -update_msbuild_filters(*DATA, $out, $sources, $headers, \&filter_cb); - -note("Result: $outstr"); - -like($outstr, qr/file1\.cpp/, 'existing source file was preserved'); -like($outstr, qr/fileNew\.cpp/m, 'new source file was added'); -unlike($outstr, qr/fileOld\.cpp/, 'old source file was removed'); -unlike($outstr, qr/file3\.cpp/, 'another old source file was removed'); -unlike($outstr, qr/file3\.h/, 'old header was removed'); -like($outstr, qr/fileNew\.h/, 'new header was added'); - -done_testing() - -__DATA__ - - - - - {...} - - - {...} - - - - - Common Sources - - - Common Sources - - - - - - - Common Headers - - - Common Headers - - - Common Headers - - - diff --git a/build/upmake b/build/upmake index 5824a95653..0a8f1190c8 100755 --- a/build/upmake +++ b/build/upmake @@ -1,5 +1,1345 @@ #!/usr/bin/env perl +# This chunk of stuff was generated by App::FatPacker. To find the original +# file's code, look for the end of this BEGIN block or the string 'FATPACK' +BEGIN { +my %fatpacked; + +$fatpacked{"Makefile/Update.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MAKEFILE_UPDATE'; + package Makefile::Update; + + # ABSTRACT: Update make files. + + use strict; + use warnings; + use autodie; + + use Exporter qw(import); + + our @EXPORT = qw(read_files_list upmake); + + our $VERSION = '0.3'; # VERSION + + + + sub read_files_list + { + my ($fh) = @_; + + my ($var, %vars); + while (<$fh>) { + chomp; + s/#.*$//; + s/^\s+//; + s/\s+$//; + next if !$_; + + if (/^(\w+)\s*=$/) { + $var = $1; + } else { + die "Unexpected contents outside variable definition at line $.\n" + unless defined $var; + if (/^\$(\w+)$/) { + my $name = $1; + die qq{Reference to undefined variable "$name" in the } . + qq{assignment to "$var" at line $.\n} + unless exists $vars{$name}; + my $value = $vars{$name}; + push @{$vars{$var}}, $_ for @$value; + } else { + push @{$vars{$var}}, $_; + } + } + } + + return \%vars; + } + + + sub upmake + { + my $file_or_options = shift; + my ($updater, @args) = @_; + + my ($fname, $verbose, $quiet, $dryrun); + if (ref $file_or_options eq 'HASH') { + $fname = $file_or_options->{file}; + $verbose = $file_or_options->{verbose}; + $quiet = $file_or_options->{quiet}; + $dryrun = $file_or_options->{dryrun}; + } else { + $fname = $file_or_options; + $verbose = + $quiet = + $dryrun = 0; + } + + if ($dryrun) { + my $old = do { + local $/; + open my $f, '<', $fname; + <$f> + }; + my $new = ''; + + open my $in, '<', \$old; + open my $out, '>', \$new; + + if ($updater->($in, $out, @args)) { + print qq{Would update "$fname"}; + + if ($verbose) { + if (eval { require Text::Diff; }) { + print " with the following changes:\n"; + + print Text::Diff::diff(\$old, \$new, { + FILENAME_A => $fname, + FILENAME_B => "$fname.new" + }); + } else { + print ".\n"; + + warn qq{Can't display diff of the changes, please install Text::Diff module.\n}; + } + } else { + print ".\n"; + } + } else { + print qq{Wouldn't change the file "$fname".\n}; + } + + return 0; + } + + my $fname_new = "$fname.upmake.new"; # TODO make it more unique + + open my $in, '<', $fname; + open my $out, '>', $fname_new; + + my $changed = $updater->($in, $out, @args); + + close $in; + close $out; + + if ($changed) { + rename $fname_new, $fname; + } else { + unlink $fname_new; + } + + if ($changed) { + print qq{File "$fname" successfully updated.\n} unless $quiet; + return 1; + } else { + print qq{No changes in the file "$fname".\n} if $verbose; + return 0; + } + } + + 1; + + __END__ + + =pod + + =encoding UTF-8 + + =head1 NAME + + Makefile::Update - Update make files. + + =head1 VERSION + + version 0.3 + + =head1 SYNOPSIS + + use Makefile::Update; + my $vars = read_files_list('files.lst'); + upmake('foo.vcxproj', $vars->{sources}, $vars->{headers}); + + =head1 FUNCTIONS + + =head2 read_files_list + + Reads the file containing the file lists definitions and returns a hash ref + with variable names as keys and refs to arrays of the file names as values. + + Takes an (open) file handle as argument. + + The file contents is supposed to have the following very simple format: + + # Comments are allowed and ignored. + # + # The variable definitions must always be in the format shown below, + # i.e. whitespace is significant and there should always be a single + # file per line. + sources = + file1.cpp + file2.cpp + + headers = + file1.h + file2.h + + # It is also possible to define variables in terms of other variables + # defined before it in the file (no forward references): + everything = + $sources + $headers + + =head2 upmake + + Update a file in place using the specified function and passing it the rest of + the arguments. + + The first parameter is either just the file path or a hash reference which may + contain the following keys: + + =over + + =item C + + The path to the file to be updated, required. + + =item C + + If true, give more messages about what is being done. + + =item C + + If true, don't output any non-error messages. + + =item C + + If true, don't really update the file but just output whether it would have + been updated or not. If C is also true, also output the diff of the + changes that would have been done. + + =back + + This is meant to be used with C defined in different + Makefile::Update::Xxx modules. + + Returns 1 if the file was changed or 0 otherwise. + + =head1 AUTHOR + + Vadim Zeitlin + + =head1 COPYRIGHT AND LICENSE + + This software is copyright (c) 2015 by Vadim Zeitlin. + + This is free software; you can redistribute it and/or modify it under + the same terms as the Perl 5 programming language system itself. + + =cut +MAKEFILE_UPDATE + +$fatpacked{"Makefile/Update/Bakefile0.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MAKEFILE_UPDATE_BAKEFILE0'; + package Makefile::Update::Bakefile0; + # ABSTRACT: Update bakefile-0.x files list. + + use Exporter qw(import); + our @EXPORT = qw(update_bakefile_0); + + use strict; + use warnings; + + our $VERSION = '0.3'; # VERSION + + + + sub update_bakefile_0 + { + my ($in, $out, $vars) = @_; + + # Variable whose contents is being currently replaced. + my $var; + + # Hash with files defined for the specified variable as keys and 0 or 1 + # depending on whether we have seen them in the input file as values. + my %files; + + # Set to 1 if we made any changes. + my $changed = 0; + while (<$in>) { + chomp; + + if (// && exists $vars->{$1}) { + $var = $1; + %files = map { $_ => 0 } @{$vars->{$var}}; + } elsif (defined $var) { + local $_ = $_; + s///; + s/^\s+//; + s/\s+$//; + if (m{}) { + # Check if we have any new files. + # + # TODO Insert them in alphabetical order. + while (my ($file, $seen) = each(%files)) { + if (!$seen) { + # This file was wasn't present in the input, add it. + # TODO Use proper indentation. + print $out " $file\n"; + + $changed = 1; + } + } + + undef $var; + } elsif ($_) { + if (not exists $files{$_}) { + # This file was removed. + $changed = 1; + next; + } + + if ($files{$_}) { + warn qq{Duplicate file "$_" in the definition of the } . + qq{variable "$var" at line $.\n} + } else { + $files{$_} = 1; + } + } + } + + print $out "$_\n"; + } + + $changed + } + + __END__ + + =pod + + =encoding UTF-8 + + =head1 NAME + + Makefile::Update::Bakefile0 - Update bakefile-0.x files list. + + =head1 VERSION + + version 0.3 + + =head1 SYNOPSIS + + This is used exclusively to update wxWidgets C and is probably not + useful outside of wxWidgets project. + + use Makefile::Update::Bakefile0; + Makefile::Update::upmake('bakefiles/files.bkl', \&update_bakefile_0, $vars); + + =head1 FUNCTIONS + + =head2 update_bakefile_0 + + Update file with variable definitions in bakefile-0 format with the data + from the hash ref containing all the file lists. + + Takes the (open) file handles of the files to read and to write and the file + lists hash ref as arguments. + + Returns 1 if any changes were made. + + =head1 SEE ALSO + + Makefile::Update + + =head1 AUTHOR + + Vadim Zeitlin + + =head1 COPYRIGHT AND LICENSE + + This software is copyright (c) 2015 by Vadim Zeitlin. + + This is free software; you can redistribute it and/or modify it under + the same terms as the Perl 5 programming language system itself. + + =cut +MAKEFILE_UPDATE_BAKEFILE0 + +$fatpacked{"Makefile/Update/MSBuild.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MAKEFILE_UPDATE_MSBUILD'; + package Makefile::Update::MSBuild; + # ABSTRACT: Update list of sources and headers in MSBuild projects. + + use Exporter qw(import); + our @EXPORT = qw(update_msbuild_project update_msbuild update_msbuild_filters); + + use strict; + use warnings; + + our $VERSION = '0.3'; # VERSION + + + + sub update_msbuild_project + { + my ($file_or_options, $sources, $headers) = @_; + + use Makefile::Update; + + if (!Makefile::Update::upmake($file_or_options, + \&update_msbuild, $sources, $headers + )) { + return 0; + } + + my $args; + if (ref $file_or_options eq 'HASH') { + # Need to make a copy to avoid modifying the callers hash. + $args = { %$file_or_options }; + $args->{file} .= ".filters" + } else { + $args = "$file_or_options.filters" + } + + return Makefile::Update::upmake($args, + \&update_msbuild_filters, $sources, $headers + ); + } + + + + sub update_msbuild + { + my ($in, $out, $sources, $headers) = @_; + + # Hashes mapping the sources/headers names to 1 if they have been seen in + # the project or 0 otherwise. + my %sources = map { $_ => 0 } @$sources; + my %headers = map { $_ => 0 } @$headers; + + # Reference to the hash corresponding to the files currently being + # processed. + my $files; + + # Set to 1 when we are inside any tag. + my $in_group = 0; + + # Set to 1 when we are inside an item group containing sources or headers + # respectively. + my ($in_sources, $in_headers) = 0; + + # Set to 1 if we made any changes. + my $changed = 0; + while (my $line_with_eol = <$in>) { + (my $line = $line_with_eol) =~ s/\r?\n?$//; + + if ($line =~ /^\s*$/) { + $in_group = 1; + } elsif ($line =~ m{^\s*$}) { + if (defined $files) { + my $kind = $in_sources ? 'Compile' : 'Include'; + + # Check if we have any new files. + # + # TODO Insert them in alphabetical order. + while (my ($file, $seen) = each(%$files)) { + if (!$seen) { + # Convert path separator to the one used by MSBuild. + $file =~ s@/@\\@g; + + print $out qq{ \r\n}; + + $changed = 1; + } + } + + $in_sources = $in_headers = 0; + $files = undef; + } + + $in_group = 0; + } elsif ($in_group) { + if ($line =~ m{^\s*Compile|Include) Include="(?[^"]+)"\s*(?/)?>$}) { + my $kind = $+{kind}; + if ($kind eq 'Compile') { + warn "Mix of sources and headers at line $.\n" if $in_headers; + $in_sources = 1; + $files = \%sources; + } else { + warn "Mix of headers and sources at line $.\n" if $in_sources; + $in_headers = 1; + $files = \%headers; + } + + my $closed_tag = defined $+{slash}; + + # Normalize the path separator, we always use Unix ones but the + # project files use Windows one. + my $file = $+{file}; + $file =~ s@\\@/@g; + + if (not exists $files->{$file}) { + # This file was removed. + $changed = 1; + + if (!$closed_tag) { + # We have just the opening or + # tag, ignore everything until the matching closing one. + my $tag = "Cl$kind"; + while (<$in>) { + last if m{^\s*\r?\n$}; + } + } + + # In any case skip either this line containing the full + # tag or the line with the closing tag. + next; + } else { + if ($files->{$file}) { + warn qq{Duplicate file "$file" in the project at line $.\n}; + } else { + $files->{$file} = 1; + } + } + } + } + + print $out $line_with_eol; + } + + $changed + } + + + sub update_msbuild_filters + { + my ($in, $out, $sources, $headers, $filter_cb) = @_; + + # Use standard/default classifier for the files if none is explicitly + # specified. + if (!defined $filter_cb) { + $filter_cb = sub { + my ($file) = @_; + + return 'Source Files' if $file =~ q{\.c(c|pp|xx|\+\+)?$}; + return 'Header Files' if $file =~ q{\.h(h|pp|xx|\+\+)?$}; + + warn qq{No filter defined for the file "$file".\n}; + + undef + } + } + + # Hashes mapping the sources/headers names to the text representing them in + # the input file if they have been seen in it or nothing otherwise. + my %sources = map { $_ => undef } @$sources; + my %headers = map { $_ => undef } @$headers; + + # Reference to the hash corresponding to the files currently being + # processed. + my $files; + + # Set to 1 when we are inside any tag. + my $in_group = 0; + + # Set to 1 when we are inside an item group containing sources or headers + # respectively. + my ($in_sources, $in_headers) = 0; + + # Set to 1 if we made any changes. + my $changed = 0; + while (my $line_with_eol = <$in>) { + (my $line = $line_with_eol) =~ s/\r?\n?$//; + + if ($line =~ /^\s*?$/) { + $in_group = 1; + } elsif ($line =~ m{^\s*?$}) { + if (defined $files) { + # Output the group contents now, all at once, inserting any new + # files: we must do it like this to ensure that they are + # inserted in alphabetical order. + my $kind = $in_sources ? 'Compile' : 'Include'; + + foreach my $file (sort keys %$files) { + if (defined $files->{$file}) { + print $out $files->{$file}; + } else { + my $filter = $filter_cb->($file); + + # Convert path separator to the one used by MSBuild. + $file =~ s@/@\\@g; + + my $indent = ' ' x 2; + + print $out qq{$indent$indent\r\n$indent$indent$indent$filter\r\n$indent$indent\r\n"; + } else { + print $out " />\r\n"; + } + + $changed = 1; + } + } + + $in_sources = $in_headers = 0; + $files = undef; + } + + $in_group = 0; + } elsif ($in_group && + $line =~ m{^\s*Compile|Include) Include="(?[^"]+)"\s*(?/)?>?$}) { + my $kind = $+{kind}; + if ($kind eq 'Compile') { + warn "Mix of sources and headers at line $.\n" if $in_headers; + $in_sources = 1; + $files = \%sources; + } else { + warn "Mix of headers and sources at line $.\n" if $in_sources; + $in_headers = 1; + $files = \%headers; + } + + my $closed_tag = defined $+{slash}; + + # Normalize the path separator, we always use Unix ones but the + # project files use Windows one. + my $file = $+{file}; + $file =~ s@\\@/@g; + + my $text = $line_with_eol; + if (!$closed_tag) { + # We have just the opening tag, get everything + # until the next . + while (<$in>) { + $text .= $_; + last if m{^\s*\r?\n?$}; + } + } + + if (not exists $files->{$file}) { + # This file was removed. + $changed = 1; + } else { + if ($files->{$file}) { + warn qq{Duplicate file "$file" in the project at line $.\n}; + } else { + $files->{$file} = $text; + } + } + + # Don't output this line yet, wait until the end of the group. + next + } + + print $out $line_with_eol; + } + + $changed + } + + __END__ + + =pod + + =encoding UTF-8 + + =head1 NAME + + Makefile::Update::MSBuild - Update list of sources and headers in MSBuild projects. + + =head1 VERSION + + version 0.3 + + =head1 SYNOPSIS + + Given an MSBuild project C and its associated filters file + C, the functions in this module can be used to update + the list of files in them to correspond to the given ones. + + use Makefile::Update::MSBuild; + upmake_msbuild_project('project.vcxproj', \@sources, \@headers); + + =head1 FUNCTIONS + + =head2 update_msbuild_project + + Update sources and headers in an MSBuild project and filter files. + + Pass the path of the project to update or a hash with the same keys as used by + C as the first parameter and the references to the + sources and headers arrays as the subsequent ones. + + Returns 1 if any changes were made, either to the project itself or to its + associated C<.filters> file. + + =head2 update_msbuild + + Update sources and headers in an MSBuild project. + + Parameters: input and output file handles and array references to the sources + and the headers to be used in this project. + + Returns 1 if any changes were made. + + =head2 update_msbuild_filters + + Update sources and headers in an MSBuild filters file. + + Parameters: input and output file handles, array references to the sources + and the headers to be used in this project and a callback used to determine + the filter for the new files. + + Returns 1 if any changes were made. + + =head1 SEE ALSO + + Makefile::Update, Makefile::Update::VCProj + + =head1 AUTHOR + + Vadim Zeitlin + + =head1 COPYRIGHT AND LICENSE + + This software is copyright (c) 2015 by Vadim Zeitlin. + + This is free software; you can redistribute it and/or modify it under + the same terms as the Perl 5 programming language system itself. + + =cut +MAKEFILE_UPDATE_MSBUILD + +$fatpacked{"Makefile/Update/Makefile.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MAKEFILE_UPDATE_MAKEFILE'; + package Makefile::Update::Makefile; + # ABSTRACT: Update lists of files in makefile variables. + + use Exporter qw(import); + our @EXPORT = qw(update_makefile); + + use strict; + use warnings; + + our $VERSION = '0.3'; # VERSION + + + + sub update_makefile + { + my ($in, $out, $vars) = @_; + + # Variable whose contents is being currently replaced and its original + # name in the makefile. + my ($var, $makevar); + + # Hash with files defined for the specified variable as keys and 0 or 1 + # depending on whether we have seen them in the input file as values. + my %files; + + # Array of lines in the existing makefile. + my @values; + + # True if the values are in alphabetical order: we use this to add new + # entries in alphabetical order too if the existing ones use it, otherwise + # we just append them at the end. + my $sorted = 1; + + # Extensions of the files in the files list (they're keys of this hash, + # the values are not used), there can be more than one (e.g. ".c" and + # ".cpp"). + my %src_exts; + + # Extension of the files in the makefiles: here there can also be more + # than one, but in this case we just give up and don't perform any + # extensions translation because we don't have enough information to do it + # (e.g. which extension should be used for the new files in the makefile?). + # Such case is indicated by make_ext being empty (as opposed to its + # initial undefined value). + my $make_ext; + + # Helper to get the extension. Note that the "extension" may be a make + # variable, e.g. the file could be something like "foo.$(obj)", so don't + # restrict it to just word characters. + sub _get_ext { $_[0] =~ /(\.\S+)$/ ? $1 : undef } + + # Indent and the part after the value (typically some amount of spaces and + # a backslash) for normal lines and, separately, for the last one, as it + # may or not have backslash after it. + my ($indent, $tail, $last_tail); + + # We can't use the usual check for EOF inside while itself because this + # wouldn't work for files with no new line after the last line, so check + # for the EOF manually. + my $eof = 0; + + # Set to 1 if we made any changes. + my $changed = 0; + while (1) { + my $line = <$in>; + if (defined $line) { + chomp $line; + } else { + $line = ''; + $eof = 1; + } + + # If we're inside the variable definition, parse the current line as + # another file name, + if (defined $var) { + if ($line =~ /^(?\s*)(?[^ ]+)(?\s*\\?)$/) { + if (defined $indent) { + warn qq{Inconsistent indent at line $. in the } . + qq{definition of the variable "$makevar".\n} + if $+{indent} ne $indent; + } else { + $indent = $+{indent}; + } + + $last_tail = $+{tail}; + my $file_orig = $+{file}; + + $tail = $last_tail if !defined $tail; + + # Check if we have something with the correct extension and + # preserve unchanged all the rest -- we don't want to remove + # expansions of other makefile variables from this one, for + # example, but such expansions would never be in the files + # list as they don't make sense for the other formats. + my $file = $file_orig; + if (defined (my $file_ext = _get_ext($file))) { + if (defined $make_ext) { + if ($file_ext ne $make_ext) { + # As explained in the comment before make_ext + # definition, just don't do anything in this case. + $make_ext = ''; + } + } else { + $make_ext = $file_ext; + } + + # We need to try this file with all of the source + # extensions we have as it can correspond to any of them. + for my $src_ext (keys %src_exts) { + if ($file_ext ne $src_ext) { + (my $file_try = $file) =~ s/\Q$file_ext\E$/$src_ext/; + if (exists $files{$file_try}) { + $file = $file_try; + last + } + } + } + + if (!exists $files{$file}) { + # This file was removed. + $changed = 1; + + # Don't store this line in @values below. + next; + } + } + + if (exists $files{$file}) { + if ($files{$file}) { + warn qq{Duplicate file "$file" in the definition of the } . + qq{variable "$makevar" at line $.\n} + } else { + $files{$file} = 1; + } + } + + # Are we still sorted? + if (@values && lc $line lt $values[-1]) { + $sorted = 0; + } + + push @values, $line; + next; + } + + # If the last line had a continuation character, the file list + # should only end if there is nothing else on the following line. + if ($last_tail =~ /\\$/ && $line =~ /\S/) { + warn qq{Expected blank line at line $..\n}; + } + + # End of variable definition, add new lines. + + # We can only map the extensions if we have a single extension to + # map them to (i.e. make_ext is not empty) and we only need to do + # it if are using more than one extension in the source files list + # or the single extension that we use is different from make_ext. + if (defined $make_ext) { + if ($make_ext eq '' || + (keys %src_exts == 1 && exists $src_exts{$make_ext})) { + undef $make_ext + } + } + + my $new_files = 0; + while (my ($file, $seen) = each(%files)) { + next if $seen; + + # This file was wasn't present in the input, add it. + + # If this is the first file we add, ensure that the last line + # present in the makefile so far has the line continuation + # character at the end as this might not have been the case. + if (!$new_files) { + $new_files = 1; + + if (@values && $values[-1] !~ /\\$/) { + $values[-1] .= $tail; + } + } + + # Next give it the right extension. + if (defined $make_ext) { + $file =~ s/\.\S+$/$make_ext/ + } + + # Finally store it. + push @values, "$indent$file$tail"; + } + + if ($new_files) { + $changed = 1; + + # Sort them if necessary using the usual Schwartzian transform. + if ($sorted) { + @values = map { $_->[0] } + sort { $a->[1] cmp $b->[1] } + map { [$_, lc $_] } @values; + } + + # Fix up the tail of the last line to be the same as that of + # the previous last line. + $values[-1] =~ s/\s*\\$/$last_tail/; + } + + undef $var; + + print $out join("\n", @values), "\n"; + } + + # We're only interested in variable or target declarations, and does + # not look like target-specific variable (this would contain an equal + # sign after the target). + if ($line =~ /^\s*(?\S+)\s*(?::?=|:)(?[^=]*)$/) { + $makevar = $+{var}; + my $tail = $+{tail}; + + # And only those of them for which we have values, but this is + # where it gets tricky as we try to be smart to accommodate common + # use patterns with minimal effort. + if (exists $vars->{$makevar}) { + $var = $makevar; + } else { + # Helper: return name if a variable with such name exists or + # undef otherwise. + my $var_if_exists = sub { exists $vars->{$_[0]} ? $_[0] : undef }; + + if ($makevar =~ /^objects$/i || $makevar =~ /^obj$/i) { + # Special case: map it to "sources" as we work with the + # source, not object, files. + $var = $var_if_exists->('sources'); + } elsif ($makevar =~ /^(\w+)_(objects|obj|sources|src)$/i) { + # Here we deal with "foo_sources" typically found in + # hand-written makefiles but also "foo_SOURCES" used in + # automake ones, but the latter also uses libfoo_a_SOURCES + # for static libraries and libfoo_la_SOURCES for the + # libtool libraries, be smart about it to allow defining + # just "foo" or "foo_sources" variables usable with all + # kinds of make/project files. + $var = $var_if_exists->($1) || $var_if_exists->("$1_sources"); + if (!defined $var && $2 eq 'SOURCES' && $1 =~ /^(\w+)_l?a$/) { + $var = $var_if_exists->($1) || $var_if_exists->("$1_sources"); + if (!defined $var && $1 =~ /^lib(\w+)$/) { + $var = $var_if_exists->($1) || $var_if_exists->("$1_sources"); + } + } + } elsif ($makevar =~ /^(\w+)\$\(\w+\)/) { + # This one is meant to catch relatively common makefile + # constructions like "target$(exe_ext)". + $var = $var_if_exists->($1); + } + } + + if (defined $var) { + if ($tail !~ /\s*\\$/) { + warn qq{Unsupported format for variable "$makevar" at line $..\n}; + undef $var; + } else { + %files = map { $_ => 0 } @{$vars->{$var}}; + + @values = (); + + # Find all the extensions used by files in this variable. + for my $file (@{$vars->{$var}}) { + if (defined (my $src_ext = _get_ext($file))) { + $src_exts{$src_ext} = 1; + } + } + + # Not known yet. + undef $make_ext; + + undef $indent; + $tail = $tail; + undef $last_tail; + + # Not unsorted so far. + $sorted = 1; + } + } + } + + print $out "$line"; + + # Don't add an extra new line at the EOF if it hadn't been there. + last if $eof; + + print $out "\n"; + } + + $changed + } + + __END__ + + =pod + + =encoding UTF-8 + + =head1 NAME + + Makefile::Update::Makefile - Update lists of files in makefile variables. + + =head1 VERSION + + version 0.3 + + =head1 SYNOPSIS + + This can be used to update the contents of a variable containing a list of + files in a makefile. + + use Makefile::Update::Makefile; + Makefile::Update::upmake('GNUmakefile', \&update_makefile, $vars); + + =head1 FUNCTIONS + + =head2 update_makefile + + Update variable definitions in a makefile format with the data from the hash + ref containing all the file lists. + + Only most straightforward cases of variable or target definitions are + recognized here, i.e. just "var := value", "var = value" or "target: value". + In particular we don't support any GNU make extensions such as "export" or + "override" without speaking of anything more complex. + + On top of it, currently the value should contain a single file per line with + none at all on the first line (but this restriction could be relaxed later if + needed), i.e. the only supported case is + + var = \ + foo \ + bar \ + baz + + and it must be followed by an empty line, too. + + Notice that if any of the "files" in the variable value looks like a makefile + variable, i.e. has "$(foo)" form, it is ignored by this function, i.e. not + removed even if it doesn't appear in the list of files (which will never be + the case normally). + + Takes the (open) file handles of the files to read and to write and the file + lists hash ref as arguments. + + Returns 1 if any changes were made. + + =head1 SEE ALSO + + Makefile::Update + + =head1 AUTHOR + + Vadim Zeitlin + + =head1 COPYRIGHT AND LICENSE + + This software is copyright (c) 2015 by Vadim Zeitlin. + + This is free software; you can redistribute it and/or modify it under + the same terms as the Perl 5 programming language system itself. + + =cut +MAKEFILE_UPDATE_MAKEFILE + +$fatpacked{"Makefile/Update/VCProj.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MAKEFILE_UPDATE_VCPROJ'; + package Makefile::Update::VCProj; + # ABSTRACT: Update list of sources and headers in Visual C++ projects. + + use Exporter qw(import); + our @EXPORT = qw(update_vcproj); + + use strict; + use warnings; + + our $VERSION = '0.3'; # VERSION + + + + sub update_vcproj + { + my ($in, $out, $sources, $headers, $filter_cb) = @_; + + # Use standard/default classifier for the files if none is explicitly + # specified. + if (!defined $filter_cb) { + $filter_cb = sub { + my ($file) = @_; + + return 'Source Files' if $file =~ q{\.c(c|pp|xx|\+\+)?$}; + return 'Header Files' if $file =~ q{\.h(h|pp|xx|\+\+)?$}; + + warn qq{No filter defined for the file "$file".\n}; + + undef + } + } + + # Hash mapping the filter to all the files using it (whether sources or + # headers). + my %files_by_filter; + foreach my $file (@$sources, @$headers) { + my $filter = $filter_cb->($file); + if (defined $filter) { + push @{$files_by_filter{$filter}}, $file + } + } + + # Name of the current filter, if any. + my $filter; + + # Hash containing 0 or 1 for each file using the current filter. + my %seen; + + # Indicates whether the closing angle bracket of "" tags is on its + # own line (which is how MSVS 2005 and 2008 format their files) or on the + # same line as "RelativePath" attribute (which is how MSVS 2003 does it). + my $angle_bracket_on_same_line = 0; + + # Set to 1 if we made any changes. + my $changed = 0; + + while (defined (my $line_with_eol = <$in>)) { + (my $line = $line_with_eol) =~ s/\r?\n$//; + + if ($line =~ /^\s* tag at line $. while parsing filter } . + qq{"$filter" is not supported.\n}; + next; + } + + print $out $line_with_eol; + $line_with_eol = <$in>; + if (defined $line_with_eol && + $line_with_eol =~ /^\s*Name="(.*)"\r?\n$/) { + $filter = $1; + if (!exists $files_by_filter{$filter}) { + # If we don't have any files for this filter, don't remove + # all the files from it, just skip it entirely instead. + undef $filter; + } else { + %seen = map { $_ => 0 } @{$files_by_filter{$filter}}; + } + } else { + warn qq{Unrecognized format for tag at line $..\n}; + } + } elsif (defined $filter) { + if ($line =~ /^\s*; + if (defined $line_with_eol && + $line_with_eol =~ /^\s*RelativePath="(.*)"(>?)\r?\n$/) { + $angle_bracket_on_same_line = $2 eq '>'; + + # Normalize path separators to Unix and remove the leading + # dot which MSVC likes to use for some reason. + (my $file = $1) =~ s@\\@/@g; + $file =~ s@^\./@@; + + # Special hack for resource files that sometimes occur in + # the "Source Files" section of MSVC projects too: don't + # remove them, even if they don't appear in the master + # files list, because they are never going to appear in it. + if ($file !~ /\.rc$/) { + if (!exists $seen{$file}) { + # This file is not in the master file list any + # more, delete it from the project file as well by + # not copying the lines corresponding to it to the + # output. + $changed = 1; + + # Skip the next line unless we had already seen + # the angle bracket. + if (!$angle_bracket_on_same_line) { + if (<$in> !~ /^\s*>\r?\n$/) { + warn qq{Expected closing '>' on the line $.\n} + } + } + + # And skip everything up to and including the + # closing tag in any case. + while (<$in>) { + last if qr{^\s*\r?\n$} + } + + next; + } + + # This file is still in the files list, mark it as seen. + if ($seen{$file}) { + warn qq{Duplicate file "$file" in the project at line $.\n}; + } else { + $seen{$file} = 1; + } + } + } else { + warn qq{Unrecognized format for tag inside filter } . + qq{"$filter" at line $..\n}; + } + + # Don't lose the original line, it won't be printed at the + # end of the loop any more. + print $out $line_file_start; + } elsif ($line =~ qr{^\s*$}) { + my $angle_bracket = $angle_bracket_on_same_line + ? '>' + : "\n\t\t\t\t>"; + + # Add new files, if any. + # + # TODO Insert them in alphabetical order. + while (my ($file, $seen) = each(%seen)) { + if (!$seen) { + # Convert path separator to the one used by MSVC. + $file =~ s@/@\\@g; + + # And use path even for the files in this directory. + $file = ".\\$file" if $file !~ /\\/; + + print $out < + END + ; + + $changed = 1; + } + } + + undef $filter; + } + } + + print $out $line_with_eol; + } + + $changed + } + + __END__ + + =pod + + =encoding UTF-8 + + =head1 NAME + + Makefile::Update::VCProj - Update list of sources and headers in Visual C++ projects. + + =head1 VERSION + + version 0.3 + + =head1 SYNOPSIS + + The function L can be used to update the list of headers and + sources in the given Visual C++ project file C: + + use Makefile::Update::VCProj; + upmake_msbuild_project('project.vcproj', \@sources, \@headers); + + =head1 FUNCTIONS + + =head2 update_vcproj + + Update sources and headers in a VC++ project. + + Parameters: input and output file handles, array references to the sources + and the headers to be used in this project and a callback used to determine + the filter for the new files. + + Returns 1 if any changes were made. + + =head1 SEE ALSO + + Makefile::Update, Makefile::Update::MSBuild + + =head1 AUTHOR + + Vadim Zeitlin + + =head1 COPYRIGHT AND LICENSE + + This software is copyright (c) 2015 by Vadim Zeitlin. + + This is free software; you can redistribute it and/or modify it under + the same terms as the Perl 5 programming language system itself. + + =cut +MAKEFILE_UPDATE_VCPROJ + +s/^ //mg for values %fatpacked; + +my $class = 'FatPacked::'.(0+\%fatpacked); +no strict 'refs'; +*{"${class}::files"} = sub { keys %{$_[0]} }; + +if ($] < 5.008) { + *{"${class}::INC"} = sub { + if (my $fat = $_[0]{$_[1]}) { + return sub { + return 0 unless length $fat; + $fat =~ s/^([^\n]*\n?)//; + $_ = $1; + return 1; + }; + } + return; + }; +} + +else { + *{"${class}::INC"} = sub { + if (my $fat = $_[0]{$_[1]}) { + open my $fh, '<', \$fat + or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; + return $fh; + } + return; + }; +} + +unshift @INC, bless \%fatpacked, $class; + } # END OF FATPACK CODE + + use strict; use warnings; use autodie; @@ -7,7 +1347,6 @@ use autodie; use Getopt::Long; use FindBin qw($Bin); -use lib "$Bin/tools/upmake/lib"; use Text::Upmake; use Text::Upmake::Bakefile0; diff --git a/build/upmake_script.pl b/build/upmake_script.pl new file mode 100755 index 0000000000..a07a372582 --- /dev/null +++ b/build/upmake_script.pl @@ -0,0 +1,184 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use autodie; + +use Getopt::Long; + +use FindBin qw($Bin); + +use Makefile::Update; +use Makefile::Update::Bakefile0; +use Makefile::Update::MSBuild; +use Makefile::Update::VCProj; + +my $verbose = 0; +my $quiet = 0; +my ($only_bkl, $only_msbuild, $only_project, $only_version); + +GetOptions( + 'verbose|v' => \$verbose, + 'quiet|q' => \$quiet, + 'only-bkl' => \$only_bkl, + 'only-project=s' => sub { $only_msbuild = 1; $only_project = $_[1] }, + 'only-version=i' => sub { $only_msbuild = 1; $only_version = $_[1] }, + ) or die <] [--only-version=] + +Update the files used by bakefile and MSVC projects from the master list +of files in build/files. + +If --no-xxx option is specified, the corresponding outputs are not updated. +By default everything is. + +The version argument of --only-version can be 7, 8, 9 or 10 with the latter +selecting the MSBuild projects. +EOF +; + +if ($only_bkl && $only_msbuild) { + die qq{Options --only-bkl and --only-project or --only-version can't be used together.\n} +} + +sub call_upmake +{ + my ($fname, @args) = @_; + + upmake({file => $fname, quiet => $quiet, verbose => $verbose}, @args) +} + +open my $files, '<', "$Bin/files"; +my $vars = read_files_list($files); + +if (!$only_msbuild) { + if (call_upmake("$Bin/bakefiles/files.bkl", \&update_bakefile_0, $vars)) { + print qq{Don't forget to run "bakefile_gen -b wx.bkl".} if $verbose; + } +} + +if (!$only_bkl) { + # Path to the project root directory from the directory containing the + # projects. + my $top_srcdir = '../../'; + + # The base names of all our projects with the list of variables + # containing the files that should appear in them. + my %projects_vars = ( + adv => [qw(ADVANCED_CMN ADVANCED_MSW ADVANCED_MSW_DESKTOP ADVANCED_MSW_NATIVE)], + aui => [qw(AUI_CMN)], + base => [qw(BASE_CMN BASE_AND_GUI_CMN BASE_WIN32 BASE_AND_GUI_WIN32)], + core => [qw(BASE_AND_GUI_CMN BASE_AND_GUI_WIN32 MSW_LOWLEVEL MSW_DESKTOP_LOWLEVEL MSW MSW_DESKTOP GUI_CMN)], + gl => [qw(OPENGL_CMN OPENGL_MSW)], + html => [qw(HTML_CMN HTML_MSW)], + media => [qw(MEDIA_CMN MEDIA_MSW MEDIA_MSW_DESKTOP)], + net => [qw(NET_CMN NET_WIN32)], + propgrid => [qw(PROPGRID)], + qa => [qw(QA)], + ribbon => [qw(RIBBON)], + stc => [qw(STC)], + webview => [qw(WEBVIEW_CMN WEBVIEW_MSW)], + xml => [qw(XML)], + xrc => [qw(XRC)], + ); + + # The versions of non-MSBuild projects (MSBuild ones all use version "10"). + my @vcproj_versions = qw(7 8 9); + + # Return the "filter" to use for the given file. + sub filter_cb + { + my ($file) = @_; + + my %filters = ( + 'src/common/.*' => 'Common Sources', + 'src/gtk/.*' => 'GTK+ Sources', + 'src/msw/.*' => 'MSW Sources', + 'src/generic/.*' => 'Generic Sources', + 'src/univ/.*' => 'wxUniv Sources', + 'src/html/.*' => 'wxHTML Sources', + 'include/.*/setup.h' => 'Setup Headers', + 'include/wx/gtk/.*' => 'GTK+ Headers', + 'include/wx/msw/.*' => 'MSW Headers', + 'include/wx/generic/.*' => 'Generic Headers', + 'include/wx/univ/.*' => 'wxUniv Headers', + 'include/wx/html/.*' => 'wxHTML Headers', + ); + + foreach (keys %filters) { + return $filters{$_} if $file =~ qr{^${top_srcdir}$_$}; + } + + # Two fall backs which can't be used in the hash as they must be + # checked after the other patterns. + return 'Source Files' if $file =~ q{src/.*}; + return 'Common Headers' if $file =~ q{include/wx/.*}; + + warn qq{No filter defined for the file "$file".\n}; + + undef + } + + foreach my $proj (sort keys %projects_vars) { + next if defined $only_project && $proj ne $only_project; + + my (@sources, @headers); + + # All our projects use the special dummy file for PCH creation, but it's + # not included in the file lists. + push @sources, "${top_srcdir}src/common/dummy.cpp"; + + foreach my $var (@{$projects_vars{$proj}}) { + # The paths in the files lists are relative to the project root, + # so add relative path to it from the projects directory. + push @sources, "${top_srcdir}$_" for @{$vars->{"${var}_SRC"}}; + + # It is possible that we don't have any headers of some kind at all. + if (exists $vars->{"${var}_HDR"}) { + # Our files lists don't use the full path for the headers, the + # common "include/" prefix is omitted, add it back here. + push @headers, "${top_srcdir}include/$_" for @{$vars->{"${var}_HDR"}}; + } + } + + my @args = (\@sources, \@headers, \&filter_cb); + + # First deal with MSBuild project, it's the simplest case. + if (!defined $only_version || $only_version == 10) { + call_upmake("$Bin/msw/wx_${proj}.vcxproj", \&update_msbuild, @args); + call_upmake("$Bin/msw/wx_${proj}.vcxproj.filters", \&update_msbuild_filters, @args); + } + + # Pre-MSBuild projects also put this header together with all the other + # ones, so it needs to be added (with MSBuild it's a + # element and so is completely separate from the real headers that use + # ). + push @headers, "${top_srcdir}include/wx/msw/genrcdefs.h"; + + # And core project also includes all GUI headers instead of just those + # it really uses. + if ($proj eq 'core') { + foreach my $gui_proj (grep { $_ ne 'base' && + $_ ne 'core' && + $_ ne 'net' && + $_ ne 'xml' } keys %projects_vars) { + foreach my $var (@{$projects_vars{$gui_proj}}) { + push @headers, "${top_srcdir}include/$_" for @{$vars->{"${var}_HDR"}}; + } + } + } + + # For compatibility with the existing bakefile-generated projects, + # don't include *.cpp files in the list of headers -- even these files + # are actually used as headers (i.e. they are #include'd). + if ($proj eq 'base') { + @headers = grep { $_ !~ /\.cpp$/ } @headers; + } + + foreach my $ver (@vcproj_versions) { + next if defined $only_version && $ver != $only_version; + + call_upmake("$Bin/msw/wx_vc${ver}_${proj}.vcproj", \&update_vcproj, @args); + } + } +}