-
Notifications
You must be signed in to change notification settings - Fork 11
xposed.pm
quillon edited this page Jan 27, 2016
·
1 revision
###xposed.pm
package Xposed;
use strict;
use warnings;
### 貌似是一些和模块定义、导出相关的,暂时不去细扣了
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(print_status print_error);
use Config::IniFiles;
use File::Path qw(make_path);
use File::ReadBackwards;
use File::Tail;
use FindBin qw($Bin);
use POSIX qw(strftime);
use Term::ANSIColor;
### 定义包全局变量
our $cfg;
### 定义局部变量。
### 根据字面意思,应该是当前支持的最大sdk版本是23,对应的android6.0
my $MAX_SUPPORTED_SDK = 23;
### 输出一些信息,不细究了
sub print_status($$) {
my $text = shift;
my $level = shift;
my $color = ('black on_white', 'white on_blue')[$level];
print colored($text, $color), "\n";
}
### 输出一些信息,不细究了
sub print_error($) {
my $text = shift;
print STDERR colored("ERROR: $text", 'red'), "\n";
}
### 获取时间戳
sub timestamp() {
return strftime('%Y%m%d_%H%M%S', localtime());
}
### 读取配置文件的信息,目前默认的是build.conf文件
# Load and return a config file in .ini format
sub load_config($) {
### 获取传入的配置文件的名称。(build.conf)
my $cfgname = shift;
### 检查文件是否有读取权限
# Make sure that the file is readable
if (!-r $cfgname) {
print_error("$cfgname doesn't exist or isn't readable");
return undef;
}
### 使用Config::IniFiles读取配置文件信息
# Load the file
my $cfg = Config::IniFiles->new( -file => $cfgname, -handle_trailing_comment => 1);
if (!$cfg) {
print_error("Could not read $cfgname:");
print STDERR " $_\n" foreach (@Config::IniFiles::errors);
return undef;
}
# Trim trailing spaces of each value
foreach my $section ($cfg->Sections()) {
foreach my $key ($cfg->Parameters($section)) {
my $value = $cfg->val($section, $key);
if ($value =~ s/\s+$//) {
$cfg->setval($section, $key, $value);
}
}
}
return $cfg;
}
### 检查一些必须的东西是否设定或存在等
# Makes sure that some important exist
sub check_requirements() {
### 判断输出目录是否设定,并且这个目录是否存在
my $outdir = $cfg->val('General', 'outdir');
if (!-d $outdir) {
print_error('[General][outdir] must point to a directory');
return 0;
}
### 判断输出目录下的java/XposedBridge.jar是否存在并可读
my $jar = "$outdir/java/XposedBridge.jar";
if (!-r $jar) {
print_error("$jar doesn't exist or isn't readable");
return 0;
}
### 判断version信息的合法性
my $version = $cfg->val('Build', 'version');
if (!$version || $version !~ m/^\d+/) {
print_error('[Build][version] must begin with an integer');
return 0;
}
if ($version =~ m/^\d+\s*$/ && !$cfg->val('Build', 'official')) {
print_error('[Build][version] should contain your custom suffix');
return 0;
}
return 1;
}
### 创建一个显示最后一行信息的进程。滚动更新最后一行信息。
# Start a separate process to display the last line of the log
sub start_tail_process($) {
my $logfile = shift;
my $longest = 0;
local $SIG{'TERM'} = sub {
print "\r", ' ' x $longest, color('reset'), "\n" if $longest;
exit 0;
};
my $pid = fork();
return $pid if ($pid > 0);
my $file = File::Tail->new(name => $logfile, ignore_nonexistant => 1, interval => 5, tail => 1);
while (defined(my $line = $file->read())) {
$line = substr($line, 0, 80);
$line =~ s/\s+$//;
my $len = length($line);
if ($len < $longest) {
$line .= ' ' x ($longest - $len);
} else {
$longest = $len;
}
print "\r", colored($line, 'yellow');
}
exit 0;
}
### 展开编译目标。比如编译参数(-t arm,x86:21)
# Expands the list of targets and replaces the "all" wildcard
sub expand_targets($;$) {
### 获取目标信息(命令行的-t参数)
my $spec = shift;
### 是否打印信息
my $print = shift || 0;
my @result;
my %seen;
### 将-t参数以‘/’分开,对每部分子串进行处理
foreach (split(m/[\/ ]+/, $spec)) {
### 将前面分开的子串再以‘:’分开为2部分,进行处理。分别存入pfspec和sdkspec。(platfrom和sdk)
my ($pfspec, $sdkspec) = split(m/[: ]+/, $_, 2);
### 将pfspec以‘,’分开,存入pflist数组。如果是all或all+,则出入所有的可支持的平台
my @pflist = ($pfspec ne 'all' && $pfspec ne 'all+') ? split(m/[, ]/, $pfspec) : ('arm', 'x86', 'arm64', 'armv5');
### 如果是all+,再增加host和hostd,然后将pfspec强制改为all
if ($pfspec eq 'all+') {
push @pflist, 'host';
push @pflist, 'hostd';
$pfspec = 'all';
}
### 将sdkspec以‘,’分开,存入sdklist数组。如果是all,则获取build.conf的AospDir节下的所有键存入sdklist
my @sdklist = ($sdkspec ne 'all') ? split(m/[, ]/, $sdkspec) : $cfg->Parameters('AospDir');
### 对每一个sdk值
foreach my $sdk (@sdklist) {
### 对每一个平台
foreach my $pf (@pflist) {
### 检查每个对应的sdk和平台是否支持
next if !check_target_sdk_platform($pf, $sdk, $pfspec eq 'all' || $sdkspec eq 'all');
### 没看出来这个干嘛用。。。
next if $seen{"$pf/$sdk"}++;
### 将符合的platform和sdk对存入result数组
push @result, { platform => $pf, sdk => $sdk };
### 如果print为真,打印信息
print " SDK $sdk, platform $pf\n" if $print;
}
}
}
### 返回符合的platform和sdk对数组
return @result;
}
### 检查给定的平台和sdk是否支持
# Check target SDK version and platform
sub check_target_sdk_platform($$;$) {
my $platform = shift;
my $sdk = shift;
my $wildcard = shift || 0;
### 不支持sdk小于15,等于20或者大于23的
if ($sdk < 15 || $sdk == 20 || $sdk > $MAX_SUPPORTED_SDK) {
print_error("Unsupported SDK version $sdk");
return 0;
}
### 平台为armv5的要求sdk大于17
if ($platform eq 'armv5') {
if ($sdk > 17) {
print_error('ARMv5 builds are only supported up to Android 4.2 (SDK 17)') unless $wildcard;
return 0;
}
### 平台为arm64的要求sdk大于等于21
} elsif ($platform eq 'arm64') {
if ($sdk < 21) {
print_error('arm64 builds are not supported prior to Android 5.0 (SDK 21)') unless $wildcard;
return 0;
}
### host或者hostd参数,要求sdk大于等于21
} elsif ($platform eq 'host' || $platform eq 'hostd') {
if ($sdk < 21) {
print_error('host builds are not supported prior to Android 5.0 (SDK 21)') unless $wildcard;
return 0;
}
### 其他的只支持arm平台或者x86平台,这两个平台对sdk没要求
} elsif ($platform ne 'arm' && $platform ne 'x86') {
print_error("Unsupported target platform $platform");
return 0;
}
return 1;
}
### 获取build.conf中定义的aosp的路径
# Returns the root of the AOSP tree for the specified SDK
sub get_rootdir($) {
my $sdk = shift;
my $dir = $cfg->val('AospDir', $sdk);
if (!$dir) {
print_error("No root directory has been configured for SDK $sdk");
return undef;
} elsif ($dir !~ m/^/) {
print_error("Root directory $dir must be an absolute path");
return undef;
} elsif (!-d $dir) {
print_error("$dir is not a directory");
return undef;
} else {
# Trim trailing slashes
$dir =~ s|/+$||;
return $dir;
}
}
### 获取aosp的输出目录
# Determines the root directory where compiled files are put
sub get_outdir($) {
my $platform = shift;
if ($platform eq 'arm') {
return 'out/target/product/generic';
} elsif ($platform eq 'armv5') {
return 'out_armv5/target/product/generic';
} elsif ($platform eq 'x86' || $platform eq 'arm64') {
return 'out/target/product/generic_' . $platform;
} else {
print_error("Could not determine output directory for $platform");
return undef;
}
}
### 获取收集目录
# Determines the directory where compiled files etc. are collected
sub get_collection_dir($$) {
my $platform = shift;
my $sdk = shift;
return sprintf('%s/sdk%d/%s', $cfg->val('General', 'outdir'), $sdk, $platform);
}
### android源码编译前,lunch命令选择的模式
# Determines the mode that has to be passed to the "lunch" command
sub get_lunch_mode($$) {
my $platform = shift;
my $sdk = shift;
if ($platform eq 'arm' || $platform eq 'armv5' || $platform eq 'host' || $platform eq 'hostd') {
return ($sdk <= 17) ? 'full-eng' : 'aosp_arm-eng';
} elsif ($platform eq 'x86') {
return ($sdk <= 17) ? 'full_x86-eng' : 'aosp_x86-eng';
} elsif ($platform eq 'arm64' && $sdk >= 21) {
return 'aosp_arm64-eng';
} else {
print_error("Could not determine lunch mode for SDK $sdk, platform $platform");
return undef;
}
}
# Get default make parameters
sub get_make_parameters($$) {
my $platform = shift;
my $sdk = shift;
### 将build.conf配置文件中定义的[Build]->makeflags的值以空字符分割。若没有,默认位-j4
my @params = split(m/\s+/, $cfg->val('Build', 'makeflags', '-j4'));
### 如果platform为armv5,增加这几个参数
# ARMv5 build need some special parameters
if ($platform eq 'armv5') {
push @params, 'OUT_DIR=out_armv5';
push @params, 'TARGET_ARCH_VARIANT=armv5te';
push @params, 'ARCH_ARM_HAVE_TLS_REGISTER=false';
push @params, 'TARGET_CPU_SMP=false';
### 如果sdk小于23,增加下面这个参数
} elsif ($sdk < 23) {
push @params, 'TARGET_CPU_SMP=true';
### 如果platform是x86,增加下面这个参数
} elsif ($platform eq 'x86') {
push @params, 'arch_variant_cflags=\'-march=prescott -mno-ssse3\'';
}
return @params;
}
### 编译给定的paltform和sdk组合
sub compile($$$$$;$$$) {
my $platform = shift;
my $sdk = shift;
my $params = shift;
my $targets = shift;
my $makefiles = shift;
my $incremental = shift || 0;
my $silent = shift || 0;
my $logprefix = shift || 'build';
# Initialize some general build parameters
### 获取当前sdk对应的aosp路径,即build.conf里面定义的路径
my $rootdir = get_rootdir($sdk) || return 0;
### 获取android源码编译前,lunch命令选择的模式
my $lunch_mode = get_lunch_mode($platform, $sdk) || return 0;
# Build the command string
### 将工作目录切换到aosp的目录
my $cdcmd = 'cd ' . $rootdir;
### 执行 build/envsetup.sh命令,配置aosp的编译环境
my $envsetupcmd = '. build/envsetup.sh >/dev/null';
### 执行lunch命令,指定编译目标版本
my $lunchcmd = 'lunch ' . $lunch_mode . ' >/dev/null';
### 如果是增量编译,则命令为: ONE_SHOT_MAKEFILE='frameworks/base/cmds/xposed/Android.mk art/Android.mk' make -C $rootdir -f build/core/main.mk
### 如果不是增量编译,则命令为:make
my $makecmd = $incremental ? "ONE_SHOT_MAKEFILE='" . join(' ', @$makefiles) . "' make -C $rootdir -f build/core/main.mk " : 'make ';
### make命令后面增加前面取得的参数列表和目标列表
$makecmd .= join(' ', @$params, @$targets);
### 总的命令是把前面几个命令连起来
my $cmd = join(' && ', $cdcmd, $envsetupcmd, $lunchcmd, $makecmd);
print colored('Executing: ', 'magenta'), $cmd, "\n";
my ($logfile, $tailpid);
### 静默模式
if ($silent) {
my $logdir = get_collection_dir($platform, $sdk) . '/logs';
make_path($logdir);
$logfile = sprintf('%s/%s_%s.log', $logdir, $logprefix, timestamp());
print colored('Log: ', 'magenta'), $logfile, "\n";
$cmd = "{ $cmd ;} &> $logfile";
$tailpid = start_tail_process($logfile);
}
### 执行真正的编译过程
# Execute the command
my $rc = system("bash -c \"$cmd\"");
### 对应静默模式的清理工作
# Stop progress indicator process
if ($tailpid) {
kill('TERM', $tailpid);
waitpid($tailpid, 0);
}
### 输出编译结果
# Return the result
if ($rc == 0) {
print colored('Build was successful!', 'green'), "\n\n";
return 1;
} else {
print colored('Build failed!', 'red'), "\n";
if ($silent) {
print "Last 10 lines from the log:\n";
my $tail = File::ReadBackwards->new($logfile);
my @lines;
for (1..10) {
last if $tail->eof();
unshift @lines, $tail->readline();
}
print " $_" foreach (@lines);
}
print "\n";
return 0;
}
}
sub sign_zip($) {
my $file = shift;
my $signed = $file . '.signed';
my $cmd = "java -jar $Bin/signapk.jar -w $Bin/signkey.x509.pem $Bin/signkey.pk8 $file $signed";
system("bash -c \"$cmd\"") == 0 || return 0;
rename($signed, $file);
return 1;
}
1;