438 lines
10 KiB
Perl
Executable File
438 lines
10 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
# Copyright (c) 2021-2023, PostgreSQL Global Development Group
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Cwd qw(abs_path getcwd);
|
|
use File::Find;
|
|
use File::Spec qw(devnull);
|
|
use File::Temp;
|
|
use IO::Handle;
|
|
use Getopt::Long;
|
|
|
|
# Update for pg_bsd_indent version
|
|
my $INDENT_VERSION = "2.1.1";
|
|
|
|
# Our standard indent settings
|
|
my $indent_opts =
|
|
"-bad -bap -bbb -bc -bl -cli1 -cp33 -cdb -nce -d0 -di12 -nfc1 -i4 -l79 -lp -lpl -nip -npro -sac -tpg -ts4";
|
|
|
|
my $devnull = File::Spec->devnull;
|
|
|
|
my ($typedefs_file, $typedef_str,
|
|
@excludes, $indent, $build,
|
|
$show_diff, $silent_diff, $help,
|
|
@commits,);
|
|
|
|
$help = 0;
|
|
|
|
my %options = (
|
|
"help" => \$help,
|
|
"commit=s" => \@commits,
|
|
"typedefs=s" => \$typedefs_file,
|
|
"list-of-typedefs=s" => \$typedef_str,
|
|
"excludes=s" => \@excludes,
|
|
"indent=s" => \$indent,
|
|
"show-diff" => \$show_diff,
|
|
"silent-diff" => \$silent_diff,);
|
|
GetOptions(%options) || usage("bad command line argument");
|
|
|
|
usage() if $help;
|
|
|
|
usage("Cannot have both --silent-diff and --show-diff")
|
|
if $silent_diff && $show_diff;
|
|
|
|
usage("Cannot use --commit with command line file list")
|
|
if (@commits && @ARGV);
|
|
|
|
# command line option wins, then environment, then locations based on current
|
|
# dir, then default location
|
|
$typedefs_file ||= $ENV{PGTYPEDEFS};
|
|
|
|
# get indent location for environment or default
|
|
$indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent";
|
|
|
|
my $sourcedir = locate_sourcedir();
|
|
|
|
# if it's the base of a postgres tree, we will exclude the files
|
|
# postgres wants excluded
|
|
if ($sourcedir)
|
|
{
|
|
my $exclude_candidate = "$sourcedir/exclude_file_patterns";
|
|
push (@excludes, $exclude_candidate) if -f $exclude_candidate;
|
|
}
|
|
|
|
# The typedef list that's mechanically extracted by the buildfarm may omit
|
|
# some names we want to treat like typedefs, e.g. "bool" (which is a macro
|
|
# according to <stdbool.h>), and may include some names we don't want
|
|
# treated as typedefs, although various headers that some builds include
|
|
# might make them so. For the moment we just hardwire a list of names
|
|
# to add and a list of names to exclude; eventually this may need to be
|
|
# easier to configure. Note that the typedefs need trailing newlines.
|
|
my @additional = ("bool\n");
|
|
|
|
my %excluded = map { +"$_\n" => 1 } qw(
|
|
ANY FD_SET U abs allocfunc boolean date digit ilist interval iterator other
|
|
pointer printfunc reference string timestamp type wrap
|
|
);
|
|
|
|
# globals
|
|
my @files;
|
|
my $filtered_typedefs_fh;
|
|
|
|
sub check_indent
|
|
{
|
|
system("$indent -? < $devnull > $devnull 2>&1");
|
|
if ($? >> 8 != 1)
|
|
{
|
|
print STDERR
|
|
"You do not appear to have $indent installed on your system.\n";
|
|
exit 1;
|
|
}
|
|
|
|
if (`$indent --version` !~ m/ $INDENT_VERSION /)
|
|
{
|
|
print STDERR
|
|
"You do not appear to have $indent version $INDENT_VERSION installed on your system.\n";
|
|
exit 1;
|
|
}
|
|
|
|
system("$indent -gnu < $devnull > $devnull 2>&1");
|
|
if ($? == 0)
|
|
{
|
|
print STDERR
|
|
"You appear to have GNU indent rather than BSD indent.\n";
|
|
exit 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub locate_sourcedir
|
|
{
|
|
# try fairly hard to locate the sourcedir
|
|
my $sub = "./src/tools/pgindent";
|
|
return $sub if -d $sub;
|
|
# try to find it from an ancestor directory
|
|
$sub = "../src/tools/pgindent";
|
|
foreach (1..4)
|
|
{
|
|
return $sub if -d $sub;
|
|
$sub = "../$sub";
|
|
}
|
|
return; # undef if nothing found
|
|
}
|
|
|
|
sub load_typedefs
|
|
{
|
|
# try fairly hard to find the typedefs file if it's not set
|
|
|
|
foreach my $try ('.', $sourcedir, '/usr/local/etc')
|
|
{
|
|
last if $typedefs_file;
|
|
next unless defined $try;
|
|
$typedefs_file = "$try/typedefs.list"
|
|
if (-f "$try/typedefs.list");
|
|
}
|
|
|
|
die "cannot locate typedefs file \"$typedefs_file\"\n"
|
|
unless $typedefs_file && -f $typedefs_file;
|
|
|
|
open(my $typedefs_fh, '<', $typedefs_file)
|
|
|| die "cannot open typedefs file \"$typedefs_file\": $!\n";
|
|
my @typedefs = <$typedefs_fh>;
|
|
close($typedefs_fh);
|
|
|
|
# add command-line-supplied typedefs?
|
|
if (defined($typedef_str))
|
|
{
|
|
foreach my $typedef (split(m/[, \t\n]+/, $typedef_str))
|
|
{
|
|
push(@typedefs, $typedef . "\n");
|
|
}
|
|
}
|
|
|
|
# add additional entries
|
|
push(@typedefs, @additional);
|
|
|
|
# remove excluded entries
|
|
@typedefs = grep { !$excluded{$_} } @typedefs;
|
|
|
|
# write filtered typedefs
|
|
my $filter_typedefs_fh = new File::Temp(TEMPLATE => "pgtypedefXXXXX");
|
|
print $filter_typedefs_fh @typedefs;
|
|
$filter_typedefs_fh->close();
|
|
|
|
# temp file remains because we return a file handle reference
|
|
return $filter_typedefs_fh;
|
|
}
|
|
|
|
sub process_exclude
|
|
{
|
|
foreach my $excl (@excludes)
|
|
{
|
|
last unless @files;
|
|
open(my $eh, '<', $excl)
|
|
|| die "cannot open exclude file \"$excl\"\n";
|
|
while (my $line = <$eh>)
|
|
{
|
|
chomp $line;
|
|
next if $line =~ m/^#/;
|
|
my $rgx = qr!$line!;
|
|
@files = grep { $_ !~ /$rgx/ } @files if $rgx;
|
|
}
|
|
close($eh);
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub read_source
|
|
{
|
|
my $source_filename = shift;
|
|
my $source;
|
|
|
|
open(my $src_fd, '<', $source_filename)
|
|
|| die "cannot open file \"$source_filename\": $!\n";
|
|
local ($/) = undef;
|
|
$source = <$src_fd>;
|
|
close($src_fd);
|
|
|
|
return $source;
|
|
}
|
|
|
|
sub write_source
|
|
{
|
|
my $source = shift;
|
|
my $source_filename = shift;
|
|
|
|
open(my $src_fh, '>', $source_filename)
|
|
|| die "cannot open file \"$source_filename\": $!\n";
|
|
print $src_fh $source;
|
|
close($src_fh);
|
|
return;
|
|
}
|
|
|
|
sub pre_indent
|
|
{
|
|
my $source = shift;
|
|
|
|
## Comments
|
|
|
|
# Convert // comments to /* */
|
|
$source =~ s!^([ \t]*)//(.*)$!$1/* $2 */!gm;
|
|
|
|
# Adjust dash-protected block comments so indent won't change them
|
|
$source =~ s!/\* +---!/*---X_X!g;
|
|
|
|
## Other
|
|
|
|
# Prevent indenting of code in 'extern "C"' blocks.
|
|
# we replace the braces with comments which we'll reverse later
|
|
my $extern_c_start = '/* Open extern "C" */';
|
|
my $extern_c_stop = '/* Close extern "C" */';
|
|
$source =~
|
|
s!(^#ifdef[ \t]+__cplusplus.*\nextern[ \t]+"C"[ \t]*\n)\{[ \t]*$!$1$extern_c_start!gm;
|
|
$source =~ s!(^#ifdef[ \t]+__cplusplus.*\n)\}[ \t]*$!$1$extern_c_stop!gm;
|
|
|
|
# Protect wrapping in CATALOG()
|
|
$source =~ s!^(CATALOG\(.*)$!/*$1*/!gm;
|
|
|
|
return $source;
|
|
}
|
|
|
|
sub post_indent
|
|
{
|
|
my $source = shift;
|
|
|
|
# Restore CATALOG lines
|
|
$source =~ s!^/\*(CATALOG\(.*)\*/$!$1!gm;
|
|
|
|
# Put back braces for extern "C"
|
|
$source =~ s!^/\* Open extern "C" \*/$!{!gm;
|
|
$source =~ s!^/\* Close extern "C" \*/$!}!gm;
|
|
|
|
## Comments
|
|
|
|
# Undo change of dash-protected block comments
|
|
$source =~ s!/\*---X_X!/* ---!g;
|
|
|
|
# Fix run-together comments to have a tab between them
|
|
$source =~ s!\*/(/\*.*\*/)$!*/\t$1!gm;
|
|
|
|
## Functions
|
|
|
|
# Use a single space before '*' in function return types
|
|
$source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm;
|
|
|
|
return $source;
|
|
}
|
|
|
|
sub run_indent
|
|
{
|
|
my $source = shift;
|
|
my $error_message = shift;
|
|
|
|
my $cmd = "$indent $indent_opts -U" . $filtered_typedefs_fh->filename;
|
|
|
|
my $tmp_fh = new File::Temp(TEMPLATE => "pgsrcXXXXX");
|
|
my $filename = $tmp_fh->filename;
|
|
print $tmp_fh $source;
|
|
$tmp_fh->close();
|
|
|
|
$$error_message = `$cmd $filename 2>&1`;
|
|
|
|
return "" if ($? || length($$error_message) > 0);
|
|
|
|
unlink "$filename.BAK";
|
|
|
|
open(my $src_out, '<', $filename);
|
|
local ($/) = undef;
|
|
$source = <$src_out>;
|
|
close($src_out);
|
|
|
|
return $source;
|
|
}
|
|
|
|
sub show_diff
|
|
{
|
|
my $indented = shift;
|
|
my $source_filename = shift;
|
|
|
|
my $post_fh = new File::Temp(TEMPLATE => "pgdiffXXXXX");
|
|
my $post_fh_filename = $post_fh->filename;
|
|
|
|
print $post_fh $indented;
|
|
|
|
$post_fh->close();
|
|
|
|
my $diff = `diff -upd "$source_filename" "$post_fh_filename" 2>&1`;
|
|
return $diff;
|
|
}
|
|
|
|
sub usage
|
|
{
|
|
my $message = shift;
|
|
my $helptext = <<'EOF';
|
|
Usage:
|
|
pgindent [OPTION]... [FILE]...
|
|
Options:
|
|
--help show this message and quit
|
|
--commit=gitref use files modified by the named commit
|
|
--typedefs=FILE file containing a list of typedefs
|
|
--list-of-typedefs=STR string containing typedefs, space separated
|
|
--excludes=PATH file containing list of filename patterns to ignore
|
|
--indent=PATH path to pg_bsd_indent program
|
|
--show-diff show the changes that would be made
|
|
--silent-diff exit with status 2 if any changes would be made
|
|
The --excludes and --commit options can be given more than once.
|
|
EOF
|
|
if ($help)
|
|
{
|
|
print $helptext;
|
|
exit 0;
|
|
}
|
|
else
|
|
{
|
|
print STDERR "$message\n", $helptext;
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
# main
|
|
|
|
$filtered_typedefs_fh = load_typedefs();
|
|
|
|
check_indent();
|
|
|
|
my $wanted = sub
|
|
{
|
|
my ($dev, $ino, $mode, $nlink, $uid, $gid);
|
|
(($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
|
|
&& -f _
|
|
&& /^.*\.[ch]\z/s
|
|
&& push(@files, $File::Find::name);
|
|
};
|
|
|
|
# any non-option arguments are files or directories to be processed
|
|
File::Find::find({wanted => $wanted}, @ARGV) if @ARGV;
|
|
|
|
# commit file locations are relative to the source root
|
|
chdir "$sourcedir/../../.." if @commits && $sourcedir;
|
|
|
|
# process named commits by comparing each with their immediate ancestor
|
|
foreach my $commit (@commits)
|
|
{
|
|
my $prev="$commit~";
|
|
my @affected=`git diff --diff-filter=ACMR --name-only $prev $commit`;
|
|
die "git error" if $?;
|
|
chomp(@affected);
|
|
push(@files,@affected);
|
|
}
|
|
|
|
warn "No files to process" unless @files;
|
|
|
|
# remove excluded files from the file list
|
|
process_exclude();
|
|
|
|
my %processed;
|
|
|
|
foreach my $source_filename (@files)
|
|
{
|
|
# skip duplicates
|
|
next if $processed{$source_filename};
|
|
$processed{$source_filename} = 1;
|
|
|
|
# ignore anything that's not a .c or .h file
|
|
next unless $source_filename =~ /\.[ch]$/;
|
|
|
|
# don't try to indent a file that doesn't exist
|
|
unless (-f $source_filename)
|
|
{
|
|
warn "Could not find $source_filename";
|
|
next;
|
|
}
|
|
# Automatically ignore .c and .h files that correspond to a .y or .l
|
|
# file. indent tends to get badly confused by Bison/flex output,
|
|
# and there's no value in indenting derived files anyway.
|
|
my $otherfile = $source_filename;
|
|
$otherfile =~ s/\.[ch]$/.y/;
|
|
next if $otherfile ne $source_filename && -f $otherfile;
|
|
$otherfile =~ s/\.y$/.l/;
|
|
next if $otherfile ne $source_filename && -f $otherfile;
|
|
|
|
my $source = read_source($source_filename);
|
|
my $orig_source = $source;
|
|
my $error_message = '';
|
|
|
|
$source = pre_indent($source);
|
|
|
|
$source = run_indent($source, \$error_message);
|
|
if ($source eq "")
|
|
{
|
|
print STDERR "Failure in $source_filename: " . $error_message . "\n";
|
|
next;
|
|
}
|
|
|
|
$source = post_indent($source);
|
|
|
|
if ($source ne $orig_source)
|
|
{
|
|
if ($silent_diff)
|
|
{
|
|
exit 2;
|
|
}
|
|
elsif ($show_diff)
|
|
{
|
|
print show_diff($source, $source_filename);
|
|
}
|
|
else
|
|
{
|
|
write_source($source, $source_filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
exit 0;
|