2014-05-26 16:15:09 -04:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
|
2015-10-03 13:44:20 -04:00
|
|
|
# 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<file>
|
|
|
|
|
|
|
|
The path to the file to be updated, required.
|
|
|
|
|
|
|
|
=item C<verbose>
|
|
|
|
|
|
|
|
If true, give more messages about what is being done.
|
|
|
|
|
|
|
|
=item C<quiet>
|
|
|
|
|
|
|
|
If true, don't output any non-error messages.
|
|
|
|
|
|
|
|
=item C<dryrun>
|
|
|
|
|
|
|
|
If true, don't really update the file but just output whether it would have
|
|
|
|
been updated or not. If C<verbose> is also true, also output the diff of the
|
|
|
|
changes that would have been done.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
This is meant to be used with C<update_xxx()> defined in different
|
|
|
|
Makefile::Update::Xxx modules.
|
|
|
|
|
|
|
|
Returns 1 if the file was changed or 0 otherwise.
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
|
|
Vadim Zeitlin <vz-cpan@zeitlins.org>
|
|
|
|
|
|
|
|
=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 (/<set var="(\w+)" hints="files">/ && exists $vars->{$1}) {
|
|
|
|
$var = $1;
|
|
|
|
%files = map { $_ => 0 } @{$vars->{$var}};
|
|
|
|
} elsif (defined $var) {
|
|
|
|
local $_ = $_;
|
|
|
|
s/<!-- .* -->//;
|
|
|
|
s/^\s+//;
|
|
|
|
s/\s+$//;
|
|
|
|
if (m{</set>}) {
|
|
|
|
# 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<files.bkl> 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 <vz-cpan@zeitlins.org>
|
|
|
|
|
|
|
|
=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 <ItemGroup> 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*<ItemGroup>$/) {
|
|
|
|
$in_group = 1;
|
|
|
|
} elsif ($line =~ m{^\s*</ItemGroup>$}) {
|
|
|
|
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{ <Cl$kind Include="$file" />\r\n};
|
|
|
|
|
|
|
|
$changed = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$in_sources = $in_headers = 0;
|
|
|
|
$files = undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
$in_group = 0;
|
|
|
|
} elsif ($in_group) {
|
|
|
|
if ($line =~ m{^\s*<Cl(?<kind>Compile|Include) Include="(?<file>[^"]+)"\s*(?<slash>/)?>$}) {
|
|
|
|
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 <ClCompile> or <ClInclude>
|
|
|
|
# tag, ignore everything until the matching closing one.
|
|
|
|
my $tag = "Cl$kind";
|
|
|
|
while (<$in>) {
|
|
|
|
last if m{^\s*</$tag>\r?\n$};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# In any case skip either this line containing the full
|
|
|
|
# <ClCompile/> 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 <ItemGroup> 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*<ItemGroup>?$/) {
|
|
|
|
$in_group = 1;
|
|
|
|
} elsif ($line =~ m{^\s*</ItemGroup>?$}) {
|
|
|
|
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<Cl$kind Include="$file"};
|
|
|
|
if (defined $filter) {
|
|
|
|
print $out ">\r\n$indent$indent$indent<Filter>$filter</Filter>\r\n$indent$indent</Cl$kind>\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*<Cl(?<kind>Compile|Include) Include="(?<file>[^"]+)"\s*(?<slash>/)?>?$}) {
|
|
|
|
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 <ClCompile> tag, get everything
|
|
|
|
# until the next </ClCompile>.
|
|
|
|
while (<$in>) {
|
|
|
|
$text .= $_;
|
|
|
|
last if m{^\s*</Cl$kind>\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<project.vcxproj> and its associated filters file
|
|
|
|
C<projects.vcxproj.filters>, 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<Makefile::Update::upmake> 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 <vz-cpan@zeitlins.org>
|
|
|
|
|
|
|
|
=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 =~ /^(?<indent>\s*)(?<file>[^ ]+)(?<tail>\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*(?<var>\S+)\s*(?::?=|:)(?<tail>[^=]*)$/) {
|
|
|
|
$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 <vz-cpan@zeitlins.org>
|
|
|
|
|
|
|
|
=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 "<File>" 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*<Filter$/) {
|
|
|
|
if (defined($filter)) {
|
|
|
|
warn qq{Nested <Filter> 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 <Filter> tag at line $..\n};
|
|
|
|
}
|
|
|
|
} elsif (defined $filter) {
|
|
|
|
if ($line =~ /^\s*<File$/) {
|
|
|
|
my $line_file_start = $line_with_eol;
|
|
|
|
|
|
|
|
$line_with_eol = <$in>;
|
|
|
|
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 </File> tag in any case.
|
|
|
|
while (<$in>) {
|
|
|
|
last if qr{^\s*</File>\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 <File> 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*</Filter>$}) {
|
|
|
|
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
|
|
|
|
\t\t\t<File
|
|
|
|
\t\t\t\tRelativePath="$file"$angle_bracket
|
|
|
|
\t\t\t</File>
|
|
|
|
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<update_vcproj()> can be used to update the list of headers and
|
|
|
|
sources in the given Visual C++ project file C<project.vcproj>:
|
|
|
|
|
|
|
|
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 <vz-cpan@zeitlins.org>
|
|
|
|
|
|
|
|
=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
|
|
|
|
|
|
|
|
|
2014-05-26 16:15:09 -04:00
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
use autodie;
|
|
|
|
|
|
|
|
use Getopt::Long;
|
|
|
|
|
|
|
|
use FindBin qw($Bin);
|
|
|
|
|
|
|
|
use Text::Upmake;
|
|
|
|
use Text::Upmake::Bakefile0;
|
|
|
|
use Text::Upmake::MSBuild;
|
|
|
|
|
|
|
|
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 <<EOF
|
2014-08-05 18:01:41 -04:00
|
|
|
Usage: $0 [--verbose] [--quiet] [--only-bkl] [--only-project=<name>]
|
2014-05-26 16:15:09 -04:00
|
|
|
|
|
|
|
Update the files used by bakefile and MSBuild 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.
|
|
|
|
EOF
|
|
|
|
;
|
|
|
|
|
|
|
|
if ($only_bkl && $only_msbuild) {
|
|
|
|
die qq{Options --only-bkl and --only-project or --only-version can't be used together.\n}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub log_upmake
|
|
|
|
{
|
|
|
|
my ($fname, @args) = @_;
|
|
|
|
|
|
|
|
if (upmake($fname, @args)) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
open my $files, '<', "$Bin/files";
|
|
|
|
my $vars = read_files_list($files);
|
|
|
|
|
|
|
|
if (!$only_msbuild) {
|
|
|
|
if (log_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 MSBuild 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)],
|
|
|
|
);
|
|
|
|
|
|
|
|
# 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
|
2015-02-14 19:01:51 -05:00
|
|
|
# common "include/" prefix is omitted, add it back here.
|
2014-05-26 16:15:09 -04:00
|
|
|
push @headers, "${top_srcdir}include/$_" for @{$vars->{"${var}_HDR"}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my @args = (\@sources, \@headers, \&filter_cb);
|
|
|
|
|
2014-08-05 18:01:41 -04:00
|
|
|
log_upmake("$Bin/msw/wx_${proj}.vcxproj", \&update_msbuild, @args);
|
|
|
|
log_upmake("$Bin/msw/wx_${proj}.vcxproj.filters", \&update_msbuild_filters, @args);
|
2014-05-26 16:15:09 -04:00
|
|
|
}
|
|
|
|
}
|