On Fri Mar 27, 2026 at 2:32 PM CET, Peter Eisentraut wrote:
I have committed these two.

Thanks!

v3-0003-pgindent-Use-git-ls-files-to-discover-files.patch

This looks structurally correct, but I wonder whether this:

     my @git_files = `git ls-files -- @dirs`;

could be written in a way that is more robust against funny file names. (pgindent is also used against trees that are not stock PostgreSQL.)

I have changed it to use open so the args are not string interpolated
and -z so the resulting files are null-separated instead of
newline-separated. Is this the kind of thing you had in mind?

v3-0004-pgindent-Default-to-indenting-the-current-directo.patch

Note that other tools also share this behavior with pgindent:

src/tools/pgindent/pgperltidy
src/tools/perlcheck/pgperlcritic
src/tools/perlcheck/pgperlsyncheck

If we change one, we should change all.

I think that's fair, but because the other tools don't use git ls-files
I don't wanna do that in this commit.

Instead I updated the later commits to not just integrate pgperltidy
into pgindent, but create a new pgcheck tool that combines all of these
tools (the old scripts are kept as tiny wrappers). So after committing
all these tools behave the same way, not just in wheter no arguments
means "current directory" but also in all the flags they accept.
From 7bc24625457bfaf54320523db49b44c2da4ead1e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Tue, 3 Mar 2026 09:43:33 +0100
Subject: [PATCH v4 1/7] pgindent: Use git ls-files to discover files

When running pgindent on the whole source tree, it would often also
indent build artifacts. This changes the pgindent directory search logic
to become git-aware, and only indent files that are actually tracked by
git.

Any files that are explicitly part of the pgindent its arguments, are
always indented though, even if they are not tracked by git. This can be
useful to force indentation of an untracked file and/or for editor
integration.
---
 src/tools/pgindent/pgindent | 54 ++++++++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 10 deletions(-)

diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index b2ec5e2914b..f5bc4b3642a 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -13,7 +13,6 @@ use strict;
 use warnings FATAL => 'all';
 
 use Cwd qw(abs_path getcwd);
-use File::Find;
 use File::Spec;
 use File::Temp;
 use IO::Handle;
@@ -338,6 +337,49 @@ sub diff
 	return $diff;
 }
 
+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, grep { /\.[ch]$/ } @git_files);
+	}
+
+	return @discovered;
+}
+
 sub usage
 {
 	my $message = shift;
@@ -373,16 +415,8 @@ $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;
+push(@files, discover_files(@ARGV)) if @ARGV;
 
 # commit file locations are relative to the source root
 chdir "$sourcedir/../../.." if @commits && $sourcedir;

base-commit: effaa464afd355e8927bf430cfe6a0ddd2ee5695
-- 
2.53.0

From d3d4a52c66773529b10bc826568df78a2cba91b4 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Tue, 3 Mar 2026 20:56:15 +0100
Subject: [PATCH v4 2/7] pgindent: Default to indenting the current directory
 if no files are given

Previously running pgindent without giving it any files would result in
this output:

src/tools/pgindent/pgindent
No files to process at src/tools/pgindent/pgindent line 526.

This instead indents the current directory, which is probably what the
user intended.
---
 src/tools/pgindent/pgindent | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index f5bc4b3642a..e2e6f19678a 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -63,6 +63,9 @@ 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;
+
 # command line option wins, then environment, then locations based on current
 # dir, then default location
 $typedefs_file ||= $ENV{PGTYPEDEFS};
-- 
2.53.0

From 1924e5dbd9a88ce0238814ec4e5a67e6f2eea02d Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Wed, 4 Mar 2026 09:24:24 +0100
Subject: [PATCH v4 3/7] pgindent: Allow parallel pgindent runs

Running pgindent on the whole source tree can take a while. This adds
support for pgindent to indent files in parallel. This speeds up a full
pgindent run from ~7 seconds to 1 second on my machine.

Especially with future commits that integrate perltidy into pgindent the
wins are huge, because perltidy is much slower at formatting than
pg_bsd_indent. With those later commits the time it takes to do a full
pgindent run (including perltidy) takes more than a minute on my machine
without the parallelization, but only take ~7 seconds when run in
parallel.
---
 src/tools/pgindent/pgindent | 181 +++++++++++++++++++++++++-----------
 1 file changed, 129 insertions(+), 52 deletions(-)

diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index e2e6f19678a..ca56758ffac 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -17,6 +17,7 @@ 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
@@ -42,10 +43,11 @@ my $indent_opts =
 
 my $devnull = File::Spec->devnull;
 
-my ($typedefs_file, $typedef_str, @excludes, $indent, $diff,
-	$check, $help, @commits,);
+my ($typedefs_file, $typedef_str, @excludes, $indent,
+	$diff, $check, $help, @commits, $jobs,);
 
 $help = 0;
+$jobs = 0;
 
 my %options = (
 	"help" => \$help,
@@ -55,9 +57,20 @@ my %options = (
 	"excludes=s" => \@excludes,
 	"indent=s" => \$indent,
 	"diff" => \$diff,
-	"check" => \$check,);
+	"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");
+}
+
+
 usage() if $help;
 
 usage("Cannot use --commit with command line file list")
