#!/bin/perl use strict; use warnings; my @DELETE_LIST = ( qr/app:layout_behavior="[^"^`]*"/ ); my $ANDROID_VERSION; my $LIB_RES_DIR; my $LIB_CLASS_DIR; my $CMD_DELETE; my $CMD_COPY; my $CMD_7Z; # get variables from includes.sh { open(my $FILE, '<', "includes.sh"); foreach my $line (<$FILE>) { if (length($line) < 2 or substr($line, 0, 1) eq '#') { next; } my $decl = substr($line, 0, -1); $decl =~ s/="/ = "/; $decl =~ s/='/ = '/; $decl = "\$" . $decl . ";\n"; eval($decl); } close($FILE); } # I make the assumption that all tags that don't directly belong to the tag can be ignored when looking for merge conflicts. # This is a helper function that keeps track of how many tag levels deep the interpreter is. sub level_delta { my @chars = split('', shift); my $delta = 0; my $quotes = 0; my $tag = 0; my $prev = ''; foreach (@chars) { if ($_ eq '"') { $quotes ^= 1; } if (!$quotes) { if ($_ eq '<') { $tag = 1; $delta += 1; } elsif ($_ eq '/' and $prev eq '<') { $delta -= 2; } elsif ($_ eq '>' and $prev eq '/') { $delta -= 1; } } $prev = $_; } return $delta; } # The purpose of this function is self-explanatory, but I'm not sure if this is a particularly good way of going about it. # The reason I converted this into a hash tree instead of a single hash was to potentially reduce lookup times, # but it seems to only be about 15% faster... sub value_exists { my $root = shift; my $dir = shift; my $type = shift; my $name = shift; if (exists($root->{$dir})) { my $dir_hash = $root->{$dir}; if (exists($dir_hash->{$type})) { my $type_hash = $dir_hash->{$type}; if (exists($type_hash->{$name})) { return 1; } else { $type_hash->{$name} = 1; # $values_hash{$name} = "$pkg_name:$line_no$value"; } } else { $dir_hash->{$type} = {}; } } else { $root->{$dir} = {}; } return 0; } if (!-d "lib") { print( "lib/ folder has not been created.\n", "Try running get-packages.sh to retrieve some library packages.\n" ); exit; } if (-d "$LIB_RES_DIR") { print("Clearing old library resources...\n"); exit if (system("$CMD_DELETE $LIB_RES_DIR") != 0); mkdir("$LIB_RES_DIR"); } if (-d "$LIB_CLASS_DIR") { print("Clearing old library classes...\n"); exit if (system("$CMD_DELETE $LIB_CLASS_DIR") != 0); mkdir("$LIB_CLASS_DIR"); } print("Extracting library resources and classes...\n"); # A JAR is basically just a ZIP file packed with classes in a certain folder structure, so we just extract everything. foreach () { system("$CMD_7Z x -y '$_' -o$LIB_CLASS_DIR > /dev/null"); } # AAR is the Android library format. It's essentially a ZIP containing a JAR and some resources. foreach () { system("$CMD_7Z x -y '$_' -o$LIB_RES_DIR res classes.jar R.txt AndroidManifest.xml > /dev/null"); system("$CMD_7Z x -y '$LIB_RES_DIR/classes.jar' -o$LIB_CLASS_DIR > /dev/null"); unlink("$LIB_RES_DIR/classes.jar"); my $name = substr($_, 4, -4); rename("$LIB_RES_DIR/R.txt", "$LIB_RES_DIR/${name}_R.txt") if (-f "$LIB_RES_DIR/R.txt"); rename("$LIB_RES_DIR/AndroidManifest.xml", "$LIB_RES_DIR/${name}_mf.xml") if (-f "$LIB_RES_DIR/AndroidManifest.xml"); rename("$LIB_RES_DIR/res", "$LIB_RES_DIR/res_$name") if (-d "$LIB_RES_DIR/res"); } print("Merging library resources...\n"); # This is the interesting part. # In order for the libraries to be loaded and work during run-time, all resources have to co-exist in the same space. # AAPT2 handles ID allocation to consistently map a single number to a single resource each, but in order for it to work, # there must not be any resources across all packages/libraries (including your project) that share the same name. # The purpose of this part of the script is to remove resources with conflicting names, for better or worse. mkdir("$LIB_RES_DIR/res"); my %xml_hash; my %values_hash; my $n_delete_items = scalar @DELETE_LIST; # For each package (=library) foreach my $pkg (<$LIB_RES_DIR/res_*>) { # 12 == length of "$LIB_RES_DIR/res_" my $pkg_name = substr($pkg, 12); # The resources' sub-folders represent resource "types". The ones we'll focus on here are the "values*" types. foreach my $type_dir (<$pkg/*>) { # Get the name of the current folder my $dir = substr($type_dir, length($pkg) + 1); # Mirror this folder name in the output my $out_dir = "$LIB_RES_DIR/res/$dir"; # Non-"values" directories if ($type_dir !~ /\/values/) { if ($n_delete_items > 0) { mkdir $out_dir if (!-d $out_dir); foreach my $fname (<$type_dir/*>) { open(my $fh, '<', $fname); read($fh, my $content, -s $fh); close($fh); foreach my $r (@DELETE_LIST) { $content =~ s/$r//g; } my $xml_name = substr($fname, length($type_dir) + 1); my $out_xml = "$out_dir/$xml_name"; open($fh, '>', $out_xml); print $fh $content; close($fh); } } else { system("$CMD_COPY -r '$type_dir' $LIB_RES_DIR/res"); } next; } mkdir $out_dir if (!-d $out_dir); # For each xml foreach my $v_xml (<$type_dir/*>) { open(my $fh, '<', $v_xml); chomp(my @xml = <$fh>); close($fh); my $xml_name = substr($v_xml, length($type_dir) + 1); my $out_xml = "$out_dir/$xml_name"; # Treat the name of this file has being a key in a hash. # When the same file is encountered in a different library package, # it will be checked for any duplicates before being appended to the existing text for that file. if (!exists($xml_hash{$out_xml})) { $xml_hash{$out_xml} = []; } my $line_no = 0; my $level = 0; # For each line in the new XML foreach (@xml) { $line_no += 1; my $line = $_; $line =~ s/^\s+|\s+$//g; # Skip this line if it's a comment my $pref = substr($line, 0, 2); next if ($pref eq "") { $xml[$line_no - 1] = ""; next; } if (substr($line, 0, 10) eq "'); unshift(@{$xml_hash{$_}}, ''); push(@{$xml_hash{$_}}, ''); my $content = join("\n", @{$xml_hash{$_}}); foreach my $r (@DELETE_LIST) { $content =~ s/$r//g; } open(my $fh, '>', $_); print $fh $content; close($fh); }