Add python script for building tarballs.

* scripts/make_distribution_archives.py: New file.

This standalone Python script should be equivalent to running `make
dist` with the Make-based build system, with the following minor
differences:

- Since `make distclean` doesn't always clean up `objs/` properly,
  `make dist` archives may contain some stale binaries like
  `objs/.libs/libfreetype.so.6` or others.

- `config.guess` and `config.sub` are not updated unless option
  `--gnu-config-dir=DIR` is used to specify the location of these
  files.

- Some bits of the auto-generated reference documentation may
  appear in slightly different order, probably due to issues related
  to mkdocs and docwriter.

As an example, the call

  scripts/make_distribution_archives.py /tmp/freetype2-dist

creates the following files under `/tmp/freetype2-dist`:

  freetype-<version>.tar.gz
  freetype-<version>.tar.xz
  ft<winversion>.zip
diff --git a/ChangeLog b/ChangeLog
index 6e11e33..574e818 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2020-09-21  David Turner  <david@freetype.org>
+
+	Add python script for building tarballs.
+
+	* scripts/make_distribution_archives.py: New file.
+
+	This standalone Python script should be equivalent to running `make
+	dist` with the Make-based build system, with the following minor
+	differences:
+
+	- Since `make distclean` doesn't always clean up `objs/` properly,
+	  `make dist` archives may contain some stale binaries like
+	  `objs/.libs/libfreetype.so.6` or others.
+
+	- `config.guess` and `config.sub` are not updated unless option
+	  `--gnu-config-dir=DIR` is used to specify the location of these
+	  files.
+
+	- Some bits of the auto-generated reference documentation may
+	  appear in slightly different order, probably due to issues related
+	  to mkdocs and docwriter.
+
+	As an example, the call
+
+	  scripts/make_distribution_archives.py /tmp/freetype2-dist
+
+	creates the following files under `/tmp/freetype2-dist`:
+
+	  freetype-<version>.tar.gz
+	  freetype-<version>.tar.xz
+	  ft<winversion>.zip
+
 2020-09-21  Werner Lemberg  <wl@gnu.org>
 
 	* scripts/extract_freetype_version.py: Fix regex typos.
