summaryrefslogtreecommitdiff
path: root/media/libjxl/src/tools/build_cleaner.py
diff options
context:
space:
mode:
Diffstat (limited to 'media/libjxl/src/tools/build_cleaner.py')
-rwxr-xr-xmedia/libjxl/src/tools/build_cleaner.py321
1 files changed, 321 insertions, 0 deletions
diff --git a/media/libjxl/src/tools/build_cleaner.py b/media/libjxl/src/tools/build_cleaner.py
new file mode 100755
index 0000000000..0a0df75635
--- /dev/null
+++ b/media/libjxl/src/tools/build_cleaner.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python3
+# Copyright (c) the JPEG XL Project Authors. All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+
+"""build_cleaner.py: Update build files.
+
+This tool keeps certain parts of the build files up to date.
+"""
+
+import argparse
+import collections
+import locale
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+
+def RepoFiles(src_dir):
+ """Return the list of files from the source git repository"""
+ git_bin = os.environ.get('GIT_BIN', 'git')
+ files = subprocess.check_output([git_bin, '-C', src_dir, 'ls-files'])
+ ret = files.decode(locale.getpreferredencoding()).splitlines()
+ ret.sort()
+ return ret
+
+def GetPrefixLibFiles(repo_files, prefix, suffixes=('.h', '.cc', '.ui')):
+ """Gets the library files that start with the prefix and end with source
+ code suffix."""
+ prefix_files = [
+ fn for fn in repo_files
+ if fn.startswith(prefix) and any(fn.endswith(suf) for suf in suffixes)]
+ return prefix_files
+
+# Type holding the different types of sources in libjxl:
+# * decoder and common sources,
+# * encoder-only sources,
+# * tests-only sources,
+# * google benchmark sources,
+# * threads library sources,
+# * extras library sources,
+# * libjxl (encoder+decoder) public include/ headers and
+# * threads public include/ headers.
+JxlSources = collections.namedtuple(
+ 'JxlSources', ['dec', 'enc', 'test', 'gbench', 'threads',
+ 'extras', 'jxl_public_hdrs', 'threads_public_hdrs'])
+
+def SplitLibFiles(repo_files):
+ """Splits the library files into the different groups.
+
+ """
+ testonly = (
+ 'testdata.h', 'test_utils.h', '_test.h', '_test.cc',
+ # _testonly.* files are library code used in tests only.
+ '_testonly.h', '_testonly.cc'
+ )
+ main_srcs = GetPrefixLibFiles(repo_files, 'lib/jxl/')
+ extras_srcs = GetPrefixLibFiles(repo_files, 'lib/extras/')
+ test_srcs = [fn for fn in main_srcs
+ if any(patt in fn for patt in testonly)]
+ lib_srcs = [fn for fn in main_srcs
+ if not any(patt in fn for patt in testonly)]
+
+ # Google benchmark sources.
+ gbench_srcs = sorted(fn for fn in lib_srcs + extras_srcs
+ if fn.endswith('_gbench.cc'))
+ lib_srcs = [fn for fn in lib_srcs if fn not in gbench_srcs]
+ # Exclude optional codecs from extras.
+ exclude_extras = [
+ '/dec/gif',
+ '/dec/apng', '/enc/apng',
+ '/dec/exr', '/enc/exr',
+ '/dec/jpg', '/enc/jpg',
+ ]
+ extras_srcs = [fn for fn in extras_srcs if fn not in gbench_srcs and
+ not any(patt in fn for patt in testonly) and
+ not any(patt in fn for patt in exclude_extras)]
+
+
+ enc_srcs = [fn for fn in lib_srcs
+ if os.path.basename(fn).startswith('enc_') or
+ os.path.basename(fn).startswith('butteraugli')]
+ enc_srcs.extend([
+ "lib/jxl/encode.cc",
+ "lib/jxl/encode_internal.h",
+ "lib/jxl/gaborish.cc",
+ "lib/jxl/gaborish.h",
+ "lib/jxl/huffman_tree.cc",
+ "lib/jxl/huffman_tree.h",
+ # Only the inlines in linalg.h header are used in the decoder.
+ # TODO(deymo): split out encoder only linalg.h functions.
+ "lib/jxl/linalg.cc",
+ "lib/jxl/optimize.cc",
+ "lib/jxl/optimize.h",
+ "lib/jxl/progressive_split.cc",
+ "lib/jxl/progressive_split.h",
+ # TODO(deymo): Add luminance.cc and luminance.h here too. Currently used
+ # by aux_out.h.
+ # dec_file is not intended to be part of the decoder library, so move it
+ # to the encoder source set
+ "lib/jxl/dec_file.cc",
+ "lib/jxl/dec_file.h",
+ ])
+ # Temporarily remove enc_bit_writer from the encoder sources: a lot of
+ # decoder source code still needs to be split up into encoder and decoder.
+ # Including the enc_bit_writer in the decoder allows to build a working
+ # libjxl_dec library.
+ # TODO(lode): remove the dependencies of the decoder on enc_bit_writer and
+ # remove enc_bit_writer from the dec_srcs again.
+ enc_srcs.remove("lib/jxl/enc_bit_writer.cc")
+ enc_srcs.remove("lib/jxl/enc_bit_writer.h")
+ enc_srcs.sort()
+
+ enc_srcs_set = set(enc_srcs)
+ lib_srcs = [fn for fn in lib_srcs if fn not in enc_srcs_set]
+
+ # The remaining of the files are in the dec_library.
+ dec_srcs = lib_srcs
+
+ thread_srcs = GetPrefixLibFiles(repo_files, 'lib/threads/')
+ thread_srcs = [fn for fn in thread_srcs
+ if not any(patt in fn for patt in testonly)]
+ public_hdrs = GetPrefixLibFiles(repo_files, 'lib/include/jxl/')
+
+ threads_public_hdrs = [fn for fn in public_hdrs if '_parallel_runner' in fn]
+ jxl_public_hdrs = list(sorted(set(public_hdrs) - set(threads_public_hdrs)))
+ return JxlSources(dec_srcs, enc_srcs, test_srcs, gbench_srcs, thread_srcs,
+ extras_srcs, jxl_public_hdrs, threads_public_hdrs)
+
+
+def CleanFile(args, filename, pattern_data_list):
+ """Replace a pattern match with new data in the passed file.
+
+ Given a regular expression pattern with a single () match, it runs the regex
+ over the passed filename and replaces the match () with the new data. If
+ args.update is set, it will update the file with the new contents, otherwise
+ it will return True when no changes were needed.
+
+ Multiple pairs of (regular expression, new data) can be passed to the
+ pattern_data_list parameter and will be applied in order.
+
+ The regular expression must match at least once in the file.
+ """
+ filepath = os.path.join(args.src_dir, filename)
+ with open(filepath, 'r') as f:
+ src_text = f.read()
+
+ if not pattern_data_list:
+ return True
+
+ new_text = src_text
+
+ for pattern, data in pattern_data_list:
+ offset = 0
+ chunks = []
+ for match in re.finditer(pattern, new_text):
+ chunks.append(new_text[offset:match.start(1)])
+ offset = match.end(1)
+ chunks.append(data)
+ if not chunks:
+ raise Exception('Pattern not found for %s: %r' % (filename, pattern))
+ chunks.append(new_text[offset:])
+ new_text = ''.join(chunks)
+
+ if new_text == src_text:
+ return True
+
+ if args.update:
+ print('Updating %s' % filename)
+ with open(filepath, 'w') as f:
+ f.write(new_text)
+ return True
+ else:
+ with tempfile.NamedTemporaryFile(
+ mode='w', prefix=os.path.basename(filename)) as new_file:
+ new_file.write(new_text)
+ new_file.flush()
+ subprocess.call(
+ ['diff', '-u', filepath, '--label', 'a/' + filename, new_file.name,
+ '--label', 'b/' + filename])
+ return False
+
+
+def BuildCleaner(args):
+ repo_files = RepoFiles(args.src_dir)
+ ok = True
+
+ # jxl version
+ with open(os.path.join(args.src_dir, 'lib/CMakeLists.txt'), 'r') as f:
+ cmake_text = f.read()
+
+ gni_patterns = []
+ for varname in ('JPEGXL_MAJOR_VERSION', 'JPEGXL_MINOR_VERSION',
+ 'JPEGXL_PATCH_VERSION'):
+ # Defined in CMakeLists.txt as "set(varname 1234)"
+ match = re.search(r'set\(' + varname + r' ([0-9]+)\)', cmake_text)
+ version_value = match.group(1)
+ gni_patterns.append((r'"' + varname + r'=([0-9]+)"', version_value))
+
+ jxl_src = SplitLibFiles(repo_files)
+
+ # libjxl
+ jxl_cmake_patterns = []
+ jxl_cmake_patterns.append(
+ (r'set\(JPEGXL_INTERNAL_SOURCES_DEC\n([^\)]+)\)',
+ ''.join(' %s\n' % fn[len('lib/'):] for fn in jxl_src.dec)))
+ jxl_cmake_patterns.append(
+ (r'set\(JPEGXL_INTERNAL_SOURCES_ENC\n([^\)]+)\)',
+ ''.join(' %s\n' % fn[len('lib/'):] for fn in jxl_src.enc)))
+ ok = CleanFile(
+ args, 'lib/jxl.cmake',
+ jxl_cmake_patterns) and ok
+
+ ok = CleanFile(
+ args, 'lib/jxl_benchmark.cmake',
+ [(r'set\(JPEGXL_INTERNAL_SOURCES_GBENCH\n([^\)]+)\)',
+ ''.join(' %s\n' % fn[len('lib/'):] for fn in jxl_src.gbench))]) and ok
+
+ gni_patterns.append((
+ r'libjxl_dec_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):] for fn in jxl_src.dec)))
+ gni_patterns.append((
+ r'libjxl_enc_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):] for fn in jxl_src.enc)))
+ gni_patterns.append((
+ r'libjxl_gbench_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):] for fn in jxl_src.gbench)))
+
+
+ tests = [fn[len('lib/'):] for fn in jxl_src.test if fn.endswith('_test.cc')]
+ testlib = [fn[len('lib/'):] for fn in jxl_src.test
+ if not fn.endswith('_test.cc')]
+ gni_patterns.append((
+ r'libjxl_tests_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn for fn in tests)))
+ gni_patterns.append((
+ r'libjxl_testlib_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn for fn in testlib)))
+
+ # libjxl_threads
+ ok = CleanFile(
+ args, 'lib/jxl_threads.cmake',
+ [(r'set\(JPEGXL_THREADS_SOURCES\n([^\)]+)\)',
+ ''.join(' %s\n' % fn[len('lib/'):] for fn in jxl_src.threads))]) and ok
+
+ gni_patterns.append((
+ r'libjxl_threads_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):] for fn in jxl_src.threads)))
+
+ # libjxl_extras
+ ok = CleanFile(
+ args, 'lib/jxl_extras.cmake',
+ [(r'set\(JPEGXL_EXTRAS_SOURCES\n([^\)]+)\)',
+ ''.join(' %s\n' % fn[len('lib/'):] for fn in jxl_src.extras))]) and ok
+
+ gni_patterns.append((
+ r'libjxl_extras_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):] for fn in jxl_src.extras)))
+
+ # libjxl_profiler
+ profiler_srcs = [fn[len('lib/'):] for fn in repo_files
+ if fn.startswith('lib/profiler')]
+ ok = CleanFile(
+ args, 'lib/jxl_profiler.cmake',
+ [(r'set\(JPEGXL_PROFILER_SOURCES\n([^\)]+)\)',
+ ''.join(' %s\n' % fn for fn in profiler_srcs))]) and ok
+
+ gni_patterns.append((
+ r'libjxl_profiler_sources = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn for fn in profiler_srcs)))
+
+ # Public headers.
+ gni_patterns.append((
+ r'libjxl_public_headers = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):]
+ for fn in jxl_src.jxl_public_hdrs)))
+ gni_patterns.append((
+ r'libjxl_threads_public_headers = \[\n([^\]]+)\]',
+ ''.join(' "%s",\n' % fn[len('lib/'):]
+ for fn in jxl_src.threads_public_hdrs)))
+
+
+ # Update the list of tests. CMake version include test files in other libs,
+ # not just in libjxl.
+ tests = [fn[len('lib/'):] for fn in repo_files
+ if fn.endswith('_test.cc') and fn.startswith('lib/')]
+ ok = CleanFile(
+ args, 'lib/jxl_tests.cmake',
+ [(r'set\(TEST_FILES\n([^\)]+) ### Files before this line',
+ ''.join(' %s\n' % fn for fn in tests))]) and ok
+ ok = CleanFile(
+ args, 'lib/jxl_tests.cmake',
+ [(r'set\(TESTLIB_FILES\n([^\)]+)\)',
+ ''.join(' %s\n' % fn for fn in testlib))]) and ok
+
+ # Update lib.gni
+ ok = CleanFile(args, 'lib/lib.gni', gni_patterns) and ok
+
+ return ok
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--src-dir',
+ default=os.path.realpath(os.path.join(
+ os.path.dirname(__file__), '..')),
+ help='path to the build directory')
+ parser.add_argument('--update', default=False, action='store_true',
+ help='update the build files instead of only checking')
+ args = parser.parse_args()
+ if not BuildCleaner(args):
+ print('Build files need update.')
+ sys.exit(2)
+
+
+if __name__ == '__main__':
+ main()