diff --git a/source/mason/build/analysers/compressman.d b/source/mason/build/analysers/compressman.d new file mode 100644 index 0000000000..981d156bf4 --- /dev/null +++ b/source/mason/build/analysers/compressman.d @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: Copyright © 2020-2023 Serpent OS Developers + * + * SPDX-License-Identifier: Zlib + */ + +/** + * mason.build.analysers.compressman; + * + * Compress man or info pages with gzip + * + * Authors: Copyright © 2020-2023 Serpent OS Developers + * License: Zlib + */ + +module mason.build.analysers.compressman; + +import mason.build.builder : Builder; +import mason.build.context; +import moss.core.sizing : formattedSize; +import std.algorithm : canFind, endsWith; +import std.experimental.logger; +import std.file : getTimes, read, readLink, setTimes, symlink, write; +import std.stdio: File, toFile; +import std.string : format; + +public import moss.deps.analysis; + +/** + * Detect man or info pages + * + * Params: + * analyser = Scoped analyser for this run + * fileInfo = Current file to run analysis on + * Returns: AnalysisReturn.NextFunction when a page is found and compressman is enabled, + * otherwise AnalysisReturn.NextHandler. + */ +public AnalysisReturn acceptManInfoPages(scope Analyser analyser, ref FileInfo fileInfo) +{ + if (!buildContext.spec.options.compressman) + { + return AnalysisReturn.NextHandler; + } + if (fileInfo.type == FileType.Directory) + { + return AnalysisReturn.NextHandler; + } + + auto filename = fileInfo.path; + + /* Accept Man pages */ + // FIXME: Some man pages do not end with 1..9 but with .1foobar (such as openssl) + if (filename.canFind("man") && filename.endsWith("1", "2", "3", "4", "5", "6", "7", "8", "9")) + { + return AnalysisReturn.NextFunction; + } + /* Accept Info pages */ + if (filename.canFind("info") && filename.endsWith(".info")) + { + return AnalysisReturn.NextFunction; + } + + return AnalysisReturn.NextHandler; +} + +/** + * Compress man or info pages with gzip + * + * Params: + * analyser = Scoped analyser for this run + * fileInfo = Current file to run analysis on + * Returns: AnalysisReturn.IgnoreFile always + */ +static AnalysisReturn compressPage(scope Analyser analyser, ref FileInfo fileInfo) +{ + import std.zlib : Compress, HeaderFormat; + import std.datetime : abs, SysTime; + + auto filename = fileInfo.path; + auto instance = analyser.userdata!Builder; + + immutable ext = ".gz"; + + /* We have a symlink file, update it to point to the compressed file */ + // FIXME: Seemingly working but not tested on a real install + if (fileInfo.type == FileType.Symlink) + { + auto actualPath = readLink(fileInfo.fullPath); + trace(format!"[Man] Updated symlink %s to %s"(filename, format!"%s%s"(actualPath, ext))); + symlink(format!"%s%s"(actualPath, ext), format!"%s%s"(fileInfo.fullPath, ext)); + /* Collect the updated symlink into the manifest */ + instance.collectPath(format!"%s%s"(fileInfo.fullPath, ext), instance.installRoot); + /* Remove the original file */ + return AnalysisReturn.IgnoreFile; + } + + /* Get atime, mtime of the file */ + SysTime accessTime, modificationTime; + getTimes(fileInfo.fullPath, accessTime, modificationTime); + + /* Compress it in memory */ + Compress cmp = new Compress(9, HeaderFormat.gzip); + auto page = read(fileInfo.fullPath); + auto compressedPage = cmp.compress(page) ~ cmp.flush(); + + /* Stats */ + immutable double presize = page.length; + immutable double postsize = compressedPage.length; + info(format!"[Man] Compressed: %s. Original size: %s Compressed size: %s"(format!"%s%s"(filename, ext), + formattedSize(presize), formattedSize(postsize))); + + /* Write to disk with extension */ + write(format!"%s%s"(fileInfo.fullPath, ext), compressedPage); + + /* Set atime, mtime of compressed file to the original for reproducibility */ + setTimes(format!"%s%s"(fileInfo.fullPath, ext), accessTime, modificationTime); + + /* Collect the compressed file into the manifest */ + instance.collectPath(format!"%s%s"(fileInfo.fullPath, ext), instance.installRoot); + + /* Remove the original pre-compressed file */ + return AnalysisReturn.IgnoreFile; +} diff --git a/source/mason/build/analysers/package.d b/source/mason/build/analysers/package.d index 4ac8ae90ba..35bd606b76 100644 --- a/source/mason/build/analysers/package.d +++ b/source/mason/build/analysers/package.d @@ -17,6 +17,7 @@ module mason.build.analysers; public import mason.build.analysers.binary; public import mason.build.analysers.cmake; +public import mason.build.analysers.compressman; public import mason.build.analysers.elves; public import mason.build.analysers.rejects; public import mason.build.analysers.pkgconfig; diff --git a/source/mason/build/builder.d b/source/mason/build/builder.d index 49b2e4f52c..6b9565197f 100644 --- a/source/mason/build/builder.d +++ b/source/mason/build/builder.d @@ -327,6 +327,9 @@ private: &acceptCmakeFiles, &handleCmakeFiles, &includeFile ], 50), + /* Compress man and info pages if enabled */ + AnalysisChain("compressman", [&acceptManInfoPages, &compressPage], 40), + /* Default inclusion policy */ AnalysisChain("default", [&includeFile], 0), ]; diff --git a/source/mason/meson.build b/source/mason/meson.build index 9b2d80da25..2363a2f109 100644 --- a/source/mason/meson.build +++ b/source/mason/meson.build @@ -4,6 +4,7 @@ libmason_sources = [ 'package.d', 'build/analysers/binary.d', 'build/analysers/cmake.d', + 'build/analysers/compressman.d', 'build/analysers/elves.d', 'build/analysers/package.d', 'build/analysers/pkgconfig.d',