1635e0e46SAndrew Geissler#
292b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors
392b42cb3SPatrick Williams#
4635e0e46SAndrew Geissler# SPDX-License-Identifier: GPL-2.0-only
5635e0e46SAndrew Geissler#
6635e0e46SAndrew Geissler
7635e0e46SAndrew Geisslerimport re
8635e0e46SAndrew Geisslerimport shutil
9635e0e46SAndrew Geisslerimport subprocess
10635e0e46SAndrew Geisslerfrom oe.package_manager import *
11635e0e46SAndrew Geissler
12635e0e46SAndrew Geisslerclass OpkgIndexer(Indexer):
13635e0e46SAndrew Geissler    def write_index(self):
14635e0e46SAndrew Geissler        arch_vars = ["ALL_MULTILIB_PACKAGE_ARCHS",
15635e0e46SAndrew Geissler                     "SDK_PACKAGE_ARCHS",
16635e0e46SAndrew Geissler                     ]
17635e0e46SAndrew Geissler
18635e0e46SAndrew Geissler        opkg_index_cmd = bb.utils.which(os.getenv('PATH'), "opkg-make-index")
19*73bd93f1SPatrick Williams        opkg_index_cmd_extra_params = self.d.getVar('OPKG_MAKE_INDEX_EXTRA_PARAMS') or ""
20635e0e46SAndrew Geissler        if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
21635e0e46SAndrew Geissler            signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND'))
22635e0e46SAndrew Geissler        else:
23635e0e46SAndrew Geissler            signer = None
24635e0e46SAndrew Geissler
25635e0e46SAndrew Geissler        if not os.path.exists(os.path.join(self.deploy_dir, "Packages")):
26635e0e46SAndrew Geissler            open(os.path.join(self.deploy_dir, "Packages"), "w").close()
27635e0e46SAndrew Geissler
28635e0e46SAndrew Geissler        index_cmds = set()
29635e0e46SAndrew Geissler        index_sign_files = set()
30635e0e46SAndrew Geissler        for arch_var in arch_vars:
31635e0e46SAndrew Geissler            archs = self.d.getVar(arch_var)
32635e0e46SAndrew Geissler            if archs is None:
33635e0e46SAndrew Geissler                continue
34635e0e46SAndrew Geissler
35635e0e46SAndrew Geissler            for arch in archs.split():
36635e0e46SAndrew Geissler                pkgs_dir = os.path.join(self.deploy_dir, arch)
37635e0e46SAndrew Geissler                pkgs_file = os.path.join(pkgs_dir, "Packages")
38635e0e46SAndrew Geissler
39635e0e46SAndrew Geissler                if not os.path.isdir(pkgs_dir):
40635e0e46SAndrew Geissler                    continue
41635e0e46SAndrew Geissler
42635e0e46SAndrew Geissler                if not os.path.exists(pkgs_file):
43635e0e46SAndrew Geissler                    open(pkgs_file, "w").close()
44635e0e46SAndrew Geissler
45*73bd93f1SPatrick Williams                index_cmds.add('%s --checksum md5 --checksum sha256 -r %s -p %s -m %s %s' %
46*73bd93f1SPatrick Williams                                  (opkg_index_cmd, pkgs_file, pkgs_file, pkgs_dir, opkg_index_cmd_extra_params))
47635e0e46SAndrew Geissler
48635e0e46SAndrew Geissler                index_sign_files.add(pkgs_file)
49635e0e46SAndrew Geissler
50635e0e46SAndrew Geissler        if len(index_cmds) == 0:
51635e0e46SAndrew Geissler            bb.note("There are no packages in %s!" % self.deploy_dir)
52635e0e46SAndrew Geissler            return
53635e0e46SAndrew Geissler
54635e0e46SAndrew Geissler        oe.utils.multiprocess_launch(create_index, index_cmds, self.d)
55635e0e46SAndrew Geissler
56635e0e46SAndrew Geissler        if signer:
57635e0e46SAndrew Geissler            feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE')
58635e0e46SAndrew Geissler            is_ascii_sig = (feed_sig_type.upper() != "BIN")
59635e0e46SAndrew Geissler            for f in index_sign_files:
60635e0e46SAndrew Geissler                signer.detach_sign(f,
61635e0e46SAndrew Geissler                                   self.d.getVar('PACKAGE_FEED_GPG_NAME'),
62635e0e46SAndrew Geissler                                   self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE'),
63635e0e46SAndrew Geissler                                   armor=is_ascii_sig)
64635e0e46SAndrew Geissler
656ce62a20SAndrew Geisslerclass PMPkgsList(PkgsList):
666ce62a20SAndrew Geissler    def __init__(self, d, rootfs_dir):
676ce62a20SAndrew Geissler        super(PMPkgsList, self).__init__(d, rootfs_dir)
686ce62a20SAndrew Geissler        config_file = d.getVar("IPKGCONF_TARGET")
69635e0e46SAndrew Geissler
70635e0e46SAndrew Geissler        self.opkg_cmd = bb.utils.which(os.getenv('PATH'), "opkg")
71635e0e46SAndrew Geissler        self.opkg_args = "-f %s -o %s " % (config_file, rootfs_dir)
72635e0e46SAndrew Geissler        self.opkg_args += self.d.getVar("OPKG_ARGS")
73635e0e46SAndrew Geissler
74635e0e46SAndrew Geissler    def list_pkgs(self, format=None):
75635e0e46SAndrew Geissler        cmd = "%s %s status" % (self.opkg_cmd, self.opkg_args)
76635e0e46SAndrew Geissler
77635e0e46SAndrew Geissler        # opkg returns success even when it printed some
78635e0e46SAndrew Geissler        # "Collected errors:" report to stderr. Mixing stderr into
79635e0e46SAndrew Geissler        # stdout then leads to random failures later on when
80635e0e46SAndrew Geissler        # parsing the output. To avoid this we need to collect both
81635e0e46SAndrew Geissler        # output streams separately and check for empty stderr.
82635e0e46SAndrew Geissler        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
83635e0e46SAndrew Geissler        cmd_output, cmd_stderr = p.communicate()
84635e0e46SAndrew Geissler        cmd_output = cmd_output.decode("utf-8")
85635e0e46SAndrew Geissler        cmd_stderr = cmd_stderr.decode("utf-8")
86635e0e46SAndrew Geissler        if p.returncode or cmd_stderr:
87635e0e46SAndrew Geissler            bb.fatal("Cannot get the installed packages list. Command '%s' "
88635e0e46SAndrew Geissler                     "returned %d and stderr:\n%s" % (cmd, p.returncode, cmd_stderr))
89635e0e46SAndrew Geissler
90635e0e46SAndrew Geissler        return opkg_query(cmd_output)
91635e0e46SAndrew Geissler
92635e0e46SAndrew Geissler
93635e0e46SAndrew Geissler
94635e0e46SAndrew Geisslerclass OpkgDpkgPM(PackageManager):
95635e0e46SAndrew Geissler    def __init__(self, d, target_rootfs):
96635e0e46SAndrew Geissler        """
97635e0e46SAndrew Geissler        This is an abstract class. Do not instantiate this directly.
98635e0e46SAndrew Geissler        """
99635e0e46SAndrew Geissler        super(OpkgDpkgPM, self).__init__(d, target_rootfs)
100635e0e46SAndrew Geissler
101635e0e46SAndrew Geissler    def package_info(self, pkg, cmd):
102635e0e46SAndrew Geissler        """
103635e0e46SAndrew Geissler        Returns a dictionary with the package info.
104635e0e46SAndrew Geissler
105635e0e46SAndrew Geissler        This method extracts the common parts for Opkg and Dpkg
106635e0e46SAndrew Geissler        """
107635e0e46SAndrew Geissler
108db4c27eeSPatrick Williams        proc = subprocess.run(cmd, capture_output=True, encoding="utf-8", shell=True)
109db4c27eeSPatrick Williams        if proc.returncode:
110635e0e46SAndrew Geissler            bb.fatal("Unable to list available packages. Command '%s' "
111db4c27eeSPatrick Williams                     "returned %d:\n%s" % (cmd, proc.returncode, proc.stderr))
112db4c27eeSPatrick Williams        elif proc.stderr:
113db4c27eeSPatrick Williams            bb.note("Command '%s' returned stderr: %s" % (cmd, proc.stderr))
114db4c27eeSPatrick Williams
115db4c27eeSPatrick Williams        return opkg_query(proc.stdout)
116635e0e46SAndrew Geissler
117635e0e46SAndrew Geissler    def extract(self, pkg, pkg_info):
118635e0e46SAndrew Geissler        """
119635e0e46SAndrew Geissler        Returns the path to a tmpdir where resides the contents of a package.
120635e0e46SAndrew Geissler
121635e0e46SAndrew Geissler        Deleting the tmpdir is responsability of the caller.
122635e0e46SAndrew Geissler
123635e0e46SAndrew Geissler        This method extracts the common parts for Opkg and Dpkg
124635e0e46SAndrew Geissler        """
125635e0e46SAndrew Geissler
126635e0e46SAndrew Geissler        ar_cmd = bb.utils.which(os.getenv("PATH"), "ar")
127635e0e46SAndrew Geissler        tar_cmd = bb.utils.which(os.getenv("PATH"), "tar")
128635e0e46SAndrew Geissler        pkg_path = pkg_info[pkg]["filepath"]
129635e0e46SAndrew Geissler
130635e0e46SAndrew Geissler        if not os.path.isfile(pkg_path):
131635e0e46SAndrew Geissler            bb.fatal("Unable to extract package for '%s'."
132635e0e46SAndrew Geissler                     "File %s doesn't exists" % (pkg, pkg_path))
133635e0e46SAndrew Geissler
134635e0e46SAndrew Geissler        tmp_dir = tempfile.mkdtemp()
135635e0e46SAndrew Geissler        current_dir = os.getcwd()
136635e0e46SAndrew Geissler        os.chdir(tmp_dir)
137169d7bccSPatrick Williams        data_tar = 'data.tar.zst'
138635e0e46SAndrew Geissler
139635e0e46SAndrew Geissler        try:
140635e0e46SAndrew Geissler            cmd = [ar_cmd, 'x', pkg_path]
141635e0e46SAndrew Geissler            output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
142635e0e46SAndrew Geissler            cmd = [tar_cmd, 'xf', data_tar]
143635e0e46SAndrew Geissler            output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
144635e0e46SAndrew Geissler        except subprocess.CalledProcessError as e:
145635e0e46SAndrew Geissler            bb.utils.remove(tmp_dir, recurse=True)
146635e0e46SAndrew Geissler            bb.fatal("Unable to extract %s package. Command '%s' "
147635e0e46SAndrew Geissler                     "returned %d:\n%s" % (pkg_path, ' '.join(cmd), e.returncode, e.output.decode("utf-8")))
148635e0e46SAndrew Geissler        except OSError as e:
149635e0e46SAndrew Geissler            bb.utils.remove(tmp_dir, recurse=True)
150635e0e46SAndrew Geissler            bb.fatal("Unable to extract %s package. Command '%s' "
151635e0e46SAndrew Geissler                     "returned %d:\n%s at %s" % (pkg_path, ' '.join(cmd), e.errno, e.strerror, e.filename))
152635e0e46SAndrew Geissler
153635e0e46SAndrew Geissler        bb.note("Extracted %s to %s" % (pkg_path, tmp_dir))
154635e0e46SAndrew Geissler        bb.utils.remove(os.path.join(tmp_dir, "debian-binary"))
155635e0e46SAndrew Geissler        bb.utils.remove(os.path.join(tmp_dir, "control.tar.gz"))
156635e0e46SAndrew Geissler        os.chdir(current_dir)
157635e0e46SAndrew Geissler
158635e0e46SAndrew Geissler        return tmp_dir
159635e0e46SAndrew Geissler
160635e0e46SAndrew Geissler    def _handle_intercept_failure(self, registered_pkgs):
161635e0e46SAndrew Geissler        self.mark_packages("unpacked", registered_pkgs.split())
162635e0e46SAndrew Geissler
163635e0e46SAndrew Geisslerclass OpkgPM(OpkgDpkgPM):
164635e0e46SAndrew Geissler    def __init__(self, d, target_rootfs, config_file, archs, task_name='target', ipk_repo_workdir="oe-rootfs-repo", filterbydependencies=True, prepare_index=True):
165635e0e46SAndrew Geissler        super(OpkgPM, self).__init__(d, target_rootfs)
166635e0e46SAndrew Geissler
167635e0e46SAndrew Geissler        self.config_file = config_file
168635e0e46SAndrew Geissler        self.pkg_archs = archs
169635e0e46SAndrew Geissler        self.task_name = task_name
170635e0e46SAndrew Geissler
171635e0e46SAndrew Geissler        self.deploy_dir = oe.path.join(self.d.getVar('WORKDIR'), ipk_repo_workdir)
172635e0e46SAndrew Geissler        self.deploy_lock_file = os.path.join(self.deploy_dir, "deploy.lock")
173635e0e46SAndrew Geissler        self.opkg_cmd = bb.utils.which(os.getenv('PATH'), "opkg")
174635e0e46SAndrew Geissler        self.opkg_args = "--volatile-cache -f %s -t %s -o %s " % (self.config_file, self.d.expand('${T}/ipktemp/') ,target_rootfs)
175635e0e46SAndrew Geissler        self.opkg_args += self.d.getVar("OPKG_ARGS")
176635e0e46SAndrew Geissler
177635e0e46SAndrew Geissler        if prepare_index:
178635e0e46SAndrew Geissler            create_packages_dir(self.d, self.deploy_dir, d.getVar("DEPLOY_DIR_IPK"), "package_write_ipk", filterbydependencies)
179635e0e46SAndrew Geissler
18009209eecSAndrew Geissler        self.opkg_dir = oe.path.join(target_rootfs, self.d.getVar('OPKGLIBDIR'), "opkg")
181635e0e46SAndrew Geissler        bb.utils.mkdirhier(self.opkg_dir)
182635e0e46SAndrew Geissler
183635e0e46SAndrew Geissler        self.saved_opkg_dir = self.d.expand('${T}/saved/%s' % self.task_name)
184635e0e46SAndrew Geissler        if not os.path.exists(self.d.expand('${T}/saved')):
185635e0e46SAndrew Geissler            bb.utils.mkdirhier(self.d.expand('${T}/saved'))
186635e0e46SAndrew Geissler
187635e0e46SAndrew Geissler        self.from_feeds = (self.d.getVar('BUILD_IMAGES_FROM_FEEDS') or "") == "1"
188635e0e46SAndrew Geissler        if self.from_feeds:
189635e0e46SAndrew Geissler            self._create_custom_config()
190635e0e46SAndrew Geissler        else:
191635e0e46SAndrew Geissler            self._create_config()
192635e0e46SAndrew Geissler
193635e0e46SAndrew Geissler        self.indexer = OpkgIndexer(self.d, self.deploy_dir)
194635e0e46SAndrew Geissler
195635e0e46SAndrew Geissler    def mark_packages(self, status_tag, packages=None):
196635e0e46SAndrew Geissler        """
197635e0e46SAndrew Geissler        This function will change a package's status in /var/lib/opkg/status file.
198635e0e46SAndrew Geissler        If 'packages' is None then the new_status will be applied to all
199635e0e46SAndrew Geissler        packages
200635e0e46SAndrew Geissler        """
201635e0e46SAndrew Geissler        status_file = os.path.join(self.opkg_dir, "status")
202635e0e46SAndrew Geissler
203635e0e46SAndrew Geissler        with open(status_file, "r") as sf:
204635e0e46SAndrew Geissler            with open(status_file + ".tmp", "w+") as tmp_sf:
205635e0e46SAndrew Geissler                if packages is None:
206635e0e46SAndrew Geissler                    tmp_sf.write(re.sub(r"Package: (.*?)\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)",
207635e0e46SAndrew Geissler                                        r"Package: \1\n\2Status: \3%s" % status_tag,
208635e0e46SAndrew Geissler                                        sf.read()))
209635e0e46SAndrew Geissler                else:
210635e0e46SAndrew Geissler                    if type(packages).__name__ != "list":
211635e0e46SAndrew Geissler                        raise TypeError("'packages' should be a list object")
212635e0e46SAndrew Geissler
213635e0e46SAndrew Geissler                    status = sf.read()
214635e0e46SAndrew Geissler                    for pkg in packages:
215635e0e46SAndrew Geissler                        status = re.sub(r"Package: %s\n((?:[^\n]+\n)*?)Status: (.*)(?:unpacked|installed)" % pkg,
216635e0e46SAndrew Geissler                                        r"Package: %s\n\1Status: \2%s" % (pkg, status_tag),
217635e0e46SAndrew Geissler                                        status)
218635e0e46SAndrew Geissler
219635e0e46SAndrew Geissler                    tmp_sf.write(status)
220635e0e46SAndrew Geissler
221c926e17cSAndrew Geissler        bb.utils.rename(status_file + ".tmp", status_file)
222635e0e46SAndrew Geissler
223635e0e46SAndrew Geissler    def _create_custom_config(self):
224635e0e46SAndrew Geissler        bb.note("Building from feeds activated!")
225635e0e46SAndrew Geissler
226635e0e46SAndrew Geissler        with open(self.config_file, "w+") as config_file:
227635e0e46SAndrew Geissler            priority = 1
228635e0e46SAndrew Geissler            for arch in self.pkg_archs.split():
229635e0e46SAndrew Geissler                config_file.write("arch %s %d\n" % (arch, priority))
230635e0e46SAndrew Geissler                priority += 5
231635e0e46SAndrew Geissler
232635e0e46SAndrew Geissler            for line in (self.d.getVar('IPK_FEED_URIS') or "").split():
233635e0e46SAndrew Geissler                feed_match = re.match(r"^[ \t]*(.*)##([^ \t]*)[ \t]*$", line)
234635e0e46SAndrew Geissler
235635e0e46SAndrew Geissler                if feed_match is not None:
236635e0e46SAndrew Geissler                    feed_name = feed_match.group(1)
237635e0e46SAndrew Geissler                    feed_uri = feed_match.group(2)
238635e0e46SAndrew Geissler
239635e0e46SAndrew Geissler                    bb.note("Add %s feed with URL %s" % (feed_name, feed_uri))
240635e0e46SAndrew Geissler
241635e0e46SAndrew Geissler                    config_file.write("src/gz %s %s\n" % (feed_name, feed_uri))
242635e0e46SAndrew Geissler
243635e0e46SAndrew Geissler            """
244635e0e46SAndrew Geissler            Allow to use package deploy directory contents as quick devel-testing
245635e0e46SAndrew Geissler            feed. This creates individual feed configs for each arch subdir of those
246635e0e46SAndrew Geissler            specified as compatible for the current machine.
247635e0e46SAndrew Geissler            NOTE: Development-helper feature, NOT a full-fledged feed.
248635e0e46SAndrew Geissler            """
249635e0e46SAndrew Geissler            if (self.d.getVar('FEED_DEPLOYDIR_BASE_URI') or "") != "":
250635e0e46SAndrew Geissler                for arch in self.pkg_archs.split():
251028142bdSAndrew Geissler                    cfg_file_name = oe.path.join(self.target_rootfs,
252635e0e46SAndrew Geissler                                                 self.d.getVar("sysconfdir"),
253635e0e46SAndrew Geissler                                                 "opkg",
254635e0e46SAndrew Geissler                                                 "local-%s-feed.conf" % arch)
255635e0e46SAndrew Geissler
256635e0e46SAndrew Geissler                    with open(cfg_file_name, "w+") as cfg_file:
257635e0e46SAndrew Geissler                        cfg_file.write("src/gz local-%s %s/%s" %
258635e0e46SAndrew Geissler                                       (arch,
259635e0e46SAndrew Geissler                                        self.d.getVar('FEED_DEPLOYDIR_BASE_URI'),
260635e0e46SAndrew Geissler                                        arch))
261635e0e46SAndrew Geissler
262635e0e46SAndrew Geissler                        if self.d.getVar('OPKGLIBDIR') != '/var/lib':
263635e0e46SAndrew Geissler                            # There is no command line option for this anymore, we need to add
264635e0e46SAndrew Geissler                            # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
265635e0e46SAndrew Geissler                            # the default value of "/var/lib" as defined in opkg:
266635e0e46SAndrew Geissler                            # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_LISTS_DIR     VARDIR "/lib/opkg/lists"
267635e0e46SAndrew Geissler                            # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR      VARDIR "/lib/opkg/info"
268635e0e46SAndrew Geissler                            # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE   VARDIR "/lib/opkg/status"
269635e0e46SAndrew Geissler                            cfg_file.write("option info_dir     %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'info'))
270635e0e46SAndrew Geissler                            cfg_file.write("option lists_dir    %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'lists'))
271635e0e46SAndrew Geissler                            cfg_file.write("option status_file  %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'status'))
272635e0e46SAndrew Geissler
273635e0e46SAndrew Geissler
274635e0e46SAndrew Geissler    def _create_config(self):
275635e0e46SAndrew Geissler        with open(self.config_file, "w+") as config_file:
276635e0e46SAndrew Geissler            priority = 1
277635e0e46SAndrew Geissler            for arch in self.pkg_archs.split():
278635e0e46SAndrew Geissler                config_file.write("arch %s %d\n" % (arch, priority))
279635e0e46SAndrew Geissler                priority += 5
280635e0e46SAndrew Geissler
281635e0e46SAndrew Geissler            config_file.write("src oe file:%s\n" % self.deploy_dir)
282635e0e46SAndrew Geissler
283635e0e46SAndrew Geissler            for arch in self.pkg_archs.split():
284635e0e46SAndrew Geissler                pkgs_dir = os.path.join(self.deploy_dir, arch)
285635e0e46SAndrew Geissler                if os.path.isdir(pkgs_dir):
286635e0e46SAndrew Geissler                    config_file.write("src oe-%s file:%s\n" %
287635e0e46SAndrew Geissler                                      (arch, pkgs_dir))
288635e0e46SAndrew Geissler
289635e0e46SAndrew Geissler            if self.d.getVar('OPKGLIBDIR') != '/var/lib':
290635e0e46SAndrew Geissler                # There is no command line option for this anymore, we need to add
291635e0e46SAndrew Geissler                # info_dir and status_file to config file, if OPKGLIBDIR doesn't have
292635e0e46SAndrew Geissler                # the default value of "/var/lib" as defined in opkg:
293635e0e46SAndrew Geissler                # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_LISTS_DIR     VARDIR "/lib/opkg/lists"
294635e0e46SAndrew Geissler                # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_INFO_DIR      VARDIR "/lib/opkg/info"
295635e0e46SAndrew Geissler                # libopkg/opkg_conf.h:#define OPKG_CONF_DEFAULT_STATUS_FILE   VARDIR "/lib/opkg/status"
296635e0e46SAndrew Geissler                config_file.write("option info_dir     %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'info'))
297635e0e46SAndrew Geissler                config_file.write("option lists_dir    %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'lists'))
298635e0e46SAndrew Geissler                config_file.write("option status_file  %s\n" % os.path.join(self.d.getVar('OPKGLIBDIR'), 'opkg', 'status'))
299635e0e46SAndrew Geissler
300635e0e46SAndrew Geissler    def insert_feeds_uris(self, feed_uris, feed_base_paths, feed_archs):
301635e0e46SAndrew Geissler        if feed_uris == "":
302635e0e46SAndrew Geissler            return
303635e0e46SAndrew Geissler
304635e0e46SAndrew Geissler        rootfs_config = os.path.join('%s/etc/opkg/base-feeds.conf'
305635e0e46SAndrew Geissler                                  % self.target_rootfs)
306635e0e46SAndrew Geissler
307635e0e46SAndrew Geissler        os.makedirs('%s/etc/opkg' % self.target_rootfs, exist_ok=True)
308635e0e46SAndrew Geissler
309635e0e46SAndrew Geissler        feed_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split())
310635e0e46SAndrew Geissler        archs = self.pkg_archs.split() if feed_archs is None else feed_archs.split()
311635e0e46SAndrew Geissler
312635e0e46SAndrew Geissler        with open(rootfs_config, "w+") as config_file:
313635e0e46SAndrew Geissler            uri_iterator = 0
314635e0e46SAndrew Geissler            for uri in feed_uris:
315635e0e46SAndrew Geissler                if archs:
316635e0e46SAndrew Geissler                    for arch in archs:
317635e0e46SAndrew Geissler                        if (feed_archs is None) and (not os.path.exists(oe.path.join(self.deploy_dir, arch))):
318635e0e46SAndrew Geissler                            continue
319635e0e46SAndrew Geissler                        bb.note('Adding opkg feed url-%s-%d (%s)' %
320635e0e46SAndrew Geissler                            (arch, uri_iterator, uri))
321635e0e46SAndrew Geissler                        config_file.write("src/gz uri-%s-%d %s/%s\n" %
322635e0e46SAndrew Geissler                                          (arch, uri_iterator, uri, arch))
323635e0e46SAndrew Geissler                else:
324635e0e46SAndrew Geissler                    bb.note('Adding opkg feed url-%d (%s)' %
325635e0e46SAndrew Geissler                        (uri_iterator, uri))
326635e0e46SAndrew Geissler                    config_file.write("src/gz uri-%d %s\n" %
327635e0e46SAndrew Geissler                                      (uri_iterator, uri))
328635e0e46SAndrew Geissler
329635e0e46SAndrew Geissler                uri_iterator += 1
330635e0e46SAndrew Geissler
331635e0e46SAndrew Geissler    def update(self):
332635e0e46SAndrew Geissler        self.deploy_dir_lock()
333635e0e46SAndrew Geissler
334635e0e46SAndrew Geissler        cmd = "%s %s update" % (self.opkg_cmd, self.opkg_args)
335635e0e46SAndrew Geissler
336635e0e46SAndrew Geissler        try:
337635e0e46SAndrew Geissler            subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
338635e0e46SAndrew Geissler        except subprocess.CalledProcessError as e:
339635e0e46SAndrew Geissler            self.deploy_dir_unlock()
340635e0e46SAndrew Geissler            bb.fatal("Unable to update the package index files. Command '%s' "
341635e0e46SAndrew Geissler                     "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
342635e0e46SAndrew Geissler
343635e0e46SAndrew Geissler        self.deploy_dir_unlock()
344635e0e46SAndrew Geissler
345615f2f11SAndrew Geissler    def install(self, pkgs, attempt_only=False, hard_depends_only=False):
346635e0e46SAndrew Geissler        if not pkgs:
347635e0e46SAndrew Geissler            return
348635e0e46SAndrew Geissler
349635e0e46SAndrew Geissler        cmd = "%s %s" % (self.opkg_cmd, self.opkg_args)
350635e0e46SAndrew Geissler        for exclude in (self.d.getVar("PACKAGE_EXCLUDE") or "").split():
351635e0e46SAndrew Geissler            cmd += " --add-exclude %s" % exclude
352635e0e46SAndrew Geissler        for bad_recommendation in (self.d.getVar("BAD_RECOMMENDATIONS") or "").split():
353635e0e46SAndrew Geissler            cmd += " --add-ignore-recommends %s" % bad_recommendation
354615f2f11SAndrew Geissler        if hard_depends_only:
355615f2f11SAndrew Geissler            cmd += " --no-install-recommends"
356635e0e46SAndrew Geissler        cmd += " install "
357635e0e46SAndrew Geissler        cmd += " ".join(pkgs)
358635e0e46SAndrew Geissler
359635e0e46SAndrew Geissler        os.environ['D'] = self.target_rootfs
360635e0e46SAndrew Geissler        os.environ['OFFLINE_ROOT'] = self.target_rootfs
361635e0e46SAndrew Geissler        os.environ['IPKG_OFFLINE_ROOT'] = self.target_rootfs
362635e0e46SAndrew Geissler        os.environ['OPKG_OFFLINE_ROOT'] = self.target_rootfs
363635e0e46SAndrew Geissler        os.environ['INTERCEPT_DIR'] = self.intercepts_dir
364635e0e46SAndrew Geissler        os.environ['NATIVE_ROOT'] = self.d.getVar('STAGING_DIR_NATIVE')
365635e0e46SAndrew Geissler
366635e0e46SAndrew Geissler        try:
367635e0e46SAndrew Geissler            bb.note("Installing the following packages: %s" % ' '.join(pkgs))
368635e0e46SAndrew Geissler            bb.note(cmd)
369635e0e46SAndrew Geissler            output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
370635e0e46SAndrew Geissler            bb.note(output)
371635e0e46SAndrew Geissler            failed_pkgs = []
372635e0e46SAndrew Geissler            for line in output.split('\n'):
373635e0e46SAndrew Geissler                if line.endswith("configuration required on target."):
374635e0e46SAndrew Geissler                    bb.warn(line)
375635e0e46SAndrew Geissler                    failed_pkgs.append(line.split(".")[0])
376635e0e46SAndrew Geissler            if failed_pkgs:
377635e0e46SAndrew Geissler                failed_postinsts_abort(failed_pkgs, self.d.expand("${T}/log.do_${BB_CURRENTTASK}"))
378635e0e46SAndrew Geissler        except subprocess.CalledProcessError as e:
379635e0e46SAndrew Geissler            (bb.fatal, bb.warn)[attempt_only]("Unable to install packages. "
380635e0e46SAndrew Geissler                                              "Command '%s' returned %d:\n%s" %
381635e0e46SAndrew Geissler                                              (cmd, e.returncode, e.output.decode("utf-8")))
382635e0e46SAndrew Geissler
383635e0e46SAndrew Geissler    def remove(self, pkgs, with_dependencies=True):
384635e0e46SAndrew Geissler        if not pkgs:
385635e0e46SAndrew Geissler            return
386635e0e46SAndrew Geissler
387635e0e46SAndrew Geissler        if with_dependencies:
388635e0e46SAndrew Geissler            cmd = "%s %s --force-remove --force-removal-of-dependent-packages remove %s" % \
389635e0e46SAndrew Geissler                (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
390635e0e46SAndrew Geissler        else:
391635e0e46SAndrew Geissler            cmd = "%s %s --force-depends remove %s" % \
392635e0e46SAndrew Geissler                (self.opkg_cmd, self.opkg_args, ' '.join(pkgs))
393635e0e46SAndrew Geissler
394635e0e46SAndrew Geissler        try:
395635e0e46SAndrew Geissler            bb.note(cmd)
396635e0e46SAndrew Geissler            output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8")
397635e0e46SAndrew Geissler            bb.note(output)
398635e0e46SAndrew Geissler        except subprocess.CalledProcessError as e:
399635e0e46SAndrew Geissler            bb.fatal("Unable to remove packages. Command '%s' "
400635e0e46SAndrew Geissler                     "returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
401635e0e46SAndrew Geissler
402635e0e46SAndrew Geissler    def write_index(self):
403635e0e46SAndrew Geissler        self.deploy_dir_lock()
404635e0e46SAndrew Geissler
405635e0e46SAndrew Geissler        result = self.indexer.write_index()
406635e0e46SAndrew Geissler
407635e0e46SAndrew Geissler        self.deploy_dir_unlock()
408635e0e46SAndrew Geissler
409635e0e46SAndrew Geissler        if result is not None:
410635e0e46SAndrew Geissler            bb.fatal(result)
411635e0e46SAndrew Geissler
412635e0e46SAndrew Geissler    def remove_packaging_data(self):
41309209eecSAndrew Geissler        cachedir = oe.path.join(self.target_rootfs, self.d.getVar("localstatedir"), "cache", "opkg")
414635e0e46SAndrew Geissler        bb.utils.remove(self.opkg_dir, True)
41509209eecSAndrew Geissler        bb.utils.remove(cachedir, True)
416635e0e46SAndrew Geissler
417635e0e46SAndrew Geissler    def remove_lists(self):
418635e0e46SAndrew Geissler        if not self.from_feeds:
419635e0e46SAndrew Geissler            bb.utils.remove(os.path.join(self.opkg_dir, "lists"), True)
420635e0e46SAndrew Geissler
421635e0e46SAndrew Geissler    def list_installed(self):
4226ce62a20SAndrew Geissler        return PMPkgsList(self.d, self.target_rootfs).list_pkgs()
423635e0e46SAndrew Geissler
424635e0e46SAndrew Geissler    def dummy_install(self, pkgs):
425635e0e46SAndrew Geissler        """
426635e0e46SAndrew Geissler        The following function dummy installs pkgs and returns the log of output.
427635e0e46SAndrew Geissler        """
428635e0e46SAndrew Geissler        if len(pkgs) == 0:
429635e0e46SAndrew Geissler            return
430635e0e46SAndrew Geissler
431635e0e46SAndrew Geissler        # Create an temp dir as opkg root for dummy installation
432635e0e46SAndrew Geissler        temp_rootfs = self.d.expand('${T}/opkg')
433635e0e46SAndrew Geissler        opkg_lib_dir = self.d.getVar('OPKGLIBDIR')
434635e0e46SAndrew Geissler        if opkg_lib_dir[0] == "/":
435635e0e46SAndrew Geissler            opkg_lib_dir = opkg_lib_dir[1:]
436635e0e46SAndrew Geissler        temp_opkg_dir = os.path.join(temp_rootfs, opkg_lib_dir, 'opkg')
437635e0e46SAndrew Geissler        bb.utils.mkdirhier(temp_opkg_dir)
438635e0e46SAndrew Geissler
439635e0e46SAndrew Geissler        opkg_args = "-f %s -o %s " % (self.config_file, temp_rootfs)
440635e0e46SAndrew Geissler        opkg_args += self.d.getVar("OPKG_ARGS")
441635e0e46SAndrew Geissler
442635e0e46SAndrew Geissler        cmd = "%s %s update" % (self.opkg_cmd, opkg_args)
443635e0e46SAndrew Geissler        try:
444635e0e46SAndrew Geissler            subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True)
445635e0e46SAndrew Geissler        except subprocess.CalledProcessError as e:
446635e0e46SAndrew Geissler            bb.fatal("Unable to update. Command '%s' "
447635e0e46SAndrew Geissler                     "returned %d:\n%s" % (cmd, e.returncode, e.output.decode("utf-8")))
448635e0e46SAndrew Geissler
449635e0e46SAndrew Geissler        # Dummy installation
450635e0e46SAndrew Geissler        cmd = "%s %s --noaction install %s " % (self.opkg_cmd,
451635e0e46SAndrew Geissler                                                opkg_args,
452635e0e46SAndrew Geissler                                                ' '.join(pkgs))
453db4c27eeSPatrick Williams        proc = subprocess.run(cmd, capture_output=True, encoding="utf-8", shell=True)
454db4c27eeSPatrick Williams        if proc.returncode:
455635e0e46SAndrew Geissler            bb.fatal("Unable to dummy install packages. Command '%s' "
456db4c27eeSPatrick Williams                     "returned %d:\n%s" % (cmd, proc.returncode, proc.stderr))
457db4c27eeSPatrick Williams        elif proc.stderr:
458db4c27eeSPatrick Williams            bb.note("Command '%s' returned stderr: %s" % (cmd, proc.stderr))
459635e0e46SAndrew Geissler
460635e0e46SAndrew Geissler        bb.utils.remove(temp_rootfs, True)
461635e0e46SAndrew Geissler
462db4c27eeSPatrick Williams        return proc.stdout
463635e0e46SAndrew Geissler
464635e0e46SAndrew Geissler    def backup_packaging_data(self):
465635e0e46SAndrew Geissler        # Save the opkglib for increment ipk image generation
466635e0e46SAndrew Geissler        if os.path.exists(self.saved_opkg_dir):
467635e0e46SAndrew Geissler            bb.utils.remove(self.saved_opkg_dir, True)
468635e0e46SAndrew Geissler        shutil.copytree(self.opkg_dir,
469635e0e46SAndrew Geissler                        self.saved_opkg_dir,
470635e0e46SAndrew Geissler                        symlinks=True)
471635e0e46SAndrew Geissler
472635e0e46SAndrew Geissler    def recover_packaging_data(self):
473635e0e46SAndrew Geissler        # Move the opkglib back
474635e0e46SAndrew Geissler        if os.path.exists(self.saved_opkg_dir):
475635e0e46SAndrew Geissler            if os.path.exists(self.opkg_dir):
476635e0e46SAndrew Geissler                bb.utils.remove(self.opkg_dir, True)
477635e0e46SAndrew Geissler
478635e0e46SAndrew Geissler            bb.note('Recover packaging data')
479635e0e46SAndrew Geissler            shutil.copytree(self.saved_opkg_dir,
480635e0e46SAndrew Geissler                            self.opkg_dir,
481635e0e46SAndrew Geissler                            symlinks=True)
482635e0e46SAndrew Geissler
483635e0e46SAndrew Geissler    def package_info(self, pkg):
484635e0e46SAndrew Geissler        """
485635e0e46SAndrew Geissler        Returns a dictionary with the package info.
486635e0e46SAndrew Geissler        """
487635e0e46SAndrew Geissler        cmd = "%s %s info %s" % (self.opkg_cmd, self.opkg_args, pkg)
488635e0e46SAndrew Geissler        pkg_info = super(OpkgPM, self).package_info(pkg, cmd)
489635e0e46SAndrew Geissler
490635e0e46SAndrew Geissler        pkg_arch = pkg_info[pkg]["arch"]
491635e0e46SAndrew Geissler        pkg_filename = pkg_info[pkg]["filename"]
492635e0e46SAndrew Geissler        pkg_info[pkg]["filepath"] = \
493635e0e46SAndrew Geissler                os.path.join(self.deploy_dir, pkg_arch, pkg_filename)
494635e0e46SAndrew Geissler
495635e0e46SAndrew Geissler        return pkg_info
496635e0e46SAndrew Geissler
497635e0e46SAndrew Geissler    def extract(self, pkg):
498635e0e46SAndrew Geissler        """
499635e0e46SAndrew Geissler        Returns the path to a tmpdir where resides the contents of a package.
500635e0e46SAndrew Geissler
501635e0e46SAndrew Geissler        Deleting the tmpdir is responsability of the caller.
502635e0e46SAndrew Geissler        """
503635e0e46SAndrew Geissler        pkg_info = self.package_info(pkg)
504635e0e46SAndrew Geissler        if not pkg_info:
505635e0e46SAndrew Geissler            bb.fatal("Unable to get information for package '%s' while "
506635e0e46SAndrew Geissler                     "trying to extract the package."  % pkg)
507635e0e46SAndrew Geissler
508635e0e46SAndrew Geissler        tmp_dir = super(OpkgPM, self).extract(pkg, pkg_info)
509169d7bccSPatrick Williams        bb.utils.remove(os.path.join(tmp_dir, "data.tar.zst"))
510635e0e46SAndrew Geissler
511635e0e46SAndrew Geissler        return tmp_dir
512