Skip to content

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;

Clone this wiki locally