在img_from_target_files中可以看到:
if __name__ == '__main__':
try:
common.CloseInheritedPipes()
main(sys.argv[1:])
except common.ExternalError, e:
print
print " ERROR: %s" % (e,)
print
sys.exit(1)
这个表示 如果这个文件是作为模块被其他文件调用,不会执行这里面的代码。 只有执行这个文件时, if 里面的语句才会被执行。很显然在生成ota升级包时肯定会调用。
main为脚本的入口:
def main(argv):
#参数解析,将参数放在common.py定义的OPTIONS类中
def option_handler(o, a):
if o in ("-b", "--board_config"):
pass # deprecated
elif o in ("-k", "--package_key"):
OPTIONS.package_key = a
elif o in ("-i", "--incremental_from"):
OPTIONS.incremental_source = a
elif o in ("-w", "--wipe_user_data"):
OPTIONS.wipe_user_data = True
elif o in ("-n", "--no_prereq"):
OPTIONS.omit_prereq = True
elif o in ("-e", "--extra_script"):
OPTIONS.extra_script = a
elif o in ("-a", "--aslr_mode"):
if a in ("on", "On", "true", "True", "yes", "Yes"):
OPTIONS.aslr_mode = True
else:
OPTIONS.aslr_mode = False
elif o in ("--worker_threads"):
OPTIONS.worker_threads = int(a)
elif o in ("-r", "--preloader"):
OPTIONS.preloader = a
elif o in ("-l", "--logo"):
OPTIONS.logo = a
elif o in ("-u", "--uboot"):
OPTIONS.uboot = a
elif o in ("-d", "--dsp"):
OPTIONS.dsp = a
elif o in ("-f", "--special_factory_reset"):
OPTIONS.special_factory_reset = True
elif o in ("-g", "--ubifs"):
OPTIONS.ubifs = True
elif o in ("-t", "--tee"):
OPTIONS.tee = a
elif o in ("-z", "--trustonic"):
OPTIONS.trustonic = a
else:
return False
return True
args = common.ParseOptions(argv, __doc__,
extra_opts="b:k:i:d:wfgne:r:l:u:t:z:d:a:s",
extra_long_opts=["board_config=",
"package_key=",
"incremental_from=",
"wipe_user_data",
"special_factory_reset",
"ubifs",
"no_prereq",
"extra_script=",
"preloader=",
"logo=",
"uboot=",
"tee=",
"trustonic=",
"dsp=",
"worker_threads=",
"aslr_mode=",
],
extra_option_handler=option_handler)
if len(args) != 2:
common.Usage(__doc__)
sys.exit(1)
if OPTIONS.extra_script is not None:
OPTIONS.extra_script = open(OPTIONS.extra_script).read()
#解压传入的完整rom zip包
print "unzipping target target-files..."
OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
#加载META 定义的系统info 和system/build.prop 编译时的prop
OPTIONS.target_tmp = OPTIONS.input_tmp
OPTIONS.info_dict = common.LoadInfoDict(input_zip)
# If this image was originally labelled with SELinux contexts, make sure we
# also apply the labels in our new image. During building, the "file_contexts"
# is in the out/ directory tree, but for repacking from target-files.zip it's
# in the root directory of the ramdisk.
if "selinux_fc" in OPTIONS.info_dict:
OPTIONS.info_dict["selinux_fc"] = os.path.join(OPTIONS.input_tmp, "BOOT", "RAMDISK",
"file_contexts")
if OPTIONS.verbose:
print "--- target info ---"
common.DumpInfoDict(OPTIONS.info_dict)
if OPTIONS.device_specific is None:
OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
if OPTIONS.device_specific is not None:
OPTIONS.device_specific = os.path.normpath(OPTIONS.device_specific)
print "using device-specific extensions in", OPTIONS.device_specific
#生成压缩的临时文件
temp_zip_file = tempfile.NamedTemporaryFile()
output_zip = zipfile.ZipFile(temp_zip_file, "w",
compression=zipfile.ZIP_DEFLATED)
#判断是增量升级 还是刷机升级
if OPTIONS.incremental_source is None:
#刷机升级
WriteFullOTAPackage(input_zip, output_zip)
if OPTIONS.package_key is None:
OPTIONS.package_key = OPTIONS.info_dict.get(
"default_system_dev_certificate",
"build/target/product/security/testkey")
else:
#增量升级
print "unzipping source target-files..."
OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
OPTIONS.target_info_dict = OPTIONS.info_dict
OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
if OPTIONS.package_key is None:
OPTIONS.package_key = OPTIONS.source_info_dict.get(
"default_system_dev_certificate",
"build/target/product/security/testkey")
if OPTIONS.verbose:
print "--- source info ---"
common.DumpInfoDict(OPTIONS.source_info_dict)
WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
output_zip.close()
#签名ota升级包
SignOutput(temp_zip_file.name, args[1])
temp_zip_file.close()
common.Cleanup()
print "done."
- 解压
OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
解压过程调用的是common.py中定义的UnzipTemp方法
def UnzipTemp(filename, pattern=None):
"""Unzip the given archive into a temporary directory and return the name.
If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
main file), open for reading.
"""
# 该函数用于创建一个临时文件夹,参数指的是临时文件夹的前缀,返回值tmp是临时文件夹的绝对路径,并赋给OPTIONS的tempfiles属性
tmp = tempfile.mkdtemp(prefix="targetfiles-")
OPTIONS.tempfiles.append(tmp)
def unzip_to_dir(filename, dirname):
#这里设置了一个变量名cmd的数组,里面存放的是需要执行的命令和参数,这个命令也就是“unzip -o -q filename -d dirname”
cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
if pattern is not None:
cmd.append(pattern)
#这里调用了Run方法
p = Run(cmd, stdout=subprocess.PIPE)
"""Popen.communicate(input=None)与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。Communicate()返回一个元组:(stdoutdata,stderrdata)。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。"""
p.communicate()
if p.returncode != 0:
raise ExternalError("failed to unzip input target-files \"%s\"" %
(filename,))
#match :只从字符串的开始与正则表达式匹配,匹配成功返回所匹配的项,否则返回none;
m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
#如果这里加上并执行"""print m"""语句的话,结果为"""[target.zip]"""
if m:
unzip_to_dir(m.group(1), tmp)
unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
filename = m.group(1)
else:
#这里执行解压操作,文件名的值为"target.zip",tem的值为"/tmp/targetfiles-fEX9aH",并且调用upzip_to_dir方法来执行解压缩命令
unzip_to_dir(filename, tmp)
#这里返回临时路径和存储了zipfile内容的变量
# 这里的第二个参数用r表示是读取zip文件,w是创建一个zip文件
return tmp, zipfile.ZipFile(filename, "r")
- 加载info信息
OPTIONS.info_dict = common.LoadInfoDict(input_zip)
调用的是common.py中定义的LoadInfoDict函数,解析解析target.zip中META和system/build.prop的信息
LoadInfoDict解析
def LoadInfoDict(zip):
"""Read and parse the META/misc_info.txt key/value pairs from the
input target files and return a dict."""
#定义一个字典变量用于存储处理后的信息
d = {}
try:
#这里zip.read()方法打开update.zip中的META/misc_info.txt,并按"\n"进行切片
for line in zip.read("META/misc_info.txt").split("\n"):
line = line.strip()#用于移除字符串头尾指定的字符(默认为空格)
if not line or line.startswith("#"): continue#跳过注释信息
k, v = line.split("=", 1)#这里按照第一个"="进行切片
d[k] = v#封装成数据字典
except KeyError:
# ok if misc_info.txt doesn't exist
pass
# backwards compatibility: These values used to be in their own
# files. Look for them, in case we're processing an old
# target_files zip.
if "mkyaffs2_extra_flags" not in d:
try:
d["mkyaffs2_extra_flags"] = zip.read("META/mkyaffs2-extra-flags.txt").strip()
except KeyError:
# ok if flags don't exist
pass
if "recovery_api_version" not in d:
try:
d["recovery_api_version"] = zip.read("META/recovery-api-version.txt").strip()
except KeyError:
raise ValueError("can't find recovery API version in input target-files")
if "tool_extensions" not in d:
try:
d["tool_extensions"] = zip.read("META/tool-extensions.txt").strip()
except KeyError:
# ok if extensions don't exist
pass
if "fstab_version" not in d:
d["fstab_version"] = "1"
try:
data = zip.read("META/imagesizes.txt")
for line in data.split("\n"):
if not line: continue
name, value = line.split(" ", 1)
if not value: continue
if name == "blocksize":
d[name] = value
else:
d[name + "_size"] = value
except KeyError:
pass
def makeint(key):
if key in d:
if d[key].endswith('M'):
d[key] = d[key].split("M")[0]
d[key] = int(d[key], 0) * 1024 * 1024
else:
d[key] = int(d[key], 0)
makeint("recovery_api_version")
makeint("blocksize")
makeint("system_size")
makeint("userdata_size")
makeint("cache_size")
makeint("recovery_size")
makeint("boot_size")
makeint("fstab_version")
#wschen 2012-11-07
makeint("custom_size")
d["fstab"] = LoadRecoveryFSTab(zip, d["fstab_version"])
d["build.prop"] = LoadBuildProp(zip) #加载build.prop信息
return d
- WriteFullOTAPackage解析
#函数的处理过程是先获得脚本的生成器。默认格式是edify。然后获得metadata元数据,此数据来至于Android的一些环境变量。然后获得设备配置参数比如api函数的版本。然后判断是否忽略时间戳。
def WriteFullOTAPackage(input_zip, output_zip):
# TODO: how to determine this? We don't know what version it will
# be installed on top of. For now, we expect the API just won't
# change very often.
#这里引入了一个新的模块edify_generator,并且抽象一个脚本生成器,用来生成edify脚本。这里的脚脚本指的就是updater-script,它安装脚本,它是一个文本文件。
#edify有两个主要的文件。这些文件可以在.zip文件内的META-INF/com/google/android文件夹中找到。①update-binary -- 当用户选择刷入.zip(通常是在恢复模式中)时所执行的二进制解释器。②updater-script -- 安装脚本,它是一个文本文件。
#那么edify是什么呢?edify是用于从.zip文件中安装CyanogenMod和其它软件的简单脚本语言。edify脚本不一定是用于更新固件。它可以用来替换/添加/删除特定的文件,甚至格式分区。通常情况下,edify脚本运行于用户在恢复模式中选择“刷写zip”时。
script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
#创建一个元数据字典用来封装更行包的相关的系统属性,如ro.build.fingerprint(系统指纹)等。
metadata = {"post-build": GetBuildProp("ro.build.fingerprint",
OPTIONS.info_dict),
"pre-device": GetBuildProp("ro.product.device",#(采用的设备)
OPTIONS.info_dict),
"post-timestamp": GetBuildProp("ro.build.date.utc",#(系统编译的时间(数字版),没必要修改)
OPTIONS.info_dict),
}
#获得一些环境变量,封装在DEviceSpecificParams类当中,这是一个封装了设备特定属性的类;下面每个设备参数之前都有提到过,这里不再赘述。
device_specific = common.DeviceSpecificParams(
input_zip=input_zip,
input_version=OPTIONS.info_dict["recovery_api_version"],
output_zip=output_zip,
script=script,
input_tmp=OPTIONS.input_tmp,
metadata=metadata,
info_dict=OPTIONS.info_dict)
#下面这段代码我们可以理解为不允许降级,也就是说在脚本中的这段Assert语句,使得update zip包只能用于升级旧版本。
if not OPTIONS.omit_prereq:
ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)#得到系统编译世界时间
ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)#得到编译日期
script.AssertOlderBuild(ts, ts_text)
#下面的Assert语句,表示update zip包只能用于同一设备,即目标设备的 ro.product.device 必须跟update.zip中的相同。
AppendAssertions(script, OPTIONS.info_dict)
#回调函数,用于调用设备相关代码。开始升级时调用
device_specific.FullOTA_Assertions()
def FullOTA_Assertions(self):
"""Called after emitting the block of assertions at the top of a
full OTA package. Implementations can add whatever additional
assertions they like."""
return self._DoCall("FullOTA_Assertions")
def _DoCall(self, function_name, *args, **kwargs):
"""Call the named function in the device-specific module, passing
the given args and kwargs. The first argument to the call will be
the DeviceSpecific object itself. If there is no module, or the
module does not define the function, return the value of the
'default' kwarg (which itself defaults to None)."""
if self.module is None or not hasattr(self.module, function_name):
return kwargs.get("default", None)
return getattr(self.module, function_name)(*((self,) + args), **kwargs)
接下来会获取boot.img和recovery.img文件对象
boot_img = common.GetBootableImage("boot.img", "boot.img",
OPTIONS.input_tmp, "BOOT")
recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
OPTIONS.input_tmp, "RECOVERY")
def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
info_dict=None):
"""Return a File object (with name 'name') with the desired bootable
image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
'prebuilt_name', otherwise construct it from the source files in
'unpack_dir'/'tree_subdir'."""
#连接两个文件名地址,例如os.path.join("D:\","join.txt")结果是D:\join.txt,如果我们有写的动作,就会生成相应目录下相应文件,否则不会有这个文件存在。
prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
if os.path.exists(prebuilt_path):
print "using prebuilt %s..." % (prebuilt_name,)
return File.FromLocalFile(name, prebuilt_path)
else:
print "building image from target_files %s..." % (tree_subdir,)
fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
#将创建的文件对象封装在File类中返回,这里我们参考File源码,就可以发现,File类只是对文件的一个抽象,具体还封装了写和读的操作
return File(name, BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
os.path.join(unpack_dir, fs_config),
info_dict))
def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
"""Take a kernel, cmdline, and ramdisk directory from the input (in
'sourcedir'), and turn them into a boot image. Return the image
data, or None if sourcedir does not appear to contains files for
building the requested image."""
#作为access()的mode参数,测试path是否存在.
if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
return None
if info_dict is None:
info_dict = OPTIONS.info_dict
#创建临时文件对象
ramdisk_img = tempfile.NamedTemporaryFile()
img = tempfile.NamedTemporaryFile()
if os.access(fs_config_file, os.F_OK):
#使用mkbootfs工具(mkbootfs工具是编译完毕Android源代码以后,在源码目录下/out/host/linux-x86/bin自动生成的)创建ramdisk
cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
else:
cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
p1 = Run(cmd, stdout=subprocess.PIPE)
#fileno()用来取得参数stream指定的文件流所使用的文件描述词,而mkbootfs和minigzip是通过MKBOOTFS和MINIGZIP这两个变量描述的mkbootfs和minigzip工具来生成一个格式为cpio的ramdisk.img了。mkbootfs和minigzip这两个工具对应的源码分别位于system/core/cpio和external/zlib目录中。
p2 = Run(["minigzip"],
stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
p2.wait()
p1.wait()
assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
#调用Android给的命令行文件mkbootimg(out/host/linux-x86/bin/)来打包
cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
fn = os.path.join(sourcedir, "cmdline")
if os.access(fn, os.F_OK):
cmd.append("--cmdline")
cmd.append(open(fn).read().rstrip("\n"))
fn = os.path.join(sourcedir, "base")
if os.access(fn, os.F_OK):
cmd.append("--base")
cmd.append(open(fn).read().rstrip("\n"))
fn = os.path.join(sourcedir, "pagesize")
if os.access(fn, os.F_OK):
cmd.append("--pagesize")
cmd.append(open(fn).read().rstrip("\n"))
args = info_dict.get("mkbootimg_args", None)
if args and args.strip():
cmd.extend(args.split())
#wschen 2013-06-06 for firmware version in bootimage header and limit max length to 15 bytes
fn = os.path.join(sourcedir, "board")
if os.access(fn, os.F_OK):
cmd.append("--board")
cmd.append(open(fn).read().rstrip("\n")[:15])
# cmd.extend(["--ramdisk", ramdisk_img.name,
# "--output", img.name])
cmd.extend(["--ramdisk", os.path.join(sourcedir, "ramdisk"),
"--output", img.name])
p = Run(cmd, stdout=subprocess.PIPE)
p.communicate()
assert p.returncode == 0, "mkbootimg of %s image failed" % (
os.path.basename(sourcedir),)
img.seek(os.SEEK_SET, 0)
data = img.read()
ramdisk_img.close()
img.close()
return data