yocto生成rootfs的流程
头一张图是rootfs生成的总体流程;后两张图是按阶段描述:
- 第一阶段 —— 生成deb包(只考虑deb);生成的deb包(们)被称为Feeds。这阶段与这关系不大,不细看了。
- 第二阶段 —— 使用Feeds,生成rootfs。细看着一块。图上包含了do_image相关内容,这块与这关系不大,也不看;只看do_rootfs部分。
不贴太多代码,拣关键的贴:
_create的实现
123 class PkgRootfs(DpkgOpkgRootfs):
124 def __init__(self, d, manifest_dir, progress_reporter=None, logcatcher=None):
125 ¦ super(PkgRootfs, self).__init__(d, progress_reporter, logcatcher)
132 ¦ bb.utils.remove(self.image_rootfs, True)
133 ¦ bb.utils.remove(self.d.getVar('MULTILIB_TEMP_ROOTFS'), True)
134 ¦ self.manifest = PkgManifest(d, manifest_dir)
135 ¦ self.pm = DpkgPM(d, d.getVar('IMAGE_ROOTFS'),
136 ¦ ¦ ¦ ¦ ¦ ¦d.getVar('PACKAGE_ARCHS'),
137 ¦ ¦ ¦ ¦ ¦ ¦d.getVar('DPKG_ARCH'))
140 def _create(self):
141 ¦ pkgs_to_install = self.manifest.parse_initial_manifest()
144
145 ¦ alt_dir = self.d.expand("${IMAGE_ROOTFS}/var/lib/dpkg/alternatives")
146 ¦ bb.utils.mkdirhier(alt_dir)
147
148 ¦ # update PM index files
149 ¦ self.pm.write_index()
152
158 ¦ self.pm.update()
162
163 ¦ for pkg_type in self.install_order:
164 ¦ ¦ if pkg_type in pkgs_to_install:
165 ¦ ¦ ¦ self.pm.install(pkgs_to_install[pkg_type],
166 ¦ ¦ ¦ ¦ ¦ ¦ ¦ [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY])
167 ¦ ¦ ¦ self.pm.fix_broken_dependencies()
173
174 ¦ self.pm.install_complementary()
178
179 ¦ self._setup_dbg_rootfs(['/var/lib/dpkg'])
180
181 ¦ self.pm.fix_broken_dependencies()
182
183 ¦ self.pm.mark_packages("installed")
184
185 ¦ self.pm.run_pre_post_installs()
主要是2个对象:
self.manifest = PkgManifest
self.pm = DpkgPM(
manifest
的作用是得到需要安装的包列表 pkgs_to_install = self.manifest.parse_initial_manifest()
。
剩下基本就是pm的操作:
- self.pm.write_index()
- self.pm.update()
- self.pm.install(pkgs_to_install[pkg_type], [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY])
- self.pm.fix_broken_dependencies()
- self.pm.install_complementary()
- self.pm.mark_packages("installed")
- self.pm.run_pre_post_installs()
这是抽象类中的实现流程,确认看到 self.pm.install
就可以了;该流程中最重要的对象就是DpkgPM,下面重点看这个类。
indexer
self.pm.write_index()
166 class DpkgPM(OpkgDpkgPM):
167 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None, deb_repo_workdir="oe-rootfs-repo", filterbydependencies=True):
....
189 ¦ self.indexer = DpkgIndexer(self.d, self.deploy_dir)
336 This function creates the index files
337 def write_index(self):
338 ¦ self.deploy_dir_lock()
339
340 ¦ result = self.indexer.write_index()
341
342 ¦ self.deploy_dir_unlock()
343
344 ¦ if result is not None:
345 ¦ ¦ bb.fatal(result)
这里看出,DpkgIndexer
是一个很重要的类,在看PM之前先看这个
9 class DpkgIndexer(Indexer):
10 def _create_configs(self):
11 ¦ bb.utils.mkdirhier(self.apt_conf_dir)
12 ¦ bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "lists", "partial"))
13 ¦ bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "apt.conf.d"))
14 ¦ bb.utils.mkdirhier(os.path.join(self.apt_conf_dir, "preferences.d"))
15
16 ¦ with open(os.path.join(self.apt_conf_dir, "preferences"),
17 ¦ ¦ ¦ "w") as prefs_file:
18 ¦ ¦ pass
19 ¦ with open(os.path.join(self.apt_conf_dir, "sources.list"),
20 ¦ ¦ ¦ "w+") as sources_file:
21 ¦ ¦ pass
22
23 ¦ with open(self.apt_conf_file, "w") as apt_conf:
24 ¦ ¦ with open(os.path.join(self.d.expand("${STAGING_ETCDIR_NATIVE}"),
25 ¦ ¦ ¦ "apt", "apt.conf.sample")) as apt_conf_sample:
26 ¦ ¦ ¦ for line in apt_conf_sample.read().split("\n"):
27 ¦ ¦ ¦ ¦ line = re.sub(r"#ROOTFS#", "/dev/null", line)
28 ¦ ¦ ¦ ¦ line = re.sub(r"#APTCONF#", self.apt_conf_dir, line)
29 ¦ ¦ ¦ ¦ apt_conf.write(line + "\n")
31 def write_index(self):
32 ¦ self.apt_conf_dir = os.path.join(self.d.expand("${APTCONF_TARGET}"),
33 ¦ ¦ ¦ "apt-ftparchive")
34 ¦ self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
35 ¦ self._create_configs()
36
37 ¦ os.environ['APT_CONFIG'] = self.apt_conf_file
38
39 ¦ pkg_archs = self.d.getVar('PACKAGE_ARCHS')
40 ¦ if pkg_archs is not None:
41 ¦ ¦ arch_list = pkg_archs.split()
42 ¦ sdk_pkg_archs = self.d.getVar('SDK_PACKAGE_ARCHS')
43 ¦ if sdk_pkg_archs is not None:
44 ¦ ¦ for a in sdk_pkg_archs.split():
45 ¦ ¦ ¦ if a not in pkg_archs:
46 ¦ ¦ ¦ ¦ arch_list.append(a)
47
48 ¦ all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or "").split()
49 ¦ arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in arch_list)
50
51 ¦ apt_ftparchive = bb.utils.which(os.getenv('PATH'), "apt-ftparchive")
52 ¦ gzip = bb.utils.which(os.getenv('PATH'), "gzip")
53
54 ¦ index_cmds = []
55 ¦ deb_dirs_found = False
56 ¦ for arch in arch_list:
57 ¦ ¦ arch_dir = os.path.join(self.deploy_dir, arch)
58 ¦ ¦ if not os.path.isdir(arch_dir):
59 ¦ ¦ ¦ continue
60
61 ¦ ¦ cmd = "cd %s; PSEUDO_UNLOAD=1 %s packages . > Packages;" % (arch_dir, apt_ftparchive)
62
63 ¦ ¦ cmd += "%s -fcn Packages > Packages.gz;" % gzip
64
65 ¦ ¦ with open(os.path.join(arch_dir, "Release"), "w+") as release:
66 ¦ ¦ ¦ release.write("Label: %s\n" % arch)
67
68 ¦ ¦ cmd += "PSEUDO_UNLOAD=1 %s release . >> Release" % apt_ftparchive
69
70 ¦ ¦ index_cmds.append(cmd)
71
72 ¦ ¦ deb_dirs_found = True
73
74 ¦ if not deb_dirs_found:
75 ¦ ¦ bb.note("There are no packages in %s" % self.deploy_dir)
76 ¦ ¦ return
77
78 ¦ oe.utils.multiprocess_launch(create_index, index_cmds, self.d)
79 ¦ if self.d.getVar('PACKAGE_FEED_SIGN') == '1':
80 ¦ ¦ raise NotImplementedError('Package feed signing not implementd for dpkg')
这段看完,知道 是在目录 self.deploy_dir/arch
创建apt本地仓库(其中deploy_dir后续再说)。即self.pm.write_index()
就是在初始化apt本地仓库。
DpkgPM
166 class DpkgPM(OpkgDpkgPM):
167 def __init__(self, d, target_rootfs, archs, base_archs, apt_conf_dir=None, deb_repo_workdir="oe-rootfs-repo", filterbydependencies=True):
168 ¦ super(DpkgPM, self).__init__(d, target_rootfs)
169 ¦ self.deploy_dir = oe.path.join(self.d.getVar('WORKDIR'), deb_repo_workdir)
170
171 ¦ create_packages_dir(self.d, self.deploy_dir, d.getVar("DEPLOY_DIR_DEB"), "package_write_deb", filterbydependencies)
172
173 ¦ if apt_conf_dir is None:
174 ¦ ¦ self.apt_conf_dir = self.d.expand("${APTCONF_TARGET}/apt")
175 ¦ else:
176 ¦ ¦ self.apt_conf_dir = apt_conf_dir
177 ¦ self.apt_conf_file = os.path.join(self.apt_conf_dir, "apt.conf")
178 ¦ self.apt_get_cmd = bb.utils.which(os.getenv('PATH'), "apt-get")
179 ¦ self.apt_cache_cmd = bb.utils.which(os.getenv('PATH'), "apt-cache")
180
181 ¦ self.apt_args = d.getVar("APT_ARGS")
182
183 ¦ self.all_arch_list = archs.split()
184 ¦ all_mlb_pkg_arch_list = (self.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS') or "").split()
185 ¦ self.all_arch_list.extend(arch for arch in all_mlb_pkg_arch_list if arch not in self.all_arch_list)
186
187 ¦ self._create_configs(archs, base_archs)
188
189 ¦ self.indexer = DpkgIndexer(self.d, self.deploy_dir)
380 def _create_configs(self, archs, base_archs):
381 ¦ base_archs = re.sub(r"_", r"-", base_archs)
382
383 ¦ if os.path.exists(self.apt_conf_dir):
384 ¦ ¦ bb.utils.remove(self.apt_conf_dir, True)
385
386 ¦ bb.utils.mkdirhier(self.apt_conf_dir)
387 ¦ bb.utils.mkdirhier(self.apt_conf_dir + "/lists/partial/")
388 ¦ bb.utils.mkdirhier(self.apt_conf_dir + "/apt.conf.d/")
389 ¦ bb.utils.mkdirhier(self.apt_conf_dir + "/preferences.d/")
390
391 ¦ arch_list = []
414 ¦ arch_list.reverse()
415
416 ¦ with open(os.path.join(self.apt_conf_dir, "sources.list"), "w+") as sources_file:
417 ¦ ¦ for arch in arch_list:
418 ¦ ¦ ¦ sources_file.write("deb [trusted=yes] file:%s/ ./\n" %
419 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ os.path.join(self.deploy_dir, arch))
420
421 ¦ base_arch_list = base_archs.split()
422 ¦ multilib_variants = self.d.getVar("MULTILIB_VARIANTS");
423 ¦ for variant in multilib_variants.split():
424 ¦ ¦ localdata = bb.data.createCopy(self.d)
425 ¦ ¦ variant_tune = localdata.getVar("DEFAULTTUNE:virtclass-multilib-" + variant, False)
426 ¦ ¦ orig_arch = localdata.getVar("DPKG_ARCH")
427 ¦ ¦ localdata.setVar("DEFAULTTUNE", variant_tune)
428 ¦ ¦ variant_arch = localdata.getVar("DPKG_ARCH")
429 ¦ ¦ if variant_arch not in base_arch_list:
430 ¦ ¦ ¦ base_arch_list.append(variant_arch)
431
432 ¦ with open(self.apt_conf_file, "w+") as apt_conf:
433 ¦ ¦ with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample:
434 ¦ ¦ ¦ for line in apt_conf_sample.read().split("\n"):
435 ¦ ¦ ¦ ¦ match_arch = re.match(r" Architecture \".*\";$", line)
436 ¦ ¦ ¦ ¦ architectures = ""
437 ¦ ¦ ¦ ¦ if match_arch:
438 ¦ ¦ ¦ ¦ ¦ for base_arch in base_arch_list:
439 ¦ ¦ ¦ ¦ ¦ ¦ architectures += "\"%s\";" % base_arch
440 ¦ ¦ ¦ ¦ ¦ apt_conf.write(" Architectures {%s};\n" % architectures);
441 ¦ ¦ ¦ ¦ ¦ apt_conf.write(" Architecture \"%s\";\n" % base_archs)
442 ¦ ¦ ¦ ¦ else:
443 ¦ ¦ ¦ ¦ ¦ line = re.sub(r"#ROOTFS#", self.target_rootfs, line)
444 ¦ ¦ ¦ ¦ ¦ line = re.sub(r"#APTCONF#", self.apt_conf_dir, line)
445 ¦ ¦ ¦ ¦ ¦ apt_conf.write(line + "\n")
446
447 ¦ target_dpkg_dir = "%s/var/lib/dpkg" % self.target_rootfs
448 ¦ bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "info"))
449
450 ¦ bb.utils.mkdirhier(os.path.join(target_dpkg_dir, "updates"))
264 def update(self):
265 ¦ os.environ['APT_CONFIG'] = self.apt_conf_file
266
267 ¦ self.deploy_dir_lock()
268
269 ¦ cmd = "%s update" % self.apt_get_cmd
270
271 ¦ try:
272 ¦ ¦ subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
273 ¦ except subprocess.CalledProcessError as e:
274 ¦ ¦ bb.fatal("Unable to update the package index files. Command '%s' "
275 ¦ ¦ ¦ ¦ ¦"returned %d:\n%s" % (e.cmd, e.returncode, e.output.decode("utf-8")))
276
277 ¦ self.deploy_dir_unlock()
278
279 def install(self, pkgs, attempt_only=False):
280 ¦ if attempt_only and len(pkgs) == 0:
281 ¦ ¦ return
282
283 ¦ os.environ['APT_CONFIG'] = self.apt_conf_file
284
285 ¦ cmd = "%s %s install --allow-downgrades --allow-remove-essential --allow-change-held-packages --allow-unauthenticated --no-remove %s" % \
286 ¦ ¦ ¦ (self.apt_get_cmd, self.apt_args, ' '.join(pkgs))
287
288 ¦ try:
289 ¦ ¦ bb.note("Installing the following packages: %s" % ' '.join(pkgs))
290 ¦ ¦ output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
291 ¦ ¦ bb.note(output.decode("utf-8"))
292 ¦ except subprocess.CalledProcessError as e:
293 ¦ ¦ (bb.fatal, bb.warn)[attempt_only]("Unable to install packages. "
294 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ "Command '%s' returned %d:\n%s" %
295 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ (cmd, e.returncode, e.output.decode("utf-8")))
296
297 ¦ # rename *.dpkg-new files/dirs
298 ¦ for root, dirs, files in os.walk(self.target_rootfs):
299 ¦ ¦ for dir in dirs:
300 ¦ ¦ ¦ new_dir = re.sub(r"\.dpkg-new", "", dir)
301 ¦ ¦ ¦ if dir != new_dir:
302 ¦ ¦ ¦ ¦ bb.utils.rename(os.path.join(root, dir),
303 ¦ ¦ ¦ ¦ ¦ ¦ ¦ os.path.join(root, new_dir))
304
305 ¦ ¦ for file in files:
306 ¦ ¦ ¦ new_file = re.sub(r"\.dpkg-new", "", file)
307 ¦ ¦ ¦ if file != new_file:
308 ¦ ¦ ¦ ¦ bb.utils.rename(os.path.join(root, file),
309 ¦ ¦ ¦ ¦ ¦ ¦ ¦ os.path.join(root, new_file))
这个大流程,简单的说就是配置apt-get apt-cache
,其中source.list
指向上面Indexer创建的本地apt仓库。
然后 apt-get update
, apt-get intsall
。
本地仓库中的deb来自哪里。
def create_packages_dir(d, subrepo_dir, deploydir, taskname, filterbydependencies):
这个方法比较复杂,主要是里面的数据结构比较麻烦。
代码逻辑大致明白,结合着加log的方法,弄明白了这个方法到底完成了什么。
首先,传入的参数
deploydir
指向一个目录,该目录中存放了编译好的package;当前例子中该目录的值为/home/zhangsong/work/about_yocto/build_dir/build6/tmp/deploy/deb
subrepo_dir
指向一个目录,该目录构建了一个本地库(apt库);但是这个方法并未创建仓库,而是将仓库中所有的deb都方进来。当前例子中该目录的值为/home/zhangsong/work/about_yocto/build_dir/build6/tmp/work/raspberrypi4_64-poky-linux/core-image-minimal/1.0-r0/oe-rootfs-repo
然后:这个方法是被do_rootfs
调用的(简单的这么说),后续的逻辑简单的描述就是——do_rootfs
所依赖的包(包含do_package_write_deb
的recipe)编译打包产生的deb,都放在subrepo_dir
中。效果如下:
zhangsong@zhangsong-ThinkPad-W541:~/work/about_yocto/build_dir/build6/tmp/work/raspberrypi4_64-poky-linux/core-image-minimal/1.0-r0/oe-rootfs-repo$ tree all/
all/
├── autoconf-archive_2021.02.19-r0_all.deb
├── autoconf-archive-doc_2021.02.19-r0_all.deb
├── ca-certificates_20211016-r0_all.deb
├── ca-certificates-dbg_20211016-r0_all.deb
├── ca-certificates-dev_20211016-r0_all.deb
├── ca-certificates-doc_20211016-r0_all.deb
├── Packages
├── Packages.gz
├── Release
├── run-postinsts_1.0-r10_all.deb
├── run-postinsts-dbg_1.0-r10_all.deb
├── run-postinsts-dev_1.0-r10_all.deb
├── tzdata_2021e-r0_all.deb
├── tzdata-africa_2021e-r0_all.deb
├── tzdata-americas_2021e-r0_all.deb
├── tzdata-antarctica_2021e-r0_all.deb
..............
总结
在创建rootfs的过程中,Package management起到了决定性作用(可以说PM创建了rootfs)。
分为以下几个步骤:
- 创建本地仓库目录以及将deb包放入目录中(分arch)——见
create_packages_dir
- 使用
indexer
创建apt本地仓库。 - 创建
apt-get
配置文件,重点是sources.list
指向上面创建的apt-get
本地仓库。 -
apt-get update
。 -
apt-get install pkgs
。
再结合着开头的3张图:
1 创建Feeds —— 编译出deb包
2 PM开始工作。
思路设计
目的是,(全部/大部分)使用kylin源中的deb生成rootfs。
结合上面的分析,简单的说,将apt-get的本地仓库,换成kylinos的仓库即可。