diff --git a/.editorconfig b/.editorconfig index 0ee9bd28ac47..4fa6fd1aadbb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -67,6 +67,12 @@ indent_style = space tab_width = unset indent_size = 1 +[src/tools/pgcheck/pgcheck] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = tab +tab_width = 4 + [*.data] indent_style = unset indent_size = unset diff --git a/.gitattributes b/.gitattributes index 00092168393a..8e141e960fd3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,9 @@ README conflict-marker-size=48 README.* conflict-marker-size=48 +# Some files are perl even without perly extensions +src/tools/pgcheck/pgcheck whitespace=space-before-tab,trailing-space,indent-with-non-tab,tabwidth=4 + # Certain data files that contain special whitespace, and other special cases *.data -whitespace contrib/pgcrypto/sql/pgp-armor.sql whitespace=-blank-at-eol diff --git a/src/tools/perlcheck/pgperlcritic b/src/tools/perlcheck/pgperlcritic index 2ec6f20de315..0220052acf04 100755 --- a/src/tools/perlcheck/pgperlcritic +++ b/src/tools/perlcheck/pgperlcritic @@ -1,20 +1,8 @@ #!/bin/sh -# src/tools/perlcheck/pgperlcritic +# Copyright (c) 2021-2026, PostgreSQL Global Development Group -test -f src/tools/perlcheck/perlcriticrc || { - echo could not find src/tools/perlcheck/perlcriticrc - exit 1 - } +# Wrapper that runs pgcheck in perlcritic-only mode. +# See src/tools/pgcheck/pgcheck for the full implementation. -set -e - -# set this to override default perlcritic program: -PERLCRITIC=${PERLCRITIC:-perlcritic} - -. src/tools/perlcheck/find_perl_files - -find_perl_files "$@" | xargs $PERLCRITIC \ - --quiet \ - --program-extensions .pl \ - --profile=src/tools/perlcheck/perlcriticrc +exec "$(dirname "$0")/../pgcheck/pgcheck" --perlcritic "$@" diff --git a/src/tools/perlcheck/pgperlsyncheck b/src/tools/perlcheck/pgperlsyncheck index 657d2afcc026..3928311e13a2 100755 --- a/src/tools/perlcheck/pgperlsyncheck +++ b/src/tools/perlcheck/pgperlsyncheck @@ -1,16 +1,8 @@ #!/bin/sh -# script to detect compile time errors and warnings in all perl files +# Copyright (c) 2021-2026, PostgreSQL Global Development Group -INCLUDES="-I src/backend/catalog" -INCLUDES="-I src/test/perl -I src/backend/utils/mb/Unicode $INCLUDES" -INCLUDES="-I src/bin/pg_rewind -I src/test/ssl/t $INCLUDES" +# Wrapper that runs pgcheck in perl-syncheck-only mode. +# See src/tools/pgcheck/pgcheck for the full implementation. -set -e - -. src/tools/perlcheck/find_perl_files - -# for zsh -setopt shwordsplit 2>/dev/null || true - -find_perl_files "$@" | xargs -L 1 perl $INCLUDES -cw 2>&1 | grep -v OK +exec "$(dirname "$0")/../pgcheck/pgcheck" --perl-syncheck "$@" diff --git a/src/tools/pgcheck/README b/src/tools/pgcheck/README new file mode 100644 index 000000000000..47c628f5b446 --- /dev/null +++ b/src/tools/pgcheck/README @@ -0,0 +1,21 @@ +pgcheck +======= + +pgcheck is a unified tool for formatting and checking PostgreSQL's C +and Perl code. It consolidates the functionality of pgindent, +pgperltidy, pgperlcritic, and pgperlsyncheck into a single script. +The individual tool scripts still exist as thin wrappers around pgcheck. + + src/tools/pgcheck/pgcheck + +By default pgcheck runs all tools on the current directory. You can select +specific ones or use a variety of options: + + src/tools/pgcheck/pgcheck --pgindent src contrib + src/tools/pgcheck/pgcheck --perltidy --commit HEAD --check --diff + src/tools/pgcheck/pgcheck --perlcritic --perl-syncheck + +Run pgcheck --help for the full list of options. + +See src/tools/pgindent/README for prerequisites and detailed usage +instructions. diff --git a/src/tools/pgcheck/pgcheck b/src/tools/pgcheck/pgcheck new file mode 100755 index 000000000000..9d1762e4f05a --- /dev/null +++ b/src/tools/pgcheck/pgcheck @@ -0,0 +1,979 @@ +#!/usr/bin/perl + +# Copyright (c) 2021-2026, PostgreSQL Global Development Group + +# Unified tool for formatting and checking PostgreSQL C and Perl code. +# +# Supported tools +# --pgindent format C code with pg_bsd_indent +# --perltidy format Perl code with perltidy +# --perlcritic lint Perl code with perlcritic +# --perl-syncheck syntax-check Perl code with perl -cw +# +# When no tool flags are given, all tools are run. +# +# Exit codes: +# 0 -- all OK +# 1 -- error invoking pgcheck, nothing done +# 2 -- --check mode and at least one file requires changes +# 3 -- a formatting or checking tool failed on at least one file + +use strict; +use warnings FATAL => 'all'; + +use Cwd qw(abs_path getcwd); +use File::Spec; +use File::Temp; +use IO::Handle; +use Getopt::Long; +use Fcntl qw(:flock); + +# By default Perl's SIGINT/SIGTERM kill the process without running +# END blocks, so File::Temp never gets to clean up. Converting the +# signal into an exit() makes END-block cleanup run. See: +# . Exit codes use the +# 128+signum convention so callers can tell the process was killed by +# a signal. +$SIG{INT} = sub { exit 130; }; # 128 + 2 (SIGINT) +$SIG{TERM} = sub { exit 143; }; # 128 + 15 (SIGTERM) + +# pg_bsd_indent creates a .BAK file that File::Temp doesn't know about. This +# END block makes sure that that file is cleaned up in case someone presses +# Ctrl+C during pgindent. +my $bak_to_cleanup; +END { unlink $bak_to_cleanup if defined $bak_to_cleanup; } + +# Update for pg_bsd_indent version +my $INDENT_VERSION = "2.1.3"; + +# 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, + $diff, $check, $help, @commits, + $perltidy_arg, $perlcritic_arg, $jobs,); + +# tool selectors +my ($do_pgindent, $do_perltidy, $do_perlcritic, $do_perl_syncheck); + +$help = 0; +$jobs = 0; + +# Save @ARGV before parsing so we can distinguish --perltidy=PATH (where +# the value should be used as the perltidy path) from --perltidy PATH +# (where PATH is a file to format that Getopt::Long greedily consumed). +# Same for --perlcritic. +my @orig_argv = @ARGV; + +my %options = ( + "help" => \$help, + "commit=s" => \@commits, + "typedefs=s" => \$typedefs_file, + "list-of-typedefs=s" => \$typedef_str, + "excludes=s" => \@excludes, + "indent=s" => \$indent, + "pgindent" => \$do_pgindent, + "perltidy:s" => \$perltidy_arg, + "perlcritic:s" => \$perlcritic_arg, + "perl-syncheck" => \$do_perl_syncheck, + "diff" => \$diff, + "check" => \$check, + "jobs|j=i" => \$jobs,); +GetOptions(%options) || usage("bad command line argument"); + +if ($jobs == 0) +{ + $jobs = get_num_cpus(); +} +elsif ($jobs < 0) +{ + usage("--jobs must be a non-negative number"); +} + +# For --perltidy and --perlcritic, distinguish =PATH from bare PATH. The PATH +# in =PATH is interpreted as a path to the perltidy/perlcritic binary, but a +# separated PATH is interpreted as a file to be processed. +foreach my $pair ([ \$perltidy_arg, "perltidy" ], + [ \$perlcritic_arg, "perlcritic" ]) +{ + my ($ref, $name) = @$pair; + if (defined($$ref) && $$ref ne '') + { + unless (grep { $_ eq "--$name=$$ref" } @orig_argv) + { + # If it's not =PATH add the argument back to @ARGV for processing + # as a file argument + unshift(@ARGV, $$ref); + # Set the $perltidy_arg/perlcritic_arg to the empty string, to + # indicate for the later code that the tool was selected but no + # path was given. + $$ref = ''; + } + } +} + +# --perltidy and --perlcritic as tool selectors: if given (even +# without a value), they select their respective tool. +$do_perltidy = 1 if defined($perltidy_arg); +$do_perlcritic = 1 if defined($perlcritic_arg); + +usage() if $help; + +usage("Cannot use --commit with command line file list") + if (@commits && @ARGV); + +# default to current directory if no files/dirs given +@ARGV = ('.') unless @ARGV || @commits; + +# If no tool flags were given, run everything. +my $any_op = + $do_pgindent + || $do_perltidy + || $do_perlcritic + || $do_perl_syncheck; +if (!$any_op) +{ + $do_pgindent = 1; + $do_perltidy = 1; + $do_perlcritic = 1; + $do_perl_syncheck = 1; +} + +my $do_perl_any = $do_perltidy || $do_perlcritic || $do_perl_syncheck; + +# command line option wins, then environment, then locations based on current +# dir, then default location +$typedefs_file ||= $ENV{PGTYPEDEFS}; + +my $sourcedir = locate_sourcedir(); + +# get indent location: command line wins, then environment, then try to find +# a compiled pg_bsd_indent in the source tree, then fall back to PATH. +$indent ||= $ENV{PGINDENT} || $ENV{INDENT}; +if (!$indent && $sourcedir) +{ + my $srcroot = "$sourcedir/../../.."; + my $bsd_indent_subdir = "src/tools/pg_bsd_indent/pg_bsd_indent"; + + # Look for pg_bsd_indent: first in-tree (autoconf in-tree build), + # then in a "build" directory (meson or autoconf vpath), + # then any "build*" directory. + foreach my $candidate ( + "$srcroot/$bsd_indent_subdir", + "$srcroot/build/$bsd_indent_subdir", + glob("$srcroot/build*/$bsd_indent_subdir")) + { + if (-x $candidate) + { + $indent = $candidate; + last; + } + } +} +$indent ||= "pg_bsd_indent"; + +# get perltidy location: command line wins, then environment, then try to +# find a get_perltidy-installed copy in the source tree, then fall back to +# PATH. +my $perltidy = $perltidy_arg || $ENV{PERLTIDY}; +if (!$perltidy && $sourcedir) +{ + # Look for a get_perltidy-installed perltidy in the source tree. + my $candidate = "$sourcedir/perltidy/bin/perltidy"; + if (-x $candidate) + { + $perltidy = $candidate; + + # Local installs need PERL5LIB set so perltidy can find its + # modules. + my $libdir = "$sourcedir/perltidy/lib/perl5"; + if (-d $libdir) + { + $ENV{PERL5LIB} = + $ENV{PERL5LIB} ? "$libdir:$ENV{PERL5LIB}" : $libdir; + } + } +} +$perltidy ||= "perltidy"; + +# get perlcritic location: command line wins, then environment, then PATH. +my $perlcritic = $perlcritic_arg || $ENV{PERLCRITIC} || "perlcritic"; + +# 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 ), 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 = map { "$_\n" } qw( + bool regex_t regmatch_t regoff +); + +my %excluded = map { +"$_\n" => 1 } qw( + FD_SET LookupSet boolean date duration + element_type inquiry iterator other + pointer reference rep string timestamp type wrap +); + +# globals +my @files; +my $filtered_typedefs_fh; +my $stdout_lock_fh; + +sub get_num_cpus +{ + # Try nproc (Linux, some BSDs), then sysctl (macOS, FreeBSD). + for my $cmd ('nproc', 'sysctl -n hw.ncpu') + { + my $n = `$cmd 2>$devnull`; + chomp $n; + return $n + 0 if ($? == 0 && $n =~ /^\d+$/ && $n > 0); + } + return 1; +} + +sub check_indent +{ + if (system("$indent -? < $devnull > $devnull 2>&1") != 0) + { + 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; + } + + if (system("$indent -gnu < $devnull > $devnull 2>&1") == 0) + { + print STDERR + "You appear to have GNU indent rather than BSD indent.\n"; + exit 1; + } + + return; +} + +my $PERLTIDY_VERSION = "20230309"; + +sub check_perltidy +{ + if (!$sourcedir) + { + print STDERR + "Cannot find perltidyrc: not running inside a PostgreSQL source tree.\n"; + exit 1; + } + + my $ver = `$perltidy -v 2>&1`; + if ($? != 0) + { + print STDERR + "You do not appear to have $perltidy installed on your system.\n" + . "See src/tools/pgindent/README for installation instructions.\n"; + exit 1; + } + + if ($ver !~ m/$PERLTIDY_VERSION/) + { + print STDERR + "You do not appear to have $perltidy version $PERLTIDY_VERSION installed on your system.\n" + . "See src/tools/pgindent/README for installation instructions.\n"; + exit 1; + } + + return; +} + +sub check_perlcritic +{ + if (!$sourcedir) + { + print STDERR + "Cannot find perlcriticrc: not running inside a PostgreSQL source tree.\n"; + exit 1; + } + + my $ver = `$perlcritic --version 2>&1`; + if ($? != 0) + { + print STDERR + "You do not appear to have $perlcritic installed on your system.\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/^#/ || $line eq ""; + 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; + + ## Ensure file ends with a newline (pg_bsd_indent messes up otherwise) + $source .= "\n" unless substr($source, -1) eq "\n"; + + ## 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; + + # Postprocess multiline comments except for /**... and /*-... ones + $source =~ s!^(/\*[^\*\-].*?\*/)!postprocess_multiline_comment($1)!mgse; + + ## Functions + + # Use a single space before '*' in function return types + $source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm; + + return $source; +} + +sub postprocess_multiline_comment +{ + my $source = shift; + my @lines = split "\n", $source; + + # Only reformat comments that actually span more than one line + if (scalar @lines < 2) + { + return $source; + } + + # Split any text on the first line onto a separate line, + # but keep /* === and /* --- lines as is + if ($lines[0] !~ m!^/\*\s+[=-]+$!) + { + $lines[0] =~ s!/\*\s*(.+)!/\*\n * $1!; + } + + # Apply these steps to all lines but the first + for my $i (1 .. scalar @lines - 1) + { + # Insert ' * ' if first non-white-space character is not '*' + $lines[$i] =~ s/^\s*/ \* / if $lines[$i] !~ /^\s*\*/; + # ... but that might leave us with trailing space + $lines[$i] =~ s/\s+$//; + # Require exactly one leading '*', and one space before it + $lines[$i] =~ s/^\s*\*+/ \*/; + } + + # Split trailing "*/" onto its own line if not that already, + # but keep === */ and --- */ lines as is + if ($lines[-1] !~ m!^\s*[=-]+\s+\*/$!) + { + # this also removes any trailing whitespace + $lines[-1] =~ s!(.+?)\s+\*/!$1\n \*/!; + } + + $source = join "\n", @lines; + + 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(); + + $bak_to_cleanup = "$filename.BAK"; + + $$error_message = `$cmd $filename 2>&1`; + + return "" if ($? || length($$error_message) > 0); + + unlink $bak_to_cleanup; + $bak_to_cleanup = undef; + + open(my $src_out, '<', $filename) || die $!; + local ($/) = undef; + $source = <$src_out>; + close($src_out); + + return $source; +} + +sub format_c +{ + my $source = shift; + my $source_filename = shift; + my $error_message = ''; + + my $formatted = pre_indent($source); + $formatted = run_indent($formatted, \$error_message); + if ($formatted eq "") + { + print "Failure in $source_filename: " . $error_message . "\n"; + return ("", 1); + } + return post_indent($formatted); +} + +sub format_perl +{ + my $source = shift; + my $source_filename = shift; + + my $tmp_fh = new File::Temp(TEMPLATE => "pgperltidyXXXXX"); + my $tmp_filename = $tmp_fh->filename; + print $tmp_fh $source; + $tmp_fh->close(); + + my $perltidy_profile = "$sourcedir/perltidyrc"; + my $err = + `$perltidy --profile=$perltidy_profile -b -bext='/' $tmp_filename 2>&1`; + if ($? != 0) + { + print "Failure in $source_filename: " . $err . "\n"; + return ("", 1); + } + return read_source($tmp_filename); +} + +sub run_perlcritic +{ + my $source_filename = shift; + + my $perlcheck_dir = "$sourcedir/../perlcheck"; + my $criticrc = "$perlcheck_dir/perlcriticrc"; + + my $err = + `$perlcritic --quiet --program-extensions .pl --profile=$criticrc "$source_filename" 2>&1`; + return ("", 0) if $? == 0; + return ($err, 1); +} + +sub run_perl_syncheck +{ + my $source_filename = shift; + + my $includes = join( + ' ', + map { "-I $_" } ( + "src/backend/catalog", "src/test/perl", + "src/backend/utils/mb/Unicode", "src/bin/pg_rewind", + "src/test/ssl/t",)); + + my $output = `perl $includes -cw "$source_filename" 2>&1`; + my $failed = $? != 0; + + # Filter out "OK" lines — perl -cw prints " syntax OK" on success + my @lines = grep { !/\bsyntax OK$/ } split(/\n/, $output); + my $filtered = join("\n", @lines); + $filtered .= "\n" if $filtered; + + return ($filtered, $failed || scalar(@lines) > 0); +} + +sub 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 is_c_file +{ + my $filename = shift; + # It needs to have .c or .h extension + return 0 unless $filename =~ /\.[ch]$/; + # Automatically ignore .c and .h files that correspond to a .y or .l file. + # pg_bsd_indent tends to get badly confused by Bison/flex output, and + # there's no value in indenting derived files anyway. + my $otherfile = $filename; + $otherfile =~ s/\.[ch]$/.y/; + return 0 if $otherfile ne $filename && -f $otherfile; + $otherfile =~ s/\.y$/.l/; + return 0 if $otherfile ne $filename && -f $otherfile; + return 1; +} + +sub is_perl_file +{ + my $filename = shift; + # take all .pl and .pm files + return 1 if $filename =~ /\.p[lm]$/; + # take executable files that file(1) thinks are perl files + return 0 unless -x $filename; + my $file_out = `file "$filename"`; + return $file_out =~ /:.*perl[0-9]*\b/i; +} + +sub discover_files +{ + my @paths = @_; + my @discovered; + + # Separate individual files from directories + my @dirs; + foreach my $path (@paths) + { + if (-f $path) + { + push(@discovered, $path); + } + elsif (-d $path) + { + push(@dirs, $path); + } + else + { + warn "Could not find $path"; + } + } + + # Use git ls-files for directories to avoid searching build trees etc. + if (@dirs) + { + # Use -z to handle files with newlines in their names, and split on \0 + # afterward. + open(my $git_ls, '-|', 'git', 'ls-files', '-z', '--', @dirs) + || die "could not open git ls-files: $!"; + binmode($git_ls); + # Slurp all output into a single string by temporarily unsetting + # the line separator. + my $git_output = do { local $/; <$git_ls> }; + close($git_ls); + die "git ls-files error" if $?; + my @git_files = split(/\0/, $git_output); + push(@discovered, @git_files); + } + + return @discovered; +} + +sub process_file +{ + my $source_filename = shift; + + # don't try to format a file that doesn't exist + unless (-f $source_filename) + { + warn "Could not find $source_filename"; + return 0; + } + + # The more complex filtering is already done by discover_files, at this + # point we can simply check the file extension to determine if it's a C + # file. + my $is_c = $source_filename =~ /\.[ch]$/; + # All other files are Perl files + my $is_perl = !$is_c; + + # Formatting step: pgindent or perltidy + if ($is_c && $do_pgindent) + { + my $source = read_source($source_filename); + my ($formatted, $failure) = format_c($source, $source_filename); + + if ($failure) + { + return 3; + } + + if ($formatted ne $source) + { + if (!$diff && !$check) + { + write_source($formatted, $source_filename); + } + else + { + if ($diff) + { + my $output = diff($formatted, $source_filename); + flock($stdout_lock_fh, LOCK_EX) or die "flock: $!"; + print $output; + STDOUT->flush(); + flock($stdout_lock_fh, LOCK_UN) or die "flock: $!"; + } + + return 2 if $check; + } + } + } + elsif ($is_perl && $do_perltidy) + { + my $source = read_source($source_filename); + my ($formatted, $failure) = format_perl($source, $source_filename); + + if ($failure) + { + return 3; + } + + if ($formatted ne $source) + { + if (!$diff && !$check) + { + write_source($formatted, $source_filename); + } + else + { + if ($diff) + { + my $output = diff($formatted, $source_filename); + flock($stdout_lock_fh, LOCK_EX) or die "flock: $!"; + print $output; + STDOUT->flush(); + flock($stdout_lock_fh, LOCK_UN) or die "flock: $!"; + } + + return 2 if $check; + } + } + } + + # Validation steps: perlcritic and syncheck (only for perl files) + if ($is_perl) + { + if ($do_perlcritic) + { + my ($output, $failed) = run_perlcritic($source_filename); + if ($failed) + { + flock($stdout_lock_fh, LOCK_EX) or die "flock: $!"; + print $output; + STDOUT->flush(); + flock($stdout_lock_fh, LOCK_UN) or die "flock: $!"; + return 3; + } + } + + if ($do_perl_syncheck) + { + my ($output, $failed) = run_perl_syncheck($source_filename); + if ($failed) + { + flock($stdout_lock_fh, LOCK_EX) or die "flock: $!"; + print $output; + STDOUT->flush(); + flock($stdout_lock_fh, LOCK_UN) or die "flock: $!"; + return 3; + } + } + } + + return 0; +} + +sub usage +{ + my $message = shift; + my $helptext = <<'EOF'; +Usage: +pgcheck [OPTION]... [FILE|DIR]... +Options: + --help show this message and quit + --pgindent format C code with pg_bsd_indent + --perltidy[=PATH] format Perl code with perltidy + --perlcritic[=PATH] lint Perl code with perlcritic + --perl-syncheck syntax-check Perl code with perl -cw + --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 + --diff show the changes that would be made + --check exit with status 2 if any changes would be made + --jobs=N, -j N number of parallel workers (0 = num CPUs, default 0) +When no --pgindent, --perltidy, --perlcritic, or --perl-syncheck flags are +provided, all of these tools are run. +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 + +if ($do_pgindent) +{ + $filtered_typedefs_fh = load_typedefs(); + check_indent(); +} + +if ($do_perltidy) +{ + check_perltidy(); +} + +if ($do_perlcritic) +{ + check_perlcritic(); +} + +# any non-option arguments are files or directories to be processed +if (@ARGV) +{ + foreach my $f (discover_files(@ARGV)) + { + if ($do_pgindent && is_c_file($f)) + { + push(@files, $f); + } + elsif ($do_perl_any && is_perl_file($f)) + { + push(@files, $f); + } + } +} + +# 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, grep { is_c_file($_) } @affected) if $do_pgindent; + push(@files, grep { is_perl_file($_) } @affected) if $do_perl_any; +} + +warn "No files to process" unless @files; + +# remove excluded files from the file list +process_exclude(); + +# Used by forked children to serialize diff output to STDOUT via flock(). +$stdout_lock_fh = new File::Temp(TEMPLATE => "pglockXXXXX"); + +# deduplicate file list +my %seen; +@files = grep { !$seen{$_}++ } @files; + +my $status = 0; + +if ($jobs <= 1) +{ + foreach my $source_filename (@files) + { + my $file_status = process_file($source_filename); + $status = $file_status if $file_status > $status; + last if $check && $status >= 2 && !$diff; + } +} +else +{ + my %children; # pid => 1 + + my $file_idx = 0; + while ($file_idx < scalar(@files) || %children) + { + # Fork new children up to $jobs limit + while ($file_idx < scalar(@files) && scalar(keys %children) < $jobs) + { + my $source_filename = $files[ $file_idx++ ]; + + my $pid = fork(); + die "fork failed: $!\n" unless defined $pid; + + if ($pid == 0) + { + # child + my $child_status = process_file($source_filename); + exit $child_status; + } + + $children{$pid} = 1; + } + + # Wait for at least one child to finish + my $pid = waitpid(-1, 0); + if ($pid > 0 && exists $children{$pid}) + { + delete $children{$pid}; + my $child_status = $? >> 8; + $status = $child_status if $child_status > $status; + } + } +} + +exit $status; diff --git a/src/tools/pgindent/.gitignore b/src/tools/pgindent/.gitignore new file mode 100644 index 000000000000..1497217113b8 --- /dev/null +++ b/src/tools/pgindent/.gitignore @@ -0,0 +1 @@ +/perltidy/ diff --git a/src/tools/pgindent/README b/src/tools/pgindent/README index b6cd4c6f6b76..ebc18d83e9cb 100644 --- a/src/tools/pgindent/README +++ b/src/tools/pgindent/README @@ -17,14 +17,14 @@ PREREQUISITES: 2) Install perltidy. Please be sure it is version 20230309 (older and newer versions make different formatting choices, and we want consistency). - You can get the correct version from - https://cpan.metacpan.org/authors/id/S/SH/SHANCOCK/ - To install, follow the usual install process for a Perl module - ("man perlmodinstall" explains it). Or, if you have cpan installed, - this should work: - cpan SHANCOCK/Perl-Tidy-20230309.tar.gz - Or if you have cpanm installed, you can just use: - cpanm https://cpan.metacpan.org/authors/id/S/SH/SHANCOCK/Perl-Tidy-20230309.tar.gz + + The easiest way is to use the get_perltidy script, which downloads, + verifies, and installs the correct version into the source tree: + + src/tools/pgindent/get_perltidy + + This installs perltidy into src/tools/pgindent/perltidy/, which + pgindent will find automatically. DOING THE INDENT RUN BEFORE A NORMAL COMMIT: diff --git a/src/tools/pgindent/get_perltidy b/src/tools/pgindent/get_perltidy new file mode 100755 index 000000000000..09ce4a195cb1 --- /dev/null +++ b/src/tools/pgindent/get_perltidy @@ -0,0 +1,62 @@ +#!/bin/sh + +# src/tools/pgindent/get_perltidy +# +# Downloads and installs perltidy 20230309 into src/tools/pgindent/perltidy/. + +set -e + +PERLTIDY_VERSION=20230309 +PERLTIDY_TARBALL="Perl-Tidy-${PERLTIDY_VERSION}.tar.gz" +PERLTIDY_URL="https://cpan.metacpan.org/authors/id/S/SH/SHANCOCK/${PERLTIDY_TARBALL}" +PERLTIDY_SHA256="e22949a208c618d671a18c5829b451abbe9da0da2cddd78fdbfcb036c7361c18" + +# Determine the directory this script lives in, i.e. src/tools/pgindent +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +PERLTIDY_DIR="$SCRIPT_DIR/perltidy" +PERLTIDY_BIN="$PERLTIDY_DIR/bin/perltidy" + +# Check if already installed with the correct version +if [ -x "$PERLTIDY_BIN" ]; then + perltidy_lib="$PERLTIDY_DIR/lib/perl5" + installed_version=$(PERL5LIB="$perltidy_lib${PERL5LIB:+:$PERL5LIB}" "$PERLTIDY_BIN" -v 2>/dev/null || true) + if echo "$installed_version" | grep -q "$PERLTIDY_VERSION"; then + echo "perltidy $PERLTIDY_VERSION is already installed in $PERLTIDY_DIR" + exit 0 + fi +fi + +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT + +# Download +TARBALL="$WORK_DIR/$PERLTIDY_TARBALL" +if command -v curl >/dev/null 2>&1; then + curl -sSL -o "$TARBALL" "$PERLTIDY_URL" +elif command -v wget >/dev/null 2>&1; then + wget -q -O "$TARBALL" "$PERLTIDY_URL" +else + echo "error: neither curl nor wget found" >&2 + exit 1 +fi + +# Verify SHA256 +if command -v sha256sum >/dev/null 2>&1; then + echo "$PERLTIDY_SHA256 $TARBALL" | sha256sum -c - >/dev/null +elif command -v shasum >/dev/null 2>&1; then + echo "$PERLTIDY_SHA256 $TARBALL" | shasum -a 256 -c - >/dev/null +else + echo "error: neither sha256sum nor shasum found" >&2 + exit 1 +fi + +# Extract, build, install +cd "$WORK_DIR" +tar xzf "$TARBALL" +cd "Perl-Tidy-${PERLTIDY_VERSION}" +perl Makefile.PL INSTALL_BASE="$PERLTIDY_DIR" >/dev/null +make >/dev/null +make install >/dev/null + +echo "perltidy $PERLTIDY_VERSION installed in $PERLTIDY_DIR" diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent index 004b8fcab002..f15530750300 100755 --- a/src/tools/pgindent/pgindent +++ b/src/tools/pgindent/pgindent @@ -1,515 +1,8 @@ -#!/usr/bin/perl +#!/bin/sh # Copyright (c) 2021-2026, PostgreSQL Global Development Group -# Program to maintain uniform layout style in our C code. -# Exit codes: -# 0 -- all OK -# 1 -- error invoking pgindent, nothing done -# 2 -- --check mode and at least one file requires changes -# 3 -- pg_bsd_indent failed on at least one file +# Wrapper that runs pgcheck in pgindent-only mode. +# See src/tools/pgcheck/pgcheck for the full implementation. -use strict; -use warnings FATAL => 'all'; - -use Cwd qw(abs_path getcwd); -use File::Find; -use File::Spec; -use File::Temp; -use IO::Handle; -use Getopt::Long; - -# By default Perl's SIGINT/SIGTERM kill the process without running -# END blocks, so File::Temp never gets to clean up. Converting the -# signal into an exit() makes END-block cleanup run. See: -# . Exit codes use the -# 128+signum convention so callers can tell the process was killed by -# a signal. -$SIG{INT} = sub { exit 130; }; # 128 + 2 (SIGINT) -$SIG{TERM} = sub { exit 143; }; # 128 + 15 (SIGTERM) - -# pg_bsd_indent creates a .BAK file that File::Temp doesn't know about. This -# END block makes sure that that file is cleaned up in case someone presses -# Ctrl+C during pgindent. -my $bak_to_cleanup; -END { unlink $bak_to_cleanup if defined $bak_to_cleanup; } - -# Update for pg_bsd_indent version -my $INDENT_VERSION = "2.1.3"; - -# 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, $diff, - $check, $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, - "diff" => \$diff, - "check" => \$check,); -GetOptions(%options) || usage("bad command line argument"); - -usage() if $help; - -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 ), 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 = map { "$_\n" } qw( - bool regex_t regmatch_t regoff -); - -my %excluded = map { +"$_\n" => 1 } qw( - FD_SET LookupSet boolean date duration - element_type inquiry iterator other - pointer reference rep string timestamp type wrap -); - -# globals -my @files; -my $filtered_typedefs_fh; - -sub check_indent -{ - if (system("$indent -? < $devnull > $devnull 2>&1") != 0) - { - 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; - } - - if (system("$indent -gnu < $devnull > $devnull 2>&1") == 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/^#/ || $line eq ""; - 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; - - ## Ensure file ends with a newline (pg_bsd_indent messes up otherwise) - $source .= "\n" unless substr($source, -1) eq "\n"; - - ## 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; - - # Postprocess multiline comments except for /**... and /*-... ones - $source =~ s!^(/\*[^\*\-].*?\*/)!postprocess_multiline_comment($1)!mgse; - - ## Functions - - # Use a single space before '*' in function return types - $source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm; - - return $source; -} - -sub postprocess_multiline_comment -{ - my $source = shift; - my @lines = split "\n", $source; - - # Only reformat comments that actually span more than one line - if (scalar @lines < 2) - { - return $source; - } - - # Split any text on the first line onto a separate line, - # but keep /* === and /* --- lines as is - if ($lines[0] !~ m!^/\*\s+[=-]+$!) - { - $lines[0] =~ s!/\*\s*(.+)!/\*\n * $1!; - } - - # Apply these steps to all lines but the first - for my $i (1 .. scalar @lines - 1) - { - # Insert ' * ' if first non-white-space character is not '*' - $lines[$i] =~ s/^\s*/ \* / if $lines[$i] !~ /^\s*\*/; - # ... but that might leave us with trailing space - $lines[$i] =~ s/\s+$//; - # Require exactly one leading '*', and one space before it - $lines[$i] =~ s/^\s*\*+/ \*/; - } - - # Split trailing "*/" onto its own line if not that already, - # but keep === */ and --- */ lines as is - if ($lines[-1] !~ m!^\s*[=-]+\s+\*/$!) - { - # this also removes any trailing whitespace - $lines[-1] =~ s!(.+?)\s+\*/!$1\n \*/!; - } - - $source = join "\n", @lines; - - 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(); - - $bak_to_cleanup = "$filename.BAK"; - - $$error_message = `$cmd $filename 2>&1`; - - return "" if ($? || length($$error_message) > 0); - - unlink $bak_to_cleanup; - $bak_to_cleanup = undef; - - open(my $src_out, '<', $filename) || die $!; - local ($/) = undef; - $source = <$src_out>; - close($src_out); - - return $source; -} - -sub 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|DIR]... -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 - --diff show the changes that would be made - --check 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; -my $status = 0; - -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"; - $status = 3; - next; - } - - $source = post_indent($source); - - if ($source ne $orig_source) - { - if (!$diff && !$check) - { - write_source($source, $source_filename); - } - else - { - if ($diff) - { - print diff($source, $source_filename); - } - - if ($check) - { - $status ||= 2; - last unless $diff; - } - } - } -} - -exit $status; +exec "$(dirname "$0")/../pgcheck/pgcheck" --pgindent "$@" diff --git a/src/tools/pgindent/pgperltidy b/src/tools/pgindent/pgperltidy index 87838d6bde3a..72273f50c9e8 100755 --- a/src/tools/pgindent/pgperltidy +++ b/src/tools/pgindent/pgperltidy @@ -1,18 +1,8 @@ #!/bin/sh -# src/tools/pgindent/pgperltidy +# Copyright (c) 2021-2026, PostgreSQL Global Development Group -set -e +# Wrapper that runs pgcheck in perltidy-only mode. +# See src/tools/pgcheck/pgcheck for the full implementation. -# set this to override default perltidy program: -PERLTIDY=${PERLTIDY:-perltidy} - -PERLTIDY_VERSION=20230309 -if ! $PERLTIDY -v | grep -q $PERLTIDY_VERSION; then - echo "You do not appear to have $PERLTIDY version $PERLTIDY_VERSION installed on your system." >&2 - exit 1 -fi - -. src/tools/perlcheck/find_perl_files - -find_perl_files "$@" | xargs $PERLTIDY --profile=src/tools/pgindent/perltidyrc +exec "$(dirname "$0")/../pgcheck/pgcheck" --perltidy "$@"