diff --git a/doc/Makefile b/doc/Makefile
index 5ff25d54..d04be776 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -58,6 +58,11 @@ putty.hlp: $(INPUTS)
 putty.info: $(INPUTS)
 	$(HALIBUT) --info $(INPUTS)
 
+licence.but: ../licence.pl ../LICENCE
+	perl $< --licencedoc -o $@
+copy.but: ../licence.pl ../LICENCE
+	perl $< --copyrightdoc -o $@
+
 MKMAN = $(HALIBUT) --man=$@ mancfg.but $<
 MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \
            pageant.1 psocks.1 psusan.1
diff --git a/licence.pl b/licence.pl
old mode 100644
new mode 100755
index e1d3a51f..f927bcb2
--- a/licence.pl
+++ b/licence.pl
@@ -1,17 +1,31 @@
-#!/usr/bin/env perl -w
+#!/usr/bin/env perl
 
 # This script generates licence.h (containing the PuTTY licence in the
 # form of macros expanding to C string literals) from the LICENCE
 # master file. It also regenerates the licence-related Halibut input
 # files.
 
+use warnings;
 use File::Basename;
+use Getopt::Long;
 
-# Read the input file.
-$infile = "LICENCE";
+my $usage = "usage: licence.pl (--header|--licencedoc|--copyrightdoc) " .
+            "[-o OUTFILE]\n";
+my $mode = undef;
+my $output = undef;
+GetOptions("--header" => sub {$mode = "header"},
+           "--licencedoc" => sub {$mode = "licencedoc"},
+           "--copyrightdoc" => sub {$mode = "copyrightdoc"},
+           "o|output=s" => \$output)
+    and defined $mode
+    or die $usage;
+
+# Read the input file. We expect to find that alongside this script.
+my $infile = (dirname __FILE__) . "/LICENCE";
 open my $in, $infile or die "$infile: open: $!\n";
 my @lines = ();
 while (<$in>) {
+    y/\r//d;
     chomp;
     push @lines, $_;
 }
@@ -36,31 +50,61 @@ die "bad format of first paragraph\n"
     unless $paras[0] =~ m!copyright ([^\.]*)\.!i;
 $shortdetails = $1;
 
-# Write out licence.h.
+my $out = "";
 
-$outfile = "licence.h";
-open my $out, ">", $outfile or die "$outfile: open: $!\n";
-select $out;
+if ($mode eq "header") {
+    $out .= "/*\n";
+    $out .= " * licence.h - macro definitions for the PuTTY licence.\n";
+    $out .= " *\n";
+    $out .= " * Generated by @{[basename __FILE__]} from $infile.\n";
+    $out .= " * You should edit those files rather than editing this one.\n";
+    $out .= " */\n";
+    $out .= "\n";
 
-print "/*\n";
-print " * $outfile - macro definitions for the PuTTY licence.\n";
-print " *\n";
-print " * Generated by @{[basename __FILE__]} from $infile.\n";
-print " * You should edit those files rather than editing this one.\n";
-print " */\n";
-print "\n";
+    $out .= "#define LICENCE_TEXT(parsep) \\\n";
+    for my $i (0..$#paras) {
+        my $lit = &stringlit($paras[$i]);
+        $out .= "    parsep \\\n" if $i > 0;
+        $out .= "    \"$lit\"";
+        $out .= " \\" if $i < $#paras;
+        $out .= "\n";
+    }
+    $out .= "\n";
 
-print "#define LICENCE_TEXT(parsep) \\\n";
-for my $i (0..$#paras) {
-    my $lit = &stringlit($paras[$i]);
-    print "    parsep \\\n" if $i > 0;
-    print "    \"$lit\"";
-    print " \\" if $i < $#paras;
-    print "\n";
+    $out .= sprintf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n",
+        &stringlit($shortdetails);
+} elsif ($mode eq "licencedoc") {
+    # Write out doc/licence.but.
+
+    $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n";
+    $out .= "\\# You should edit those files rather than editing this one.\n\n";
+
+    $out .= "\\A{licence} PuTTY \\ii{Licence}\n\n";
+
+    for my $i (0..$#paras) {
+        my $para = &halibutescape($paras[$i]);
+        if ($i == 0) {
+            $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1
+        }
+        $out .= "$para\n\n";
+    }
+} elsif ($mode eq "copyrightdoc") {
+    # Write out doc/copy.but, which defines a macro used in the manual
+    # preamble blurb.
+
+    $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n";
+    $out .= "\\# You should edit those files rather than editing this one.\n\n";
+
+    $out .= sprintf "\\define{shortcopyrightdetails} %s\n\n",
+        &halibutescape($shortdetails);
 }
-print "\n";
 
-printf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n", &stringlit($shortdetails);
+my $outfile;
+my $opened = (defined $output) ?
+    (open $outfile, ">", $output) : (open $outfile, ">-");
+$opened or die "$output: open: $!\n";
+print $outfile $out;
+close $outfile;
 
 sub stringlit {
     my ($lit) = @_;
@@ -69,47 +113,22 @@ sub stringlit {
     return $lit;
 }
 
-close $out;
-
-# Write out doc/licence.but.
-
-$outfile = "doc/licence.but";
-open $out, ">", $outfile or die "$outfile: open: $!\n";
-select $out;
-
-print "\\# Generated by @{[basename __FILE__]} from $infile.\n";
-print "\\# You should edit those files rather than editing this one.\n\n";
-
-print "\\A{licence} PuTTY \\ii{Licence}\n\n";
-
-for my $i (0..$#paras) {
-    my $para = &halibutescape($paras[$i]);
-    if ($i == 0) {
-        $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1
-    }
-    print "$para\n\n";
-}
-
-close $out;
-
-# And write out doc/copy.but, which defines a macro used in the manual
-# preamble blurb.
-
-$outfile = "doc/copy.but";
-open $out, ">", $outfile or die "$outfile: open: $!\n";
-select $out;
-
-print "\\# Generated by @{[basename __FILE__]} from $infile.\n";
-print "\\# You should edit those files rather than editing this one.\n\n";
-
-printf "\\define{shortcopyrightdetails} %s\n\n",
-    &halibutescape($shortdetails);
-
-close $out;
-
 sub halibutescape {
     my ($text) = @_;
     $text =~ s![\\{}]!\\$&!g; # Halibut escaping
     $text =~ s!"([^"]*)"!\\q{$1}!g; # convert quoted strings to \q{}
     return $text;
 }
+
+sub write {
+    my ($filename, $newcontents) = @_;
+    if (open my $fh, "<", $filename) {
+        my $oldcontents = "";
+        $oldcontents .= $_ while <$fh>;
+        close $fh;
+        return if $oldcontents eq $newcontents;
+    }
+    open my $fh, ">", $filename or die "$filename: open: $!\n";
+    print $fh $newcontents;
+    close $fh;
+}
diff --git a/mkfiles.pl b/mkfiles.pl
index 3282f359..8c048c33 100755
--- a/mkfiles.pl
+++ b/mkfiles.pl
@@ -48,7 +48,10 @@ open IN, "Recipe" or do {
 # sbcsgen.pl, and licence.h is likewise generated by licence.pl. We
 # need to generate those _now_, before attempting dependency analysis.
 eval 'chdir "charset"; require "./sbcsgen.pl"; chdir ".."; select STDOUT;';
+@orig_ARGV = @ARGV;
+@ARGV = ("--header", "-o", "licence.h");
 eval 'require "./licence.pl"; select STDOUT;';
+@ARGV = @orig_ARGV;
 
 @srcdirs = ("./");