1# 2# Copyright 2006-2008 OpenedHand Ltd. 3# 4# SPDX-License-Identifier: MIT 5# 6 7inherit package 8 9IMAGE_PKGTYPE ?= "deb" 10 11DPKG_BUILDCMD ??= "dpkg-deb" 12 13DPKG_ARCH ?= "${@debian_arch_map(d.getVar('TARGET_ARCH'), d.getVar('TUNE_FEATURES'))}" 14DPKG_ARCH[vardepvalue] = "${DPKG_ARCH}" 15 16PKGWRITEDIRDEB = "${WORKDIR}/deploy-debs" 17 18APTCONF_TARGET = "${WORKDIR}" 19 20APT_ARGS = "${@['', '--no-install-recommends'][d.getVar("NO_RECOMMENDATIONS") == "1"]}" 21 22def debian_arch_map(arch, tune): 23 tune_features = tune.split() 24 if arch == "allarch": 25 return "all" 26 if arch in ["i586", "i686"]: 27 return "i386" 28 if arch == "x86_64": 29 if "mx32" in tune_features: 30 return "x32" 31 return "amd64" 32 if arch.startswith("mips"): 33 endian = ["el", ""]["bigendian" in tune_features] 34 if "n64" in tune_features: 35 return "mips64" + endian 36 if "n32" in tune_features: 37 return "mipsn32" + endian 38 return "mips" + endian 39 if arch == "powerpc": 40 return arch + ["", "spe"]["spe" in tune_features] 41 if arch == "aarch64": 42 return "arm64" 43 if arch == "arm": 44 return arch + ["el", "hf"]["callconvention-hard" in tune_features] 45 return arch 46 47python do_package_deb () { 48 packages = d.getVar('PACKAGES') 49 if not packages: 50 bb.debug(1, "PACKAGES not defined, nothing to package") 51 return 52 53 tmpdir = d.getVar('TMPDIR') 54 if os.access(os.path.join(tmpdir, "stamps", "DEB_PACKAGE_INDEX_CLEAN"),os.R_OK): 55 os.unlink(os.path.join(tmpdir, "stamps", "DEB_PACKAGE_INDEX_CLEAN")) 56 57 oe.utils.multiprocess_launch(deb_write_pkg, packages.split(), d, extraargs=(d,)) 58} 59do_package_deb[vardeps] += "deb_write_pkg" 60do_package_deb[vardepsexclude] = "BB_NUMBER_THREADS" 61 62def deb_write_pkg(pkg, d): 63 import re, copy 64 import textwrap 65 import subprocess 66 import collections 67 import codecs 68 69 outdir = d.getVar('PKGWRITEDIRDEB') 70 pkgdest = d.getVar('PKGDEST') 71 72 def cleanupcontrol(root): 73 for p in ['CONTROL', 'DEBIAN']: 74 p = os.path.join(root, p) 75 if os.path.exists(p): 76 bb.utils.prunedir(p) 77 78 localdata = bb.data.createCopy(d) 79 root = "%s/%s" % (pkgdest, pkg) 80 81 lf = bb.utils.lockfile(root + ".lock") 82 try: 83 84 localdata.setVar('ROOT', '') 85 localdata.setVar('ROOT_%s' % pkg, root) 86 pkgname = localdata.getVar('PKG:%s' % pkg) 87 if not pkgname: 88 pkgname = pkg 89 localdata.setVar('PKG', pkgname) 90 91 localdata.setVar('OVERRIDES', d.getVar("OVERRIDES", False) + ":" + pkg) 92 93 basedir = os.path.join(os.path.dirname(root)) 94 95 pkgoutdir = os.path.join(outdir, localdata.getVar('PACKAGE_ARCH')) 96 bb.utils.mkdirhier(pkgoutdir) 97 98 os.chdir(root) 99 cleanupcontrol(root) 100 from glob import glob 101 g = glob('*') 102 if not g and localdata.getVar('ALLOW_EMPTY', False) != "1": 103 bb.note("Not creating empty archive for %s-%s-%s" % (pkg, localdata.getVar('PKGV'), localdata.getVar('PKGR'))) 104 return 105 106 controldir = os.path.join(root, 'DEBIAN') 107 bb.utils.mkdirhier(controldir) 108 os.chmod(controldir, 0o755) 109 110 ctrlfile = codecs.open(os.path.join(controldir, 'control'), 'w', 'utf-8') 111 112 fields = [] 113 pe = d.getVar('PKGE') 114 if pe and int(pe) > 0: 115 fields.append(["Version: %s:%s-%s\n", ['PKGE', 'PKGV', 'PKGR']]) 116 else: 117 fields.append(["Version: %s-%s\n", ['PKGV', 'PKGR']]) 118 fields.append(["Description: %s\n", ['DESCRIPTION']]) 119 fields.append(["Section: %s\n", ['SECTION']]) 120 fields.append(["Priority: %s\n", ['PRIORITY']]) 121 fields.append(["Maintainer: %s\n", ['MAINTAINER']]) 122 fields.append(["Architecture: %s\n", ['DPKG_ARCH']]) 123 fields.append(["OE: %s\n", ['PN']]) 124 fields.append(["PackageArch: %s\n", ['PACKAGE_ARCH']]) 125 if d.getVar('HOMEPAGE'): 126 fields.append(["Homepage: %s\n", ['HOMEPAGE']]) 127 128 # Package, Version, Maintainer, Description - mandatory 129 # Section, Priority, Essential, Architecture, Source, Depends, Pre-Depends, Recommends, Suggests, Conflicts, Replaces, Provides - Optional 130 131 132 def pullData(l, d): 133 l2 = [] 134 for i in l: 135 data = d.getVar(i) 136 if data is None: 137 raise KeyError(i) 138 if i == 'DPKG_ARCH' and d.getVar('PACKAGE_ARCH') == 'all': 139 data = 'all' 140 elif i == 'PACKAGE_ARCH' or i == 'DPKG_ARCH': 141 # The params in deb package control don't allow character 142 # `_', so change the arch's `_' to `-'. Such as `x86_64' 143 # -->`x86-64' 144 data = data.replace('_', '-') 145 l2.append(data) 146 return l2 147 148 ctrlfile.write("Package: %s\n" % pkgname) 149 if d.getVar('PACKAGE_ARCH') == "all": 150 ctrlfile.write("Multi-Arch: foreign\n") 151 # check for required fields 152 for (c, fs) in fields: 153 # Special behavior for description... 154 if 'DESCRIPTION' in fs: 155 summary = localdata.getVar('SUMMARY') or localdata.getVar('DESCRIPTION') or "." 156 ctrlfile.write('Description: %s\n' % summary) 157 description = localdata.getVar('DESCRIPTION') or "." 158 description = textwrap.dedent(description).strip() 159 if '\\n' in description: 160 # Manually indent 161 for t in description.split('\\n'): 162 ctrlfile.write(' %s\n' % (t.strip() or '.')) 163 else: 164 # Auto indent 165 ctrlfile.write('%s\n' % textwrap.fill(description.strip(), width=74, initial_indent=' ', subsequent_indent=' ')) 166 167 else: 168 ctrlfile.write(c % tuple(pullData(fs, localdata))) 169 170 # more fields 171 172 custom_fields_chunk = oe.packagedata.get_package_additional_metadata("deb", localdata) 173 if custom_fields_chunk: 174 ctrlfile.write(custom_fields_chunk) 175 ctrlfile.write("\n") 176 177 oe.packagedata.mapping_rename_hook(localdata) 178 179 def debian_cmp_remap(var): 180 # dpkg does not allow for '(', ')' or ':' in a dependency name 181 # Replace any instances of them with '__' 182 # 183 # In debian '>' and '<' do not mean what it appears they mean 184 # '<' = less or equal 185 # '>' = greater or equal 186 # adjust these to the '<<' and '>>' equivalents 187 # Also, "=" specifiers only work if they have the PR in, so 1.2.3 != 1.2.3-r0 188 # so to avoid issues, map this to ">= 1.2.3 << 1.2.3.0" 189 for dep in list(var.keys()): 190 if '(' in dep or '/' in dep: 191 newdep = re.sub(r'[(:)/]', '__', dep) 192 if newdep.startswith("__"): 193 newdep = "A" + newdep 194 if newdep != dep: 195 var[newdep] = var[dep] 196 del var[dep] 197 for dep in var: 198 for i, v in enumerate(var[dep]): 199 if (v or "").startswith("< "): 200 var[dep][i] = var[dep][i].replace("< ", "<< ") 201 elif (v or "").startswith("> "): 202 var[dep][i] = var[dep][i].replace("> ", ">> ") 203 elif (v or "").startswith("= ") and "-r" not in v: 204 ver = var[dep][i].replace("= ", "") 205 var[dep][i] = var[dep][i].replace("= ", ">= ") 206 var[dep].append("<< " + ver + ".0") 207 208 rdepends = bb.utils.explode_dep_versions2(localdata.getVar("RDEPENDS") or "") 209 debian_cmp_remap(rdepends) 210 for dep in list(rdepends.keys()): 211 if dep == pkg: 212 del rdepends[dep] 213 continue 214 if '*' in dep: 215 del rdepends[dep] 216 rrecommends = bb.utils.explode_dep_versions2(localdata.getVar("RRECOMMENDS") or "") 217 debian_cmp_remap(rrecommends) 218 for dep in list(rrecommends.keys()): 219 if '*' in dep: 220 del rrecommends[dep] 221 rsuggests = bb.utils.explode_dep_versions2(localdata.getVar("RSUGGESTS") or "") 222 debian_cmp_remap(rsuggests) 223 # Deliberately drop version information here, not wanted/supported by deb 224 rprovides = dict.fromkeys(bb.utils.explode_dep_versions2(localdata.getVar("RPROVIDES") or ""), []) 225 # Remove file paths if any from rprovides, debian does not support custom providers 226 for key in list(rprovides.keys()): 227 if key.startswith('/'): 228 del rprovides[key] 229 rprovides = collections.OrderedDict(sorted(rprovides.items(), key=lambda x: x[0])) 230 debian_cmp_remap(rprovides) 231 rreplaces = bb.utils.explode_dep_versions2(localdata.getVar("RREPLACES") or "") 232 debian_cmp_remap(rreplaces) 233 rconflicts = bb.utils.explode_dep_versions2(localdata.getVar("RCONFLICTS") or "") 234 debian_cmp_remap(rconflicts) 235 if rdepends: 236 ctrlfile.write("Depends: %s\n" % bb.utils.join_deps(rdepends)) 237 if rsuggests: 238 ctrlfile.write("Suggests: %s\n" % bb.utils.join_deps(rsuggests)) 239 if rrecommends: 240 ctrlfile.write("Recommends: %s\n" % bb.utils.join_deps(rrecommends)) 241 if rprovides: 242 ctrlfile.write("Provides: %s\n" % bb.utils.join_deps(rprovides)) 243 if rreplaces: 244 ctrlfile.write("Replaces: %s\n" % bb.utils.join_deps(rreplaces)) 245 if rconflicts: 246 ctrlfile.write("Conflicts: %s\n" % bb.utils.join_deps(rconflicts)) 247 ctrlfile.close() 248 249 for script in ["preinst", "postinst", "prerm", "postrm"]: 250 scriptvar = localdata.getVar('pkg_%s' % script) 251 if not scriptvar: 252 continue 253 scriptvar = scriptvar.strip() 254 scriptfile = open(os.path.join(controldir, script), 'w') 255 256 if scriptvar.startswith("#!"): 257 pos = scriptvar.find("\n") + 1 258 scriptfile.write(scriptvar[:pos]) 259 else: 260 pos = 0 261 scriptfile.write("#!/bin/sh\n") 262 263 # Prevent the prerm/postrm scripts from being run during an upgrade 264 if script in ('prerm', 'postrm'): 265 scriptfile.write('[ "$1" != "upgrade" ] || exit 0\n') 266 267 scriptfile.write(scriptvar[pos:]) 268 scriptfile.write('\n') 269 scriptfile.close() 270 os.chmod(os.path.join(controldir, script), 0o755) 271 272 conffiles_str = ' '.join(oe.package.get_conffiles(pkg, d)) 273 if conffiles_str: 274 conffiles = open(os.path.join(controldir, 'conffiles'), 'w') 275 for f in conffiles_str.split(): 276 if os.path.exists(oe.path.join(root, f)): 277 conffiles.write('%s\n' % f) 278 conffiles.close() 279 280 os.chdir(basedir) 281 subprocess.check_output("PATH=\"%s\" %s -b %s %s" % (localdata.getVar("PATH"), localdata.getVar("DPKG_BUILDCMD"), 282 root, pkgoutdir), 283 stderr=subprocess.STDOUT, 284 shell=True) 285 286 finally: 287 cleanupcontrol(root) 288 bb.utils.unlockfile(lf) 289 290# Otherwise allarch packages may change depending on override configuration 291deb_write_pkg[vardepsexclude] = "OVERRIDES" 292 293# Have to list any variables referenced as X_<pkg> that aren't in pkgdata here 294DEBEXTRAVARS = "PKGV PKGR PKGV DESCRIPTION SECTION PRIORITY MAINTAINER DPKG_ARCH PN HOMEPAGE PACKAGE_ADD_METADATA_DEB" 295do_package_write_deb[vardeps] += "${@gen_packagevar(d, 'DEBEXTRAVARS')}" 296 297SSTATETASKS += "do_package_write_deb" 298do_package_write_deb[sstate-inputdirs] = "${PKGWRITEDIRDEB}" 299do_package_write_deb[sstate-outputdirs] = "${DEPLOY_DIR_DEB}" 300 301python do_package_write_deb_setscene () { 302 tmpdir = d.getVar('TMPDIR') 303 304 if os.access(os.path.join(tmpdir, "stamps", "DEB_PACKAGE_INDEX_CLEAN"),os.R_OK): 305 os.unlink(os.path.join(tmpdir, "stamps", "DEB_PACKAGE_INDEX_CLEAN")) 306 307 sstate_setscene(d) 308} 309addtask do_package_write_deb_setscene 310 311python () { 312 if d.getVar('PACKAGES') != '': 313 deps = ' dpkg-native:do_populate_sysroot virtual/fakeroot-native:do_populate_sysroot' 314 d.appendVarFlag('do_package_write_deb', 'depends', deps) 315 d.setVarFlag('do_package_write_deb', 'fakeroot', "1") 316 317 # Needed to ensure PKG_xxx renaming of dependency packages works 318 d.setVarFlag('do_package_write_deb', 'deptask', "do_packagedata") 319 d.setVarFlag('do_package_write_deb', 'rdeptask', "do_packagedata") 320} 321 322python do_package_write_deb () { 323 bb.build.exec_func("read_subpackage_metadata", d) 324 bb.build.exec_func("do_package_deb", d) 325} 326do_package_write_deb[dirs] = "${PKGWRITEDIRDEB}" 327do_package_write_deb[cleandirs] = "${PKGWRITEDIRDEB}" 328do_package_write_deb[depends] += "${@oe.utils.build_depends_string(d.getVar('PACKAGE_WRITE_DEPS'), 'do_populate_sysroot')}" 329addtask package_write_deb after do_packagedata do_package do_deploy_source_date_epoch before do_build 330do_build[rdeptask] += "do_package_write_deb" 331 332PACKAGEINDEXDEPS += "dpkg-native:do_populate_sysroot" 333PACKAGEINDEXDEPS += "apt-native:do_populate_sysroot" 334