diff --git a/scripts/make_distribution_archives.py b/scripts/make_distribution_archives.py
new file mode 100755
index 0000000..f0ffe81
--- /dev/null
+++ b/scripts/make_distribution_archives.py
@@ -0,0 +1,208 @@
+#!/usr/bin/env python3
+"""Generate distribution archives for a given FreeType 2 release."""
+
+from __future__ import print_function
+
+import argparse
+import atexit
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+_SCRIPT_DIR = os.path.dirname(__file__)
+_TOP_DIR = os.path.abspath(os.path.join(_SCRIPT_DIR, ".."))
+
+
+def get_cmd_output(cmd, cwd=None):
+    """Run a command and return its output as a string."""
+    if cwd is not None:
+        out = subprocess.check_output(cmd, cwd=cwd)
+    else:
+        out = subprocess.check_output(cmd)
+    return out.decode("utf-8").rstrip()
+
+
+def is_git_dir_clean(git_dir):
+    """Return True iff |git_dir| is a git directory in clean state."""
+    out = get_cmd_output(["git", "status", "--porcelain"], cwd=git_dir)
+    return len(out) == 0
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+
+    parser.add_argument(
+        "--source_dir", default=_TOP_DIR, help="Source directory path."
+    )
+
+    parser.add_argument(
+        "--version",
+        help=(
+            "Specify alternate FreeType version (it is otherwise extracted"
+            " from current sources by default)."
+        ),
+    )
+
+    parser.add_argument(
+        "--gnu-config-dir",
+        help=(
+            "Path of input directory containing recent `config.guess` and"
+            " `config.sub` files from GNU config."
+        ),
+    )
+
+    parser.add_argument(
+        "--build-dir",
+        help="Specify build directory. Only used for debugging this script.",
+    )
+
+    parser.add_argument(
+        "--ignore-clean-check",
+        action="store_true",
+        help=(
+            "Do not check for a clean source git repository. Only used for"
+            " debugging this script."
+        ),
+    )
+
+    parser.add_argument(
+        "output_dir", help="Output directory for generated archives."
+    )
+
+    args = parser.parse_args()
+
+    git_dir = args.source_dir if args.source_dir else _TOP_DIR
+    if not args.ignore_clean_check and not is_git_dir_clean(git_dir):
+        sys.stderr.write(
+            "ERROR: Your git repository is not in a clean state: %s\n"
+            % git_dir
+        )
+        return 1
+
+    if args.version:
+        version = args.version
+    else:
+        # Extract FreeType version from sources.
+        version = get_cmd_output(
+            [
+                sys.executable,
+                os.path.join(_SCRIPT_DIR, "extract_freetype_version.py"),
+                os.path.join(_TOP_DIR, "include", "freetype", "freetype.h"),
+            ]
+        )
+
+    # Determine the build directory. This will be a temporary file that is
+    # cleaned up on script exit by default, unless --build-dir=DIR is used,
+    # in which case we only create and empty the directory, but never remove
+    # its content on exit.
+    if args.build_dir:
+        build_dir = args.build_dir
+        if not os.path.exists(build_dir):
+            os.makedirs(build_dir)
+        else:
+            # Remove anything from the build directory, if any.
+            for item in os.listdir(build_dir):
+                file_path = os.path.join(build_dir, item)
+                if os.path.isdir(file_path):
+                    shutil.rmtree(file_path)
+                else:
+                    os.unlink(file_path)
+    else:
+        # Create a temporary directory, and ensure it is removed on exit.
+        build_dir = tempfile.mkdtemp(prefix="freetype-dist-")
+
+        def clean_build_dir():
+            shutil.rmtree(build_dir)
+
+        atexit.register(clean_build_dir)
+
+    # Copy all source files known to git into $BUILD_DIR/freetype-$VERSION
+    # with the exception of .gitignore and .mailmap files.
+    source_files = [
+        f
+        for f in get_cmd_output(["git", "ls-files"], cwd=git_dir).split("\n")
+        if os.path.basename(f) not in (".gitignore", ".mailmap")
+    ]
+
+    freetype_dir = "freetype-" + version
+    tmp_src_dir = os.path.join(build_dir, freetype_dir)
+    os.makedirs(tmp_src_dir)
+
+    for src in source_files:
+        dst = os.path.join(tmp_src_dir, src)
+        dst_dir = os.path.dirname(dst)
+        if not os.path.exists(dst_dir):
+            os.makedirs(dst_dir)
+        shutil.copyfile(src, dst)
+
+    # Run autogen.sh in directory.
+    subprocess.check_call(["/bin/sh", "autogen.sh"], cwd=tmp_src_dir)
+    shutil.rmtree(
+        os.path.join(tmp_src_dir, "builds", "unix", "autom4te.cache")
+    )
+
+    # Copy config.guess and config.sub if possible!
+    if args.gnu_config_dir:
+        for f in ("config.guess", "config.sub"):
+            shutil.copyfile(
+                os.path.join(args.gnu_config_dir, f),
+                os.path.join(tmp_src_dir, "builds", "unix", f),
+            )
+
+    # Generate reference documentation under docs/
+    subprocess.check_call(
+        [
+            sys.executable,
+            os.path.join(_SCRIPT_DIR, "generate_reference_docs.py"),
+            "--input-dir",
+            tmp_src_dir,
+            "--version",
+            version,
+            "--output-dir",
+            os.path.join(tmp_src_dir, "docs"),
+        ]
+    )
+
+    shutil.rmtree(os.path.join(tmp_src_dir, "docs", "markdown"))
+    os.unlink(os.path.join(tmp_src_dir, "docs", "mkdocs.yml"))
+
+    # Generate our archives
+    freetype_tar = freetype_dir + ".tar"
+
+    subprocess.check_call(
+        ["tar", "-H", "ustar", "-chf", freetype_tar, freetype_dir],
+        cwd=build_dir,
+    )
+
+    subprocess.check_call(
+        ["gzip", "-9", "--keep", freetype_tar], cwd=build_dir
+    )
+
+    subprocess.check_call(["xz", "--keep", freetype_tar], cwd=build_dir)
+
+    ftwinversion = "ft" + "".join(version.split("."))
+    subprocess.check_call(
+        ["zip", "-qlr9", ftwinversion + ".zip", freetype_dir], cwd=build_dir
+    )
+
+    # Copy file to output directory now.
+    if not os.path.exists(args.output_dir):
+        os.makedirs(args.output_dir)
+
+    for f in (
+        freetype_tar + ".gz",
+        freetype_tar + ".xz",
+        ftwinversion + ".zip",
+    ):
+        shutil.copyfile(
+            os.path.join(build_dir, f), os.path.join(args.output_dir, f)
+        )
+
+    # Done!
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())