1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6 7inherit package 8 9IMAGE_PKGTYPE ?= "ipk" 10 11IPKGCONF_TARGET = "${WORKDIR}/opkg.conf" 12IPKGCONF_SDK = "${WORKDIR}/opkg-sdk.conf" 13IPKGCONF_SDK_TARGET = "${WORKDIR}/opkg-sdk-target.conf" 14 15PKGWRITEDIRIPK = "${WORKDIR}/deploy-ipks" 16 17# Program to be used to build opkg packages 18OPKGBUILDCMD ??= 'opkg-build -Z xz -a "${XZ_DEFAULTS}"' 19 20OPKG_ARGS += "--force_postinstall --prefer-arch-to-version" 21OPKG_ARGS += "${@['', '--no-install-recommends'][d.getVar("NO_RECOMMENDATIONS") == "1"]}" 22OPKG_ARGS += "${@['', '--add-exclude ' + ' --add-exclude '.join((d.getVar('PACKAGE_EXCLUDE') or "").split())][(d.getVar("PACKAGE_EXCLUDE") or "").strip() != ""]}" 23 24OPKGLIBDIR ??= "${localstatedir}/lib" 25 26python do_package_ipk () { 27 workdir = d.getVar('WORKDIR') 28 outdir = d.getVar('PKGWRITEDIRIPK') 29 tmpdir = d.getVar('TMPDIR') 30 pkgdest = d.getVar('PKGDEST') 31 if not workdir or not outdir or not tmpdir: 32 bb.error("Variables incorrectly set, unable to package") 33 return 34 35 packages = d.getVar('PACKAGES') 36 if not packages or packages == '': 37 bb.debug(1, "No packages; nothing to do") 38 return 39 40 # We're about to add new packages so the index needs to be checked 41 # so remove the appropriate stamp file. 42 if os.access(os.path.join(tmpdir, "stamps", "IPK_PACKAGE_INDEX_CLEAN"), os.R_OK): 43 os.unlink(os.path.join(tmpdir, "stamps", "IPK_PACKAGE_INDEX_CLEAN")) 44 45 oe.utils.multiprocess_launch(ipk_write_pkg, packages.split(), d, extraargs=(d,)) 46} 47do_package_ipk[vardeps] += "ipk_write_pkg" 48do_package_ipk[vardepsexclude] = "BB_NUMBER_THREADS" 49 50# FILE isn't included by default but we want the recipe to change if basename() changes 51IPK_RECIPE_FILE = "${@os.path.basename(d.getVar('FILE'))}" 52IPK_RECIPE_FILE[vardepvalue] = "${IPK_RECIPE_FILE}" 53 54def ipk_write_pkg(pkg, d): 55 import re, copy 56 import subprocess 57 import textwrap 58 import collections 59 import glob 60 61 def cleanupcontrol(root): 62 for p in ['CONTROL', 'DEBIAN']: 63 p = os.path.join(root, p) 64 if os.path.exists(p): 65 bb.utils.prunedir(p) 66 67 outdir = d.getVar('PKGWRITEDIRIPK') 68 pkgdest = d.getVar('PKGDEST') 69 recipesource = d.getVar('IPK_RECIPE_FILE') 70 71 localdata = bb.data.createCopy(d) 72 root = "%s/%s" % (pkgdest, pkg) 73 74 lf = bb.utils.lockfile(root + ".lock") 75 try: 76 localdata.setVar('ROOT', '') 77 localdata.setVar('ROOT_%s' % pkg, root) 78 pkgname = localdata.getVar('PKG:%s' % pkg) 79 if not pkgname: 80 pkgname = pkg 81 localdata.setVar('PKG', pkgname) 82 83 localdata.setVar('OVERRIDES', d.getVar("OVERRIDES", False) + ":" + pkg) 84 85 basedir = os.path.join(os.path.dirname(root)) 86 arch = localdata.getVar('PACKAGE_ARCH') 87 88 if localdata.getVar('IPK_HIERARCHICAL_FEED', False) == "1": 89 # Spread packages across subdirectories so each isn't too crowded 90 if pkgname.startswith('lib'): 91 pkg_prefix = 'lib' + pkgname[3] 92 else: 93 pkg_prefix = pkgname[0] 94 95 # Keep -dbg, -dev, -doc, -staticdev, -locale and -locale-* packages 96 # together. These package suffixes are taken from the definitions of 97 # PACKAGES and PACKAGES_DYNAMIC in meta/conf/bitbake.conf 98 if pkgname[-4:] in ('-dbg', '-dev', '-doc'): 99 pkg_subdir = pkgname[:-4] 100 elif pkgname.endswith('-staticdev'): 101 pkg_subdir = pkgname[:-10] 102 elif pkgname.endswith('-locale'): 103 pkg_subdir = pkgname[:-7] 104 elif '-locale-' in pkgname: 105 pkg_subdir = pkgname[:pkgname.find('-locale-')] 106 else: 107 pkg_subdir = pkgname 108 109 pkgoutdir = "%s/%s/%s/%s" % (outdir, arch, pkg_prefix, pkg_subdir) 110 else: 111 pkgoutdir = "%s/%s" % (outdir, arch) 112 113 bb.utils.mkdirhier(pkgoutdir) 114 os.chdir(root) 115 cleanupcontrol(root) 116 g = glob.glob('*') 117 if not g and localdata.getVar('ALLOW_EMPTY', False) != "1": 118 bb.note("Not creating empty archive for %s-%s-%s" % (pkg, localdata.getVar('PKGV'), localdata.getVar('PKGR'))) 119 return 120 121 controldir = os.path.join(root, 'CONTROL') 122 bb.utils.mkdirhier(controldir) 123 ctrlfile = open(os.path.join(controldir, 'control'), 'w') 124 125 fields = [] 126 pe = d.getVar('PKGE') 127 if pe and int(pe) > 0: 128 fields.append(["Version: %s:%s-%s\n", ['PKGE', 'PKGV', 'PKGR']]) 129 else: 130 fields.append(["Version: %s-%s\n", ['PKGV', 'PKGR']]) 131 fields.append(["Description: %s\n", ['DESCRIPTION']]) 132 fields.append(["Section: %s\n", ['SECTION']]) 133 fields.append(["Priority: %s\n", ['PRIORITY']]) 134 fields.append(["Maintainer: %s\n", ['MAINTAINER']]) 135 fields.append(["License: %s\n", ['LICENSE']]) 136 fields.append(["Architecture: %s\n", ['PACKAGE_ARCH']]) 137 fields.append(["OE: %s\n", ['PN']]) 138 if d.getVar('HOMEPAGE'): 139 fields.append(["Homepage: %s\n", ['HOMEPAGE']]) 140 141 def pullData(l, d): 142 l2 = [] 143 for i in l: 144 l2.append(d.getVar(i)) 145 return l2 146 147 ctrlfile.write("Package: %s\n" % pkgname) 148 # check for required fields 149 for (c, fs) in fields: 150 for f in fs: 151 if localdata.getVar(f, False) is None: 152 raise KeyError(f) 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: multiline description includes a leading space 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, width=74, initial_indent=' ', subsequent_indent=' ')) 166 else: 167 ctrlfile.write(c % tuple(pullData(fs, localdata))) 168 169 custom_fields_chunk = oe.packagedata.get_package_additional_metadata("ipk", localdata) 170 if custom_fields_chunk is not None: 171 ctrlfile.write(custom_fields_chunk) 172 ctrlfile.write("\n") 173 174 oe.packagedata.mapping_rename_hook(localdata) 175 176 def debian_cmp_remap(var): 177 # In debian '>' and '<' do not mean what it appears they mean 178 # '<' = less or equal 179 # '>' = greater or equal 180 # adjust these to the '<<' and '>>' equivalents 181 # Also, "=" specifiers only work if they have the PR in, so 1.2.3 != 1.2.3-r0 182 # so to avoid issues, map this to ">= 1.2.3 << 1.2.3.0" 183 for dep in var: 184 for i, v in enumerate(var[dep]): 185 if (v or "").startswith("< "): 186 var[dep][i] = var[dep][i].replace("< ", "<< ") 187 elif (v or "").startswith("> "): 188 var[dep][i] = var[dep][i].replace("> ", ">> ") 189 elif (v or "").startswith("= ") and "-r" not in v: 190 ver = var[dep][i].replace("= ", "") 191 var[dep][i] = var[dep][i].replace("= ", ">= ") 192 var[dep].append("<< " + ver + ".0") 193 194 rdepends = bb.utils.explode_dep_versions2(localdata.getVar("RDEPENDS") or "") 195 debian_cmp_remap(rdepends) 196 rrecommends = bb.utils.explode_dep_versions2(localdata.getVar("RRECOMMENDS") or "") 197 debian_cmp_remap(rrecommends) 198 rsuggests = bb.utils.explode_dep_versions2(localdata.getVar("RSUGGESTS") or "") 199 debian_cmp_remap(rsuggests) 200 # Deliberately drop version information here, not wanted/supported by ipk 201 rprovides = dict.fromkeys(bb.utils.explode_dep_versions2(localdata.getVar("RPROVIDES") or ""), []) 202 rprovides = collections.OrderedDict(sorted(rprovides.items(), key=lambda x: x[0])) 203 debian_cmp_remap(rprovides) 204 rreplaces = bb.utils.explode_dep_versions2(localdata.getVar("RREPLACES") or "") 205 debian_cmp_remap(rreplaces) 206 rconflicts = bb.utils.explode_dep_versions2(localdata.getVar("RCONFLICTS") or "") 207 debian_cmp_remap(rconflicts) 208 209 if rdepends: 210 ctrlfile.write("Depends: %s\n" % bb.utils.join_deps(rdepends)) 211 if rsuggests: 212 ctrlfile.write("Suggests: %s\n" % bb.utils.join_deps(rsuggests)) 213 if rrecommends: 214 ctrlfile.write("Recommends: %s\n" % bb.utils.join_deps(rrecommends)) 215 if rprovides: 216 ctrlfile.write("Provides: %s\n" % bb.utils.join_deps(rprovides)) 217 if rreplaces: 218 ctrlfile.write("Replaces: %s\n" % bb.utils.join_deps(rreplaces)) 219 if rconflicts: 220 ctrlfile.write("Conflicts: %s\n" % bb.utils.join_deps(rconflicts)) 221 ctrlfile.write("Source: %s\n" % recipesource) 222 ctrlfile.close() 223 224 for script in ["preinst", "postinst", "prerm", "postrm"]: 225 scriptvar = localdata.getVar('pkg_%s' % script) 226 if not scriptvar: 227 continue 228 scriptfile = open(os.path.join(controldir, script), 'w') 229 scriptfile.write(scriptvar) 230 scriptfile.close() 231 os.chmod(os.path.join(controldir, script), 0o755) 232 233 conffiles_str = ' '.join(oe.package.get_conffiles(pkg, d)) 234 if conffiles_str: 235 conffiles = open(os.path.join(controldir, 'conffiles'), 'w') 236 for f in conffiles_str.split(): 237 if os.path.exists(oe.path.join(root, f)): 238 conffiles.write('%s\n' % f) 239 conffiles.close() 240 241 os.chdir(basedir) 242 subprocess.check_output("PATH=\"%s\" %s %s %s" % (localdata.getVar("PATH"), 243 d.getVar("OPKGBUILDCMD"), pkg, pkgoutdir), 244 stderr=subprocess.STDOUT, 245 shell=True) 246 247 if d.getVar('IPK_SIGN_PACKAGES') == '1': 248 ipkver = "%s-%s" % (localdata.getVar('PKGV'), localdata.getVar('PKGR')) 249 ipk_to_sign = "%s/%s_%s_%s.ipk" % (pkgoutdir, pkgname, ipkver, localdata.getVar('PACKAGE_ARCH')) 250 sign_ipk(d, ipk_to_sign) 251 252 finally: 253 cleanupcontrol(root) 254 bb.utils.unlockfile(lf) 255 256# Have to list any variables referenced as X_<pkg> that aren't in pkgdata here 257IPKEXTRAVARS = "PRIORITY MAINTAINER PACKAGE_ARCH HOMEPAGE PACKAGE_ADD_METADATA_IPK" 258ipk_write_pkg[vardeps] += "${@gen_packagevar(d, 'IPKEXTRAVARS')}" 259 260# Otherwise allarch packages may change depending on override configuration 261ipk_write_pkg[vardepsexclude] = "OVERRIDES" 262 263 264SSTATETASKS += "do_package_write_ipk" 265do_package_write_ipk[sstate-inputdirs] = "${PKGWRITEDIRIPK}" 266do_package_write_ipk[sstate-outputdirs] = "${DEPLOY_DIR_IPK}" 267 268python do_package_write_ipk_setscene () { 269 tmpdir = d.getVar('TMPDIR') 270 271 if os.access(os.path.join(tmpdir, "stamps", "IPK_PACKAGE_INDEX_CLEAN"), os.R_OK): 272 os.unlink(os.path.join(tmpdir, "stamps", "IPK_PACKAGE_INDEX_CLEAN")) 273 274 sstate_setscene(d) 275} 276addtask do_package_write_ipk_setscene 277 278python () { 279 if d.getVar('PACKAGES') != '': 280 deps = ' opkg-utils-native:do_populate_sysroot virtual/fakeroot-native:do_populate_sysroot xz-native:do_populate_sysroot' 281 d.appendVarFlag('do_package_write_ipk', 'depends', deps) 282 d.setVarFlag('do_package_write_ipk', 'fakeroot', "1") 283 284 # Needed to ensure PKG_xxx renaming of dependency packages works 285 d.setVarFlag('do_package_write_ipk', 'deptask', "do_packagedata") 286 d.setVarFlag('do_package_write_ipk', 'rdeptask', "do_packagedata") 287} 288 289python do_package_write_ipk () { 290 bb.build.exec_func("read_subpackage_metadata", d) 291 bb.build.exec_func("do_package_ipk", d) 292} 293do_package_write_ipk[dirs] = "${PKGWRITEDIRIPK}" 294do_package_write_ipk[cleandirs] = "${PKGWRITEDIRIPK}" 295do_package_write_ipk[depends] += "${@oe.utils.build_depends_string(d.getVar('PACKAGE_WRITE_DEPS'), 'do_populate_sysroot')}" 296addtask package_write_ipk after do_packagedata do_package do_deploy_source_date_epoch before do_build 297do_build[rdeptask] += "do_package_write_ipk" 298 299PACKAGEINDEXDEPS += "opkg-utils-native:do_populate_sysroot" 300PACKAGEINDEXDEPS += "opkg-native:do_populate_sysroot" 301