From d686f2ff9c40235085558fed91a57d05c7656335 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 25 Aug 2020 20:52:32 +0200 Subject: [PATCH] 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-.tar.gz freetype-.tar.xz ft.zip --- ChangeLog | 32 ++++ scripts/make_distribution_archives.py | 208 ++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100755 scripts/make_distribution_archives.py diff --git a/ChangeLog b/ChangeLog index 6e11e337c..574e818f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +2020-09-21 David Turner + + 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-.tar.gz + freetype-.tar.xz + ft.zip + 2020-09-21 Werner Lemberg * 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 000000000..f0ffe8169 --- /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())