@@ -103,6 +116,19 @@ my %excluded = map { +"$_\n" => 1 } qw(
 # 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
 {
@@ -383,6 +409,67 @@ sub discover_files
 	return @discovered;
 }
 
+sub process_file
+{
+	my $source_filename = shift;
+
+	# ignore anything that's not a .c or .h file
+	return 0 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";
+		return 0;
+	}
+	# 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/;
+	return 0 if $otherfile ne $source_filename && -f $otherfile;
+	$otherfile =~ s/\.y$/.l/;
+	return 0 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";
+		return 3;
+	}
+
+	$source = post_indent($source);
+
+	if ($source ne $orig_source)
+	{
+		if (!$diff && !$check)
+		{
+			write_source($source, $source_filename);
+		}
+		else
+		{
+			if ($diff)
+			{
+				my $output = diff($source, $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;
+		}
+	}
+
+	return 0;
+}
+
 sub usage
 {
 	my $message = shift;
@@ -398,6 +485,7 @@ Options:
 	--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)
 The --excludes and --commit options can be given more than once.
 EOF
 	if ($help)
@@ -439,67 +527,56 @@ warn "No files to process" unless @files;
 # remove excluded files from the file list
 process_exclude();
 
-my %processed;
-my $status = 0;
+# Used by forked children to serialize diff output to STDOUT via flock().
+$stdout_lock_fh = new File::Temp(TEMPLATE => "pglockXXXXX");
 
-foreach my $source_filename (@files)
-{
-	# skip duplicates
-	next if $processed{$source_filename};
-	$processed{$source_filename} = 1;
+# deduplicate file list
+my %seen;
+@files = grep { !$seen{$_}++ } @files;
 
-	# ignore anything that's not a .c or .h file
-	next unless $source_filename =~ /\.[ch]$/;
+my $status = 0;
 
-	# don't try to indent a file that doesn't exist
-	unless (-f $source_filename)
+if ($jobs <= 1)
+{
+	foreach my $source_filename (@files)
 	{
-		warn "Could not find $source_filename";
-		next;
+		my $file_status = process_file($source_filename);
+		$status = $file_status if $file_status > $status;
+		last if $check && $status >= 2 && !$diff;
 	}
-	# 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);
+}
+else
+{
+	my %children;    # pid => 1
 
-	$source = run_indent($source, \$error_message);
-	if ($source eq "")
+	my $file_idx = 0;
+	while ($file_idx < scalar(@files) || %children)
 	{
-		print STDERR "Failure in $source_filename: " . $error_message . "\n";
-		$status = 3;
-		next;
-	}
+		# Fork new children up to $jobs limit
+		while ($file_idx < scalar(@files) && scalar(keys %children) < $jobs)
+		{
+			my $source_filename = $files[ $file_idx++ ];
 
-	$source = post_indent($source);
+			my $pid = fork();
+			die "fork failed: $!\n" unless defined $pid;
 
-	if ($source ne $orig_source)
-	{
-		if (!$diff && !$check)
-		{
-			write_source($source, $source_filename);
-		}
-		else
-		{
-			if ($diff)
+			if ($pid == 0)
 			{
-				print diff($source, $source_filename);
+				# child
+				my $child_status = process_file($source_filename);
+				exit $child_status;
 			}
 
-			if ($check)
-			{
-				$status ||= 2;
-				last unless $diff;
-			}
+			$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;
 		}
 	}
 }
-- 
2.53.0

From b5155b27a1ee35289183d9021941db3be741d664 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Tue, 3 Mar 2026 23:46:52 +0100
Subject: [PATCH v4 4/7] pgindent: Try to find pg_bsd_indent binary in common
 locations

To run pgindent you need to to have the right version of pg_bsd_indent
in your PATH, or specify it manually. This is a bit of a hassle,
especially for newcomers or when working on backbranches. So this
chnages pgindent to search for a pg_bsd_indent that's built from the
current sources, both in-tree (for in-tree autoconf builds) and in a
build directory (for meson or autoconf vpath builds).
---
 src/tools/pgindent/pgindent | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index ca56758ffac..27ebdbdbf25 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -83,11 +83,33 @@ usage("Cannot use --commit with command line file list")
 # 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();
 
+# 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";
+
 # if it's the base of a postgres tree, we will exclude the files
 # postgres wants excluded
 if ($sourcedir)
-- 
2.53.0

From 9fa01defbb6c922612c3a6eb1142206bbf255256 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Mon, 30 Mar 2026 23:16:32 +0200
Subject: [PATCH v4 5/7] Move pgindent to separate pgcheck script

Over time our pgindent script has gotten a lot of quality of life
features, like the --commit, --diff, --check, and --jobs flags. Our
pgperltidy, pgperlcritic and pgperlsyncheck are missing these features.
In the next commit these other tools will gain these quality of life
features too, by introducing a new tool called "pgcheck" that combines
the functionality of all tools. This commit only does:

   git mv src/tools/pgindent/pgindent src/tools/pgcheck/pgcheck

The resulting pgcheck script is not even functional, since some of the
hardcoded relative paths in the script are now incorrect. The only
reason this is separate from the next commit, is to to make git
understand that we moved the pgindent file, so the git history of the
pgindent file is transferred over correctly to the pgcheck file.
---
 src/tools/{pgindent/pgindent => pgcheck/pgcheck} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename src/tools/{pgindent/pgindent => pgcheck/pgcheck} (100%)

diff --git a/src/tools/pgindent/pgindent b/src/tools/pgcheck/pgcheck
similarity index 100%
rename from src/tools/pgindent/pgindent
rename to src/tools/pgcheck/pgcheck
-- 
2.53.0

From 35c2318a9205a4a5c5e380af625c939186521647 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Mon, 30 Mar 2026 23:31:03 +0200
Subject: [PATCH v4 6/7] Combine all formatting and linting tools into pgcheck

Over time our pgindent script has gotten a lot of quality of life
features, like the --commit, --diff, --check, and --jobs flags. Our
pgperltidy, pgperlcritic and pgperlsyncheck scripts were missing all
these features. This commit introduces a new tool called "pgcheck" that
combines the functionality of all these tools, so all of them can
benefit from the improvements that have been made to pgindent. The old
scripts are still available as simple wrappers around pgcheck.
---
 .editorconfig                      |   6 +
 .gitattributes                     |   3 +
 src/tools/perlcheck/pgperlcritic   |  20 +-
 src/tools/perlcheck/pgperlsyncheck |  16 +-
 src/tools/pgcheck/README           |  21 ++
 src/tools/pgcheck/pgcheck          | 387 ++++++++++++++++++++++++++---
 src/tools/pgindent/pgindent        |   8 +
 src/tools/pgindent/pgperltidy      |  18 +-
 8 files changed, 397 insertions(+), 82 deletions(-)
 create mode 100644 src/tools/pgcheck/README
 create mode 100755 src/tools/pgindent/pgindent

diff --git a/.editorconfig b/.editorconfig
index 0ee9bd28ac4..4fa6fd1aadb 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 00092168393..8e141e960fd 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 2ec6f20de31..0220052acf0 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 657d2afcc02..3928311e13a 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 00000000000..47c628f5b44
--- /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
index 27ebdbdbf25..d0586e9ae6c 100755
--- a/src/tools/pgcheck/pgcheck
+++ b/src/tools/pgcheck/pgcheck
@@ -2,12 +2,21 @@
 
 # Copyright (c) 2021-2026, PostgreSQL Global Development Group
 
-# Program to maintain uniform layout style in our C code.
+# 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 pgindent, nothing done
+#   1 -- error invoking pgcheck, nothing done
 #   2 -- --check mode and at least one file requires changes
-#   3 -- pg_bsd_indent failed on at least one file
+#   3 -- a formatting or checking tool failed on at least one file
 
 use strict;
 use warnings FATAL => 'all';
@@ -44,11 +53,21 @@ my $indent_opts =
 my $devnull = File::Spec->devnull;
 
 my ($typedefs_file, $typedef_str, @excludes, $indent,
-	$diff, $check, $help, @commits, $jobs,);
+	$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,
@@ -56,6 +75,10 @@ my %options = (
 	"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,);
@@ -70,6 +93,32 @@ 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;
 
@@ -79,6 +128,22 @@ usage("Cannot use --commit with command line file list")
 # 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};
@@ -110,6 +175,12 @@ if (!$indent && $sourcedir)
 }
 $indent ||= "pg_bsd_indent";
 
+# get perltidy location: command line wins, then environment, then PATH.
+my $perltidy = $perltidy_arg || $ENV{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)
@@ -181,6 +252,57 @@ sub check_indent
 	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
@@ -372,6 +494,78 @@ sub run_indent
 	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 "<file> 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;
@@ -388,6 +582,33 @@ sub diff
 	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 = @_;
@@ -425,7 +646,7 @@ sub discover_files
 		close($git_ls);
 		die "git ls-files error" if $?;
 		my @git_files = split(/\0/, $git_output);
-		push(@discovered, grep { /\.[ch]$/ } @git_files);
+		push(@discovered, @git_files);
 	}
 
 	return @discovered;
@@ -435,57 +656,111 @@ sub process_file
 {
 	my $source_filename = shift;
 
-	# ignore anything that's not a .c or .h file
-	return 0 unless $source_filename =~ /\.[ch]$/;
-
-	# don't try to indent a file that doesn't exist
+	# don't try to format a file that doesn't exist
 	unless (-f $source_filename)
 	{
 		warn "Could not find $source_filename";
 		return 0;
 	}
-	# 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/;
-	return 0 if $otherfile ne $source_filename && -f $otherfile;
-	$otherfile =~ s/\.y$/.l/;
-	return 0 if $otherfile ne $source_filename && -f $otherfile;
-
-	my $source = read_source($source_filename);
-	my $orig_source = $source;
-	my $error_message = '';
 
-	$source = pre_indent($source);
+	# 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;
 
-	$source = run_indent($source, \$error_message);
-	if ($source eq "")
+	# Formatting step: pgindent or perltidy
+	if ($is_c && $do_pgindent)
 	{
-		print STDERR "Failure in $source_filename: " . $error_message . "\n";
-		return 3;
-	}
+		my $source = read_source($source_filename);
+		my ($formatted, $failure) = format_c($source, $source_filename);
 
-	$source = post_indent($source);
+		if ($failure)
+		{
+			return 3;
+		}
 
-	if ($source ne $orig_source)
+		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)
 	{
-		if (!$diff && !$check)
+		my $source = read_source($source_filename);
+		my ($formatted, $failure) = format_perl($source, $source_filename);
+
+		if ($failure)
 		{
-			write_source($source, $source_filename);
+			return 3;
 		}
-		else
+
+		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)
 		{
-			if ($diff)
+			my ($output, $failed) = run_perlcritic($source_filename);
+			if ($failed)
 			{
-				my $output = diff($source, $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 3;
 			}
+		}
 
-			return 2 if $check;
+		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;
+			}
 		}
 	}
 
