#!/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 <resources> 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 (<lib/*.jar>) { 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 (<lib/*.aar>) { 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 "<!"); # Delete all meta-tags and the resource tags, so that multiple XMLs can be stitched together and still be parsed all the way through # Note that we delete the contents of the line rather than the line itself, since we're iterating forwards over the same array if ($pref eq "<?" or $line eq "</resources>") { $xml[$line_no - 1] = ""; next; } if (substr($line, 0, 10) eq "<resources") { $xml[$line_no - 1] = ""; $level++; # prime hackery daiquiri next; } my $new = 0; my $name = ""; # If this tag has a 'name' attribute and we're only one level down from the root if ($level == 1 and $line =~ /<(\w+) .*name="([^"]+)/) { # Check this name for this resource type under this type sub-folder if (value_exists(\%values_hash, $dir, $1, $2)) { print("Eliminating re-def in $pkg_name at line $line_no for $dir/$1/$2\n"); $xml[$line_no - 1] = ""; } } $level += level_delta($line); } # Now that the headers and conflicting lines have been deleted, we append all the lines left to its kind of XML push(@{$xml_hash{$out_xml}}, @xml); } }} print("Writing values XMLs...\n"); foreach (keys %xml_hash) { # We make sure to add a single set of headers/footer at the end. # Note that unshift() makes the new element come first on the list. unshift(@{$xml_hash{$_}}, '<resources xmlns:ns1="urn:oasis:names:tc:xliff:document:1.2" xmlns:ns2="http://schemas.android.com/tools">'); unshift(@{$xml_hash{$_}}, '<?xml version="1.0" encoding="utf-8"?>'); push(@{$xml_hash{$_}}, '</resources>'); my $content = join("\n", @{$xml_hash{$_}}); foreach my $r (@DELETE_LIST) { $content =~ s/$r//g; } open(my $fh, '>', $_); print $fh $content; close($fh);}