blob: d603caf858ba6299d2cb3c7a854d042f36b2b6bd [file] [log] [blame]
Patrick Williams92b42cb2022-09-03 06:53:57 -05001#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7# Compress man pages in ${mandir} and info pages in ${infodir}
8#
9# 1. The doc will be compressed to gz format by default.
10#
11# 2. It will automatically correct the compressed doc which is not
12# in ${DOC_COMPRESS} but in ${DOC_COMPRESS_LIST} to the format
13# of ${DOC_COMPRESS} policy
14#
15# 3. It is easy to add a new type compression by editing
16# local.conf, such as:
17# DOC_COMPRESS_LIST:append = ' abc'
18# DOC_COMPRESS = 'abc'
19# DOC_COMPRESS_CMD[abc] = 'abc compress cmd ***'
20# DOC_DECOMPRESS_CMD[abc] = 'abc decompress cmd ***'
21
22# All supported compression policy
23DOC_COMPRESS_LIST ?= "gz xz bz2"
24
25# Compression policy, must be one of ${DOC_COMPRESS_LIST}
26DOC_COMPRESS ?= "gz"
27
28# Compression shell command
29DOC_COMPRESS_CMD[gz] ?= 'gzip -v -9 -n'
30DOC_COMPRESS_CMD[bz2] ?= "bzip2 -v -9"
31DOC_COMPRESS_CMD[xz] ?= "xz -v"
32
33# Decompression shell command
34DOC_DECOMPRESS_CMD[gz] ?= 'gunzip -v'
35DOC_DECOMPRESS_CMD[bz2] ?= "bunzip2 -v"
36DOC_DECOMPRESS_CMD[xz] ?= "unxz -v"
37
38PACKAGE_PREPROCESS_FUNCS += "package_do_compress_doc compress_doc_updatealternatives"
39python package_do_compress_doc() {
40 compress_mode = d.getVar('DOC_COMPRESS')
41 compress_list = (d.getVar('DOC_COMPRESS_LIST') or '').split()
42 if compress_mode not in compress_list:
43 bb.fatal('Compression policy %s not supported (not listed in %s)\n' % (compress_mode, compress_list))
44
45 dvar = d.getVar('PKGD')
46 compress_cmds = {}
47 decompress_cmds = {}
48 for mode in compress_list:
49 compress_cmds[mode] = d.getVarFlag('DOC_COMPRESS_CMD', mode)
50 decompress_cmds[mode] = d.getVarFlag('DOC_DECOMPRESS_CMD', mode)
51
52 mandir = os.path.abspath(dvar + os.sep + d.getVar("mandir"))
53 if os.path.exists(mandir):
54 # Decompress doc files which format is not compress_mode
55 decompress_doc(mandir, compress_mode, decompress_cmds)
56 compress_doc(mandir, compress_mode, compress_cmds)
57
58 infodir = os.path.abspath(dvar + os.sep + d.getVar("infodir"))
59 if os.path.exists(infodir):
60 # Decompress doc files which format is not compress_mode
61 decompress_doc(infodir, compress_mode, decompress_cmds)
62 compress_doc(infodir, compress_mode, compress_cmds)
63}
64
65def _get_compress_format(file, compress_format_list):
66 for compress_format in compress_format_list:
67 compress_suffix = '.' + compress_format
68 if file.endswith(compress_suffix):
69 return compress_format
70
71 return ''
72
73# Collect hardlinks to dict, each element in dict lists hardlinks
74# which points to the same doc file.
75# {hardlink10: [hardlink11, hardlink12],,,}
76# The hardlink10, hardlink11 and hardlink12 are the same file.
77def _collect_hardlink(hardlink_dict, file):
78 for hardlink in hardlink_dict:
79 # Add to the existed hardlink
80 if os.path.samefile(hardlink, file):
81 hardlink_dict[hardlink].append(file)
82 return hardlink_dict
83
84 hardlink_dict[file] = []
85 return hardlink_dict
86
87def _process_hardlink(hardlink_dict, compress_mode, shell_cmds, decompress=False):
88 import subprocess
89 for target in hardlink_dict:
90 if decompress:
91 compress_format = _get_compress_format(target, shell_cmds.keys())
92 cmd = "%s -f %s" % (shell_cmds[compress_format], target)
93 bb.note('decompress hardlink %s' % target)
94 else:
95 cmd = "%s -f %s" % (shell_cmds[compress_mode], target)
96 bb.note('compress hardlink %s' % target)
97 (retval, output) = subprocess.getstatusoutput(cmd)
98 if retval:
99 bb.warn("de/compress file failed %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else ""))
100 return
101
102 for hardlink_dup in hardlink_dict[target]:
103 if decompress:
104 # Remove compress suffix
105 compress_suffix = '.' + compress_format
106 new_hardlink = hardlink_dup[:-len(compress_suffix)]
107 new_target = target[:-len(compress_suffix)]
108 else:
109 # Append compress suffix
110 compress_suffix = '.' + compress_mode
111 new_hardlink = hardlink_dup + compress_suffix
112 new_target = target + compress_suffix
113
114 bb.note('hardlink %s-->%s' % (new_hardlink, new_target))
115 if not os.path.exists(new_hardlink):
116 os.link(new_target, new_hardlink)
117 if os.path.exists(hardlink_dup):
118 os.unlink(hardlink_dup)
119
120def _process_symlink(file, compress_format, decompress=False):
121 compress_suffix = '.' + compress_format
122 if decompress:
123 # Remove compress suffix
124 new_linkname = file[:-len(compress_suffix)]
125 new_source = os.readlink(file)[:-len(compress_suffix)]
126 else:
127 # Append compress suffix
128 new_linkname = file + compress_suffix
129 new_source = os.readlink(file) + compress_suffix
130
131 bb.note('symlink %s-->%s' % (new_linkname, new_source))
132 if not os.path.exists(new_linkname):
133 os.symlink(new_source, new_linkname)
134
135 os.unlink(file)
136
137def _is_info(file):
138 flags = '.info .info-'.split()
139 for flag in flags:
140 if flag in os.path.basename(file):
141 return True
142
143 return False
144
145def _is_man(file):
146 import re
147
148 # It refers MANSECT-var in man(1.6g)'s man.config
149 # ".1:.1p:.8:.2:.3:.3p:.4:.5:.6:.7:.9:.0p:.tcl:.n:.l:.p:.o"
150 # Not start with '.', and contain the above colon-seperate element
151 p = re.compile(r'[^\.]+\.([1-9lnop]|0p|tcl)')
152 if p.search(file):
153 return True
154
155 return False
156
157def _is_compress_doc(file, compress_format_list):
158 compress_format = _get_compress_format(file, compress_format_list)
159 compress_suffix = '.' + compress_format
160 if file.endswith(compress_suffix):
161 # Remove the compress suffix
162 uncompress_file = file[:-len(compress_suffix)]
163 if _is_info(uncompress_file) or _is_man(uncompress_file):
164 return True, compress_format
165
166 return False, ''
167
168def compress_doc(topdir, compress_mode, compress_cmds):
169 import subprocess
170 hardlink_dict = {}
171 for root, dirs, files in os.walk(topdir):
172 for f in files:
173 file = os.path.join(root, f)
174 if os.path.isdir(file):
175 continue
176
177 if _is_info(file) or _is_man(file):
178 # Symlink
179 if os.path.islink(file):
180 _process_symlink(file, compress_mode)
181 # Hardlink
182 elif os.lstat(file).st_nlink > 1:
183 _collect_hardlink(hardlink_dict, file)
184 # Normal file
185 elif os.path.isfile(file):
186 cmd = "%s %s" % (compress_cmds[compress_mode], file)
187 (retval, output) = subprocess.getstatusoutput(cmd)
188 if retval:
189 bb.warn("compress failed %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else ""))
190 continue
191 bb.note('compress file %s' % file)
192
193 _process_hardlink(hardlink_dict, compress_mode, compress_cmds)
194
195# Decompress doc files which format is not compress_mode
196def decompress_doc(topdir, compress_mode, decompress_cmds):
197 import subprocess
198 hardlink_dict = {}
199 decompress = True
200 for root, dirs, files in os.walk(topdir):
201 for f in files:
202 file = os.path.join(root, f)
203 if os.path.isdir(file):
204 continue
205
206 res, compress_format = _is_compress_doc(file, decompress_cmds.keys())
207 # Decompress files which format is not compress_mode
208 if res and compress_mode!=compress_format:
209 # Symlink
210 if os.path.islink(file):
211 _process_symlink(file, compress_format, decompress)
212 # Hardlink
213 elif os.lstat(file).st_nlink > 1:
214 _collect_hardlink(hardlink_dict, file)
215 # Normal file
216 elif os.path.isfile(file):
217 cmd = "%s %s" % (decompress_cmds[compress_format], file)
218 (retval, output) = subprocess.getstatusoutput(cmd)
219 if retval:
220 bb.warn("decompress failed %s (cmd was %s)%s" % (retval, cmd, ":\n%s" % output if output else ""))
221 continue
222 bb.note('decompress file %s' % file)
223
224 _process_hardlink(hardlink_dict, compress_mode, decompress_cmds, decompress)
225
226python compress_doc_updatealternatives () {
227 if not bb.data.inherits_class('update-alternatives', d):
228 return
229
230 mandir = d.getVar("mandir")
231 infodir = d.getVar("infodir")
232 compress_mode = d.getVar('DOC_COMPRESS')
233 for pkg in (d.getVar('PACKAGES') or "").split():
234 old_names = (d.getVar('ALTERNATIVE:%s' % pkg) or "").split()
235 new_names = []
236 for old_name in old_names:
237 old_link = d.getVarFlag('ALTERNATIVE_LINK_NAME', old_name)
238 old_target = d.getVarFlag('ALTERNATIVE_TARGET_%s' % pkg, old_name) or \
239 d.getVarFlag('ALTERNATIVE_TARGET', old_name) or \
240 d.getVar('ALTERNATIVE_TARGET_%s' % pkg) or \
241 d.getVar('ALTERNATIVE_TARGET') or \
242 old_link
243 # Sometimes old_target is specified as relative to the link name.
244 old_target = os.path.join(os.path.dirname(old_link), old_target)
245
246 # The updatealternatives used for compress doc
247 if mandir in old_target or infodir in old_target:
248 new_name = old_name + '.' + compress_mode
249 new_link = old_link + '.' + compress_mode
250 new_target = old_target + '.' + compress_mode
251 d.delVarFlag('ALTERNATIVE_LINK_NAME', old_name)
252 d.setVarFlag('ALTERNATIVE_LINK_NAME', new_name, new_link)
253 if d.getVarFlag('ALTERNATIVE_TARGET_%s' % pkg, old_name):
254 d.delVarFlag('ALTERNATIVE_TARGET_%s' % pkg, old_name)
255 d.setVarFlag('ALTERNATIVE_TARGET_%s' % pkg, new_name, new_target)
256 elif d.getVarFlag('ALTERNATIVE_TARGET', old_name):
257 d.delVarFlag('ALTERNATIVE_TARGET', old_name)
258 d.setVarFlag('ALTERNATIVE_TARGET', new_name, new_target)
259 elif d.getVar('ALTERNATIVE_TARGET_%s' % pkg):
260 d.setVar('ALTERNATIVE_TARGET_%s' % pkg, new_target)
261 elif d.getVar('ALTERNATIVE_TARGET'):
262 d.setVar('ALTERNATIVE_TARGET', new_target)
263
264 new_names.append(new_name)
265
266 if new_names:
267 d.setVar('ALTERNATIVE:%s' % pkg, ' '.join(new_names))
268}
269