@@ -497,9 +772,13 @@ sub usage
 	my $message = shift;
 	my $helptext = <<'EOF';
 Usage:
-pgindent [OPTION]... [FILE|DIR]...
+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
@@ -508,6 +787,8 @@ Options:
 	--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)
@@ -524,12 +805,37 @@ EOF
 
 # main
 
-$filtered_typedefs_fh = load_typedefs();
+if ($do_pgindent)
+{
+	$filtered_typedefs_fh = load_typedefs();
+	check_indent();
+}
 
-check_indent();
+if ($do_perltidy)
+{
+	check_perltidy();
+}
+
+if ($do_perlcritic)
+{
+	check_perlcritic();
+}
 
 # any non-option arguments are files or directories to be processed
-push(@files, discover_files(@ARGV)) if @ARGV;
+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;
@@ -541,7 +847,8 @@ foreach my $commit (@commits)
 	my @affected = `git diff --diff-filter=ACMR --name-only $prev $commit`;
 	die "git error" if $?;
 	chomp(@affected);
-	push(@files, @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;
diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
new file mode 100755
index 00000000000..f1553075030
--- /dev/null
+++ b/src/tools/pgindent/pgindent
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Copyright (c) 2021-2026, PostgreSQL Global Development Group
+
+# Wrapper that runs pgcheck in pgindent-only mode.
+# See src/tools/pgcheck/pgcheck for the full implementation.
+
+exec "$(dirname "$0")/../pgcheck/pgcheck" --pgindent "$@"
diff --git a/src/tools/pgindent/pgperltidy b/src/tools/pgindent/pgperltidy
index 87838d6bde3..72273f50c9e 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 "$@"
-- 
2.53.0

From c65e6330ec14ea1a05b027b29269dcd5331e336c Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Wed, 4 Mar 2026 09:04:15 +0100
Subject: [PATCH v4 7/7] pgcheck: Add easy way of getting perltidy

pgcheck needs a very specific perltidy version, but getting that
installed is quite a hassle. Especially for people who don't use perl on
a daily basis. This commit adds a small script to download the exact
perltidy version that we need and install it locally in the repo. It
also teaches pgcheck to use that locally installed perltidy if it is
available.
---
 src/tools/pgcheck/pgcheck       | 25 +++++++++++--
 src/tools/pgindent/.gitignore   |  1 +
 src/tools/pgindent/README       | 16 ++++-----
 src/tools/pgindent/get_perltidy | 62 +++++++++++++++++++++++++++++++++
 4 files changed, 94 insertions(+), 10 deletions(-)
 create mode 100644 src/tools/pgindent/.gitignore
 create mode 100755 src/tools/pgindent/get_perltidy

diff --git a/src/tools/pgcheck/pgcheck b/src/tools/pgcheck/pgcheck
index d0586e9ae6c..f9fef61195a 100755
--- a/src/tools/pgcheck/pgcheck
+++ b/src/tools/pgcheck/pgcheck
@@ -175,8 +175,29 @@ if (!$indent && $sourcedir)
 }
 $indent ||= "pg_bsd_indent";
 
-# get perltidy location: command line wins, then environment, then PATH.
-my $perltidy = $perltidy_arg || $ENV{PERLTIDY} || "perltidy";
+# 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";
diff --git a/src/tools/pgindent/.gitignore b/src/tools/pgindent/.gitignore
new file mode 100644
index 00000000000..1497217113b
--- /dev/null
+++ b/src/tools/pgindent/.gitignore
@@ -0,0 +1 @@
+/perltidy/
diff --git a/src/tools/pgindent/README b/src/tools/pgindent/README
index b6cd4c6f6b7..ebc18d83e9c 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 00000000000..09ce4a195cb
--- /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"
-- 
2.53.0

Reply via email to