Add build scripts
This commit is contained in:
parent
6c382aac9d
commit
37114ce4bf
|
@ -0,0 +1,29 @@
|
||||||
|
image: openjdk:18-jdk-bullseye
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- apt --quiet update --yes
|
||||||
|
- apt --quiet install --yes wget tar unzip perl p7zip-full
|
||||||
|
|
||||||
|
BleedingEdge:
|
||||||
|
interruptible: true
|
||||||
|
stage: build
|
||||||
|
script: |
|
||||||
|
mkdir -p ./AndroidSdk
|
||||||
|
cd ./AndroidSdk
|
||||||
|
wget -O BuildTools.zip https://dl.google.com/android/repository/build-tools_r30.0.3-linux.zip
|
||||||
|
wget -O Platform.zip https://dl.google.com/android/repository/platform-30_r03.zip
|
||||||
|
yes A | unzip BuildTools.zip || true
|
||||||
|
yes A | unzip Platform.zip || true
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
mv ./app/src/main ./main
|
||||||
|
cd ./main
|
||||||
|
mv ./java ./src
|
||||||
|
mv ../tools/tiny-android-template/* ./
|
||||||
|
echo "${SecEncodedKeystore}" | base64 --decode > ./Keystore.jks
|
||||||
|
|
||||||
|
perl ./link.pl
|
||||||
|
bash ./make.sh
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- main/app.apk
|
|
@ -10,4 +10,9 @@ Go to [-/releases](https://gitlab.com/octtspacc/browserocto) to get the latest b
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
I fail to understand how to build apps supporting old minimum API targets with classic methods, to build from source then I suggest using this application: [AIDE- IDE for Android Java C++](https://play.google.com/store/apps/details?id=com.aide.ui) (it's what I'm using for development).
|
Building this project has been tested with the following methods:
|
||||||
|
|
||||||
|
- Using the amazing scripts provided by the tiny-android-template project (thanks to [jbendtsen](https://github.com/jbendtsen/tiny-android-template)!):
|
||||||
|
- Read [.gitlab-ci.yml](./.gitlab-ci.yml) to discover how
|
||||||
|
|
||||||
|
- Using the [AIDE - IDE for Android](https://play.google.com/store/apps/details?id=com.aide.ui) app
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest
|
<manifest
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.eu.octt.browserocto">
|
package="org.eu.octt.browserocto"
|
||||||
|
android:versionCode="0"
|
||||||
|
android:versionName="0">
|
||||||
|
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="30"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
|
||||||
<application
|
<application
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Jack Bendtsen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Roughly arranged in descending order of how likely you'll need to change each value
|
||||||
|
|
||||||
|
HOST_OS="linux-x86_64"
|
||||||
|
API_LEVEL="30"
|
||||||
|
API_LEVEL_MIN="1"
|
||||||
|
ANDROID_VERSION="11"
|
||||||
|
NDK_VERSION="r23-beta4"
|
||||||
|
TARGET_ARCHES=( "arm64-v8a" )
|
||||||
|
SDK_DIR="../AndroidSdk"
|
||||||
|
KOTLIN_LIB_DIR="/usr/share/kotlin/lib"
|
||||||
|
|
||||||
|
REPO="https://dl.google.com/dl/android/maven2"
|
||||||
|
|
||||||
|
KEYSTORE="Keystore.jks"
|
||||||
|
KS_PASS="$SecKeystorePassword"
|
||||||
|
|
||||||
|
TOOLS_DIR="$SDK_DIR/android-$ANDROID_VERSION"
|
||||||
|
PLATFORM_DIR="$SDK_DIR/android-$ANDROID_VERSION"
|
||||||
|
|
||||||
|
NDK_DIR="$SDK_DIR/android-ndk-$NDK_VERSION"
|
||||||
|
NDK_BIN_DIR="$NDK_DIR/toolchains/llvm/prebuilt/$HOST_OS/bin"
|
||||||
|
NDK_INCLUDE_DIR="$NDK_DIR/toolchains/llvm/prebuilt/$HOST_OS/usr/sysroot/include"
|
||||||
|
NDK_LIB_DIR="$NDK_DIR/toolchains/llvm/prebuilt/$HOST_OS/usr/sysroot/lib"
|
||||||
|
|
||||||
|
LIB_RES_DIR="lib/res"
|
||||||
|
LIB_CLASS_DIR="lib/classes"
|
||||||
|
|
||||||
|
PKG_OUTPUT="lib"
|
||||||
|
|
||||||
|
JAR_TOOLS="java -Xmx1024M -Xss1m -jar $TOOLS_DIR/lib"
|
||||||
|
|
||||||
|
CMD_7Z="7z"
|
||||||
|
CMD_MKDIR="mkdir"
|
||||||
|
CMD_RENAME="mv"
|
||||||
|
CMD_DELETE="rm -rf"
|
||||||
|
CMD_FIND="/usr/bin/find"
|
||||||
|
CMD_CURL="curl"
|
||||||
|
CMD_SED="sed"
|
||||||
|
CMD_JAR="jar"
|
||||||
|
CMD_JAVA="java"
|
||||||
|
CMD_JAVAC="javac"
|
||||||
|
CMD_KOTLINC="kotlinc"
|
||||||
|
|
||||||
|
CMD_ADB="$SDK_DIR/platform-tools/adb"
|
||||||
|
CMD_D8="$CMD_JAVA -Xmx1024M -Xss1m -cp $TOOLS_DIR/lib/d8.jar com.android.tools.r8.D8"
|
|
@ -0,0 +1,461 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $ANDROID_VERSION;
|
||||||
|
|
||||||
|
my $SDK_DIR;
|
||||||
|
my $TOOLS_DIR;
|
||||||
|
my $PLATFORM_DIR;
|
||||||
|
|
||||||
|
my $CMD_DELETE;
|
||||||
|
my $CMD_JAR;
|
||||||
|
my $CMD_JAVAC;
|
||||||
|
my $CMD_JAVA;
|
||||||
|
my $CMD_D8;
|
||||||
|
|
||||||
|
my $LIB_RES_DIR;
|
||||||
|
my $LIB_CLASS_DIR;
|
||||||
|
|
||||||
|
# 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every library/package that uses resources needs a list that maps resource variables to IDs in code form.
|
||||||
|
# It starts with a simple text file that gets translated into a .java, which is in turn compiled into the package.
|
||||||
|
# All that's needed is a simple re-formatting for each line, with a few Java keywords and curly braces in-between.
|
||||||
|
|
||||||
|
sub gen_rjava {
|
||||||
|
my $pkg = shift;
|
||||||
|
my $r_txt = shift;
|
||||||
|
|
||||||
|
my @out = (
|
||||||
|
"// Auto-generated by an unofficial tool",
|
||||||
|
"",
|
||||||
|
"package $pkg;",
|
||||||
|
"",
|
||||||
|
"public final class R {"
|
||||||
|
);
|
||||||
|
|
||||||
|
my $class = "";
|
||||||
|
|
||||||
|
foreach my $line (@$r_txt) {
|
||||||
|
my @info = split(/ /, $line, 4);
|
||||||
|
$info[1] =~ s/[^0-9a-zA-Z]/_/;
|
||||||
|
|
||||||
|
my $colon = rindex($info[2], ':');
|
||||||
|
if ($colon >= 0) {
|
||||||
|
$info[2] = substr($info[2], $colon + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info[1] ne $class) {
|
||||||
|
push(@out, "\t}") if (length($class) > 0);
|
||||||
|
|
||||||
|
$class = $info[1];
|
||||||
|
push(@out, "\tpublic static final class $class {");
|
||||||
|
}
|
||||||
|
|
||||||
|
push(@out, "\t\tpublic static final ${info[0]} ${info[2]}=${info[3]};");
|
||||||
|
}
|
||||||
|
|
||||||
|
push(@out, ("\t}", "}", ""));
|
||||||
|
return \@out;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The only reason this script cares about the AndroidManifest.xml file (found inside every APK and AAR)
|
||||||
|
# is so that it can consistently find the name of the package, which is what this function does
|
||||||
|
|
||||||
|
sub get_package_from_manifest {
|
||||||
|
my $path = shift;
|
||||||
|
|
||||||
|
if (!-f $path) {
|
||||||
|
print("Could not find manifest file $path");
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
open(my $fh, '<', $path);
|
||||||
|
read($fh, my $manifest, -s $fh);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
if ($manifest =~ /package=["']([^"']+)/g) {
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print("Could not find a suitable package name inside AndroidManifest.xml\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function creates an R.txt file based on the resources in the 'res' folder of the main project,
|
||||||
|
# which is later turned into R.java and finally R.jar.
|
||||||
|
# Essentially, each values XML is scanned for strings or other values defined on a particular line,
|
||||||
|
# and everything else is just scanned for its file name.
|
||||||
|
# The ID that each resource is given here is unimportant other than that it needs to be unique merely within this generated document.
|
||||||
|
# All resource IDs (including these ones) get overwritten later.
|
||||||
|
|
||||||
|
sub gen_proj_rtxt {
|
||||||
|
# UPDATE 12/09/22: 'resources' and 'layout_id_defs' are now hashmaps, to allow for multiple versions of the same resource to be deduplicated.
|
||||||
|
# All that matters is that one version is written to R.txt, since this is ultimately how resources are accessed from code.
|
||||||
|
my %resources = ();
|
||||||
|
my $type_idx = 0;
|
||||||
|
|
||||||
|
# The meaning of "id" here is a type, not a number that is used to identify a resource
|
||||||
|
# We create a separate hashmap and push it to the main r_txt at the end to work around the limitations of our gen_rjava() implementation.
|
||||||
|
my %layout_id_defs = ();
|
||||||
|
|
||||||
|
foreach my $dir (<res/*>) {
|
||||||
|
my $sub_idx = 0;
|
||||||
|
|
||||||
|
# If this type-folder is a values folder
|
||||||
|
if ($dir =~ /values/) {
|
||||||
|
foreach my $f (<$dir/*.xml>) {
|
||||||
|
open(my $fh, '<', $f);
|
||||||
|
foreach (<$fh>) {
|
||||||
|
if ($_ =~ /<([^ ]+).+name="([^"]+)"/) {
|
||||||
|
$resources{"$1 $2"} = sprintf('0x7f%02x%04x', $type_idx, $sub_idx);
|
||||||
|
$sub_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close($fh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# 4 == length("res/")
|
||||||
|
my $type = substr($dir, 4);
|
||||||
|
my $len = length($dir) + 1;
|
||||||
|
|
||||||
|
foreach my $f (<$dir/*>) {
|
||||||
|
# Some XML files (especially layout files) contain resource definitions under the type "id"
|
||||||
|
if (substr($f, -4) eq ".xml") {
|
||||||
|
open(my $fh, '<', $f);
|
||||||
|
foreach (<$fh>) {
|
||||||
|
if ($_ =~ /@\+id\/([^"]+)/) {
|
||||||
|
$layout_id_defs{$1} = sprintf('0x7f%02x%04x', $type_idx, $sub_idx);
|
||||||
|
$sub_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close($fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $dot_idx = index($f, ".", $len);
|
||||||
|
my $name = ($dot_idx < 0) ? substr($f, $len) : substr($f, $len, $dot_idx - $len);
|
||||||
|
|
||||||
|
$resources{"$type $name"} = sprintf('0x7f%02x%04x', $type_idx, $sub_idx);
|
||||||
|
$sub_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$type_idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @r_txt = ();
|
||||||
|
|
||||||
|
my @resource_keys = sort(keys(%resources));
|
||||||
|
foreach (@resource_keys) {
|
||||||
|
push(@r_txt, "int " . $_ . " " . $resources{$_});
|
||||||
|
}
|
||||||
|
|
||||||
|
my @layout_keys = sort(keys(%layout_id_defs));
|
||||||
|
foreach (@layout_keys) {
|
||||||
|
push(@r_txt, "int id " . $_ . " " . $layout_id_defs{$_});
|
||||||
|
}
|
||||||
|
|
||||||
|
open(my $fh, '>', "build/R.txt");
|
||||||
|
print $fh join("\n", @r_txt);
|
||||||
|
close($fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
# This is the big one. This is where all resource IDs get overwritten.
|
||||||
|
# When AAPT2 links all resources from all the libraries (and the main project) together, it reallocates all IDs so that they are unique.
|
||||||
|
# We take the new list of IDs and apply it to each package's resource listing, which AAPT2 doesn't do for us.
|
||||||
|
# After this, there should be no resource collisions at app runtime.
|
||||||
|
|
||||||
|
sub update_res_ids {
|
||||||
|
my $ids = shift;
|
||||||
|
my $r_list = shift;
|
||||||
|
|
||||||
|
my @table = (); # list of offsets to the provided files inside 'blob'
|
||||||
|
my $fmt = ""; # format string to pack the list of files into a single blob
|
||||||
|
my @files = ();
|
||||||
|
|
||||||
|
# Load each R.txt
|
||||||
|
my $size = 0;
|
||||||
|
foreach (@$r_list) {
|
||||||
|
open(my $fh, '<:raw', $_);
|
||||||
|
my $s = -s $fh;
|
||||||
|
read($fh, my $r, $s);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
push(@table, $size);
|
||||||
|
$fmt .= "a$s ";
|
||||||
|
push(@files, $r);
|
||||||
|
$size += $s;
|
||||||
|
}
|
||||||
|
push(@table, $size);
|
||||||
|
|
||||||
|
# Make a copy of each R.txt and embed it into one homogenous string. This is likely faster than scanning each file individually.
|
||||||
|
my $blob = pack($fmt, @files);
|
||||||
|
|
||||||
|
# Make an index of replacements to happen later. This means there (shouldn't) be any search-replace ordering issues.
|
||||||
|
my @repl_list = ();
|
||||||
|
|
||||||
|
# For each line in the AAPT2 'ids.txt' output
|
||||||
|
foreach (@$ids) {
|
||||||
|
# Hard-coded 10 == length("0xnnnnnnnn"), the 32-bit hex number scheme that IDs use in text form
|
||||||
|
my $new_id = substr($_, -10);
|
||||||
|
|
||||||
|
my $nm_start = index($_, ':') + 1;
|
||||||
|
my $nm_end = index($_, ' ') + 1;
|
||||||
|
|
||||||
|
# 'name' will look like "type variable "
|
||||||
|
my $name = substr($_, $nm_start, $nm_end - $nm_start);
|
||||||
|
$name =~ s/\// /;
|
||||||
|
|
||||||
|
# For each instance where 'name' gets defined as a single ID:
|
||||||
|
my $name_reg = qr/$name(0x[0-9a-fA-F]+)/;
|
||||||
|
while ($blob =~ /$name_reg/g) {
|
||||||
|
my $match_len = length($1);
|
||||||
|
my $off = (pos $blob) - $match_len;
|
||||||
|
next if ($off < 0);
|
||||||
|
|
||||||
|
# If the ID is not a complete ID (likely 0x0), just mark a single replacement
|
||||||
|
if ($match_len != 10) {
|
||||||
|
push(@repl_list, {"off" => $off, "len" => $match_len, "new" => $new_id});
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find the current file
|
||||||
|
my $file_idx = 0;
|
||||||
|
$file_idx++ while ($table[$file_idx] < $off);
|
||||||
|
$file_idx--;
|
||||||
|
|
||||||
|
# Find all instances of the old ID for this new ID so we can replace them all
|
||||||
|
my $id_reg = qr/$1/;
|
||||||
|
while ($files[$file_idx] =~ /$id_reg/g) {
|
||||||
|
my $pos = (pos $files[$file_idx]) - $match_len;
|
||||||
|
next if ($pos < 0);
|
||||||
|
|
||||||
|
push(@repl_list, {"off" => $pos + $table[$file_idx], "len" => 10, "new" => $new_id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Since @ids (from ids.txt by AAPT2 link) is not sorted in a convenient order, we sort the replacement list here
|
||||||
|
# so that for each offset, the necessary displacement can be calculated linearly
|
||||||
|
my @replacements = sort { $a->{"off"} <=> $b->{"off"} } @repl_list;
|
||||||
|
|
||||||
|
my $n_repl = @replacements;
|
||||||
|
my $file_idx = 0;
|
||||||
|
my $disp = 0;
|
||||||
|
|
||||||
|
# This assumes that at least one replacement is needed in each file
|
||||||
|
for (my $i = 0; $i < $n_repl; $i++) {
|
||||||
|
my $repl = $replacements[$i];
|
||||||
|
my $off = $repl->{"off"};
|
||||||
|
my $len = $repl->{"len"};
|
||||||
|
|
||||||
|
# We need to update the table of file offsets so that we can write the correct range of bytes to the intended file later
|
||||||
|
while ($off > $table[$file_idx + 1]) {
|
||||||
|
$file_idx++;
|
||||||
|
$table[$file_idx] += $disp;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Actually replace the old ID with the new one.
|
||||||
|
# The key here is that the new ID may not necessarily be the same length as the old one,
|
||||||
|
# so everything after this replacement may get shifted up/down.
|
||||||
|
# A displacement is calculated as we go so that the current offset is always up to date.
|
||||||
|
|
||||||
|
substr($blob, $off + $disp, $len) = $repl->{"new"};
|
||||||
|
$disp += 10 - $len; # disp += length($repl->{"new"}) - $len
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure the last file has its size corrected as well
|
||||||
|
$table[-1] += $disp;
|
||||||
|
|
||||||
|
# Overwrite all the R.txts
|
||||||
|
my $idx = 0;
|
||||||
|
foreach (@$r_list) {
|
||||||
|
open(my $fh, '>', $_);
|
||||||
|
my $len = $table[$idx+1] - $table[$idx];
|
||||||
|
print $fh substr($blob, $table[$idx], $len);
|
||||||
|
close($fh);
|
||||||
|
$idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# This function iterates over every AAR, finds its R.txt, generates R.java and compiles it,
|
||||||
|
# placing the resulting .class files inside the already extracted classes folder for the current package.
|
||||||
|
# This means when the library is properly compiled into a JAR later, it knows how to access its own resources.
|
||||||
|
|
||||||
|
sub gen_libs_rjava {
|
||||||
|
my $dir = "$LIB_RES_DIR/r_java";
|
||||||
|
my @rjava_list = ();
|
||||||
|
|
||||||
|
foreach (<lib/*.aar>) {
|
||||||
|
my $name = substr($_, 4, -4);
|
||||||
|
|
||||||
|
my $in_path = "$LIB_RES_DIR/${name}_R.txt";
|
||||||
|
|
||||||
|
if (!-f $in_path) {
|
||||||
|
print("No resources file for $name, skipping...\n");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
open(my $fh, '<', $in_path);
|
||||||
|
chomp(my @r_txt = <$fh>);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
# skip this library if the resources index is empty
|
||||||
|
if (@r_txt <= 0) {
|
||||||
|
print("R.txt for $name is missing, skipping...\n");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $package = get_package_from_manifest("$LIB_RES_DIR/${name}_mf.xml");
|
||||||
|
if (!defined($package)) { # a bit harsh ;)
|
||||||
|
print("Could not find package name inside ${name}/AndroidManifest.xml, skipping...\n");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $out_path = "$dir/$name";
|
||||||
|
mkdir($out_path) if (!-d $out_path);
|
||||||
|
|
||||||
|
my $r_java = gen_rjava($package, \@r_txt);
|
||||||
|
|
||||||
|
$out_path .= "/R.java";
|
||||||
|
push(@rjava_list, $out_path);
|
||||||
|
|
||||||
|
open($fh, '>', $out_path);
|
||||||
|
print $fh join("\n", @$r_java);
|
||||||
|
close($fh);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \@rjava_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Entry-point
|
||||||
|
|
||||||
|
if (-d "lib" && not (-d $LIB_RES_DIR && -d $LIB_CLASS_DIR)) {
|
||||||
|
print(
|
||||||
|
"This stage depends on library resources already being compiled.\n",
|
||||||
|
"Run export-libs.pl first.\n"
|
||||||
|
);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir("build") if (!-d "build");
|
||||||
|
|
||||||
|
my $aapt2_res = "build/res.zip";
|
||||||
|
|
||||||
|
if (-d "lib") {
|
||||||
|
print("Compiling library resources...\n");
|
||||||
|
|
||||||
|
system("$TOOLS_DIR/aapt2 compile -o build/res_libs.zip --dir lib/res/res");
|
||||||
|
exit if ($? != 0);
|
||||||
|
|
||||||
|
$aapt2_res = "build/res_libs.zip " . $aapt2_res;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Compiling project resources...\n");
|
||||||
|
|
||||||
|
system("$TOOLS_DIR/aapt2 compile -o build/res.zip --dir res");
|
||||||
|
exit if ($? != 0);
|
||||||
|
|
||||||
|
print("Linking resources...\n");
|
||||||
|
|
||||||
|
# This is what gives us the actual set of properly unique IDs
|
||||||
|
system("$TOOLS_DIR/aapt2 link -o build/unaligned.apk --manifest AndroidManifest.xml -I $PLATFORM_DIR/android.jar --emit-ids ids.txt $aapt2_res");
|
||||||
|
exit if ($? != 0);
|
||||||
|
|
||||||
|
# Load those unique IDs
|
||||||
|
open(my $fh, '<', "ids.txt");
|
||||||
|
chomp(my @ids = <$fh>);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
system("$CMD_DELETE ids.txt");
|
||||||
|
|
||||||
|
print("Generating project R.txt...\n");
|
||||||
|
|
||||||
|
gen_proj_rtxt();
|
||||||
|
|
||||||
|
print("Updating resource IDs...\n");
|
||||||
|
|
||||||
|
my @r_list = ("build/R.txt");
|
||||||
|
push(@r_list, <$LIB_RES_DIR/*_R.txt>);
|
||||||
|
|
||||||
|
update_res_ids(\@ids, \@r_list);
|
||||||
|
|
||||||
|
if (-d $LIB_RES_DIR && -d $LIB_CLASS_DIR) {
|
||||||
|
print("Generating library resource maps...\n");
|
||||||
|
mkdir("$LIB_RES_DIR/r_java") if (!-d "$LIB_RES_DIR/r_java");
|
||||||
|
my $rjava_list = gen_libs_rjava();
|
||||||
|
|
||||||
|
open(my $fh, '>', "rjava_list.txt");
|
||||||
|
print $fh join("\n", @$rjava_list);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
print("Compiling resource maps...\n");
|
||||||
|
mkdir("$LIB_RES_DIR/R") if (!-d "$LIB_RES_DIR/R");
|
||||||
|
|
||||||
|
system("$CMD_JAVAC -source 8 -target 8 -bootclasspath $PLATFORM_DIR/android.jar -d $LIB_RES_DIR/R \@rjava_list.txt");
|
||||||
|
exit if ($? != 0);
|
||||||
|
unlink("rjava_list.txt");
|
||||||
|
|
||||||
|
system("$CMD_JAR --create --file build/libs_r.jar -C '$LIB_RES_DIR/R' .");
|
||||||
|
exit if ($? != 0);
|
||||||
|
|
||||||
|
print("Compiling resource maps into DEX bytecode...\n");
|
||||||
|
system("$CMD_D8 --intermediate build/libs_r.jar --classpath $PLATFORM_DIR/android.jar --output build");
|
||||||
|
exit if ($? != 0);
|
||||||
|
rename("build/classes.dex", "build/libs_r.dex");
|
||||||
|
|
||||||
|
print("Fusing library classes into a .JAR...\n");
|
||||||
|
system("$CMD_JAR --create --file build/libs.jar -C '$LIB_CLASS_DIR' .");
|
||||||
|
exit if ($? != 0);
|
||||||
|
|
||||||
|
print("Compiling library .JAR into DEX bytecode...\n");
|
||||||
|
system("$CMD_D8 --intermediate build/libs.jar --classpath $PLATFORM_DIR/android.jar --output build");
|
||||||
|
exit if ($? != 0);
|
||||||
|
rename("build/classes.dex", "build/libs.dex");
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Generating project R.java...\n");
|
||||||
|
|
||||||
|
my $pkg = get_package_from_manifest("AndroidManifest.xml");
|
||||||
|
exit if (!defined($pkg));
|
||||||
|
|
||||||
|
open($fh, '<', "build/R.txt");
|
||||||
|
chomp(my @r_txt = <$fh>);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
my $r_java = gen_rjava($pkg, \@r_txt);
|
||||||
|
|
||||||
|
open($fh, '>', "build/R.java");
|
||||||
|
print $fh join("\n", @$r_java);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
print("Compiling project R.java...\n");
|
||||||
|
|
||||||
|
mkdir("build/R") if (!-d "build/R");
|
||||||
|
|
||||||
|
system("$CMD_JAVAC -source 8 -target 8 -bootclasspath $PLATFORM_DIR/android.jar build/R.java -d build/R");
|
||||||
|
exit if ($? != 0);
|
||||||
|
|
||||||
|
system("$CMD_JAR --create --file build/R.jar -C build/R .");
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script makes the following assumptions:
|
||||||
|
# 1) You have a local copy of the Android SDK
|
||||||
|
# 2) You have an installed copy of the Java Development Kit (JDK)
|
||||||
|
# 3) If you are using AAR libraries (such as the AndroidX suite), you have copied/downloaded them to the lib directory and have run export-libs.pl then link.pl
|
||||||
|
# 4) You have already created a KeyStore file using keytool (comes with the JRE/JDK)
|
||||||
|
|
||||||
|
source includes.sh
|
||||||
|
|
||||||
|
SEP=":"
|
||||||
|
OS=`uname -s`
|
||||||
|
[[ $OS =~ "CYGWIN" || $OS =~ "MINGW" || $OS =~ "MSYS" ]] && SEP=";"
|
||||||
|
|
||||||
|
if [ ! -d "build" ]; then
|
||||||
|
mkdir build
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Cleaning build...
|
||||||
|
|
||||||
|
# Deletes all folders and APK files inside the build folder
|
||||||
|
$CMD_DELETE build/*.apk build/*/ 2> /dev/null
|
||||||
|
|
||||||
|
MF=`cat AndroidManifest.xml`
|
||||||
|
TERM="package=[\'\"]([a-z0-9.]+)"
|
||||||
|
package_path=""
|
||||||
|
|
||||||
|
if [[ "$MF" =~ $TERM ]]
|
||||||
|
then
|
||||||
|
package="${BASH_REMATCH[1]}"
|
||||||
|
package_path=${package//\./\/}
|
||||||
|
else
|
||||||
|
echo Could not find a suitable package name inside AndroidManifest.xml
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Compiling project source...
|
||||||
|
|
||||||
|
java_list=`$CMD_FIND src -name "*.java"`
|
||||||
|
kt_list=`$CMD_FIND src -name "*.kt"`
|
||||||
|
|
||||||
|
# If string length of java_list > 2 then we've got some Java source
|
||||||
|
# I picked '2' in case newlines bump it up from 0, though it's likely overkill
|
||||||
|
found_src=0
|
||||||
|
if [ ${#java_list} -gt 2 ]; then
|
||||||
|
jars=""
|
||||||
|
[ -f "build/R.jar" ] && jars+="build/R.jar${SEP}"
|
||||||
|
[ -f "build/libs.jar" ] && jars+="build/libs.jar${SEP}"
|
||||||
|
jars+="$PLATFORM_DIR/android.jar"
|
||||||
|
|
||||||
|
$CMD_JAVAC -source 11 -target 11 -classpath $jars -d build $java_list || exit
|
||||||
|
found_src=1
|
||||||
|
fi
|
||||||
|
if [ ${#kt_list} -gt 2 ]; then
|
||||||
|
$CMD_KOTLINC -d build -cp "build/R.jar${SEP}build/libs.jar${SEP}$PLATFORM_DIR/android.jar" -jvm-target 1.8 $kt_list || exit
|
||||||
|
found_src=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#if (( ! $found_src )); then
|
||||||
|
# echo No project sources were found in the 'src' folder.
|
||||||
|
# exit
|
||||||
|
#fi
|
||||||
|
|
||||||
|
echo Compiling classes into DEX bytecode...
|
||||||
|
|
||||||
|
dex_list=""
|
||||||
|
[ -f "build/libs.dex" ] && dex_list+=" build/libs.dex"
|
||||||
|
[ -f "build/libs_r.dex" ] && dex_list+=" build/libs_r.dex"
|
||||||
|
[ -f "build/kotlin.dex" ] && dex_list+=" build/kotlin.dex"
|
||||||
|
class_list=""
|
||||||
|
[ -d "build/$package_path" ] && class_list="build/$package_path/*"
|
||||||
|
$CMD_D8 --classpath $PLATFORM_DIR/android.jar $dex_list $class_list --output build || exit
|
||||||
|
|
||||||
|
echo Creating APK...
|
||||||
|
|
||||||
|
res=""
|
||||||
|
[ -f "build/res.zip" ] && res+="build/res.zip"
|
||||||
|
[ -f "build/res_libs.zip" ] && res+=" build/res_libs.zip"
|
||||||
|
$TOOLS_DIR/aapt2 link -o build/unaligned.apk --manifest AndroidManifest.xml -I $PLATFORM_DIR/android.jar --emit-ids ids.txt $res || exit
|
||||||
|
|
||||||
|
# Pack the DEX file into a new APK file
|
||||||
|
cd build
|
||||||
|
$CMD_7Z a -tzip unaligned.apk classes.dex > /dev/null
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
for t in ${TARGET_ARCHES[@]}; do
|
||||||
|
if [ -d $t ]; then
|
||||||
|
$CMD_7Z a -tzip build/unaligned.apk $t > /dev/null
|
||||||
|
$CMD_7Z rn -tzip build/unaligned.apk $t lib/$t > /dev/null
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Align the APK
|
||||||
|
# I've seen the next step and this one be in the other order, but the Android reference site says it should be this way...
|
||||||
|
$TOOLS_DIR/zipalign -f 4 build/unaligned.apk build/aligned.apk || exit
|
||||||
|
|
||||||
|
echo Signing APK...
|
||||||
|
|
||||||
|
# Sign the APK
|
||||||
|
$JAR_TOOLS/apksigner.jar sign --ks $KEYSTORE --ks-pass "pass:$KS_PASS" --min-sdk-version $API_LEVEL_MIN --out app.apk build/aligned.apk
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Tiny Android Template
|
||||||
|
|
||||||
|
*For Android projects written in Kotlin and/or Java, using the latest AndroidX libraries*
|
||||||
|
|
||||||
|
The purpose of this template is to give people the ability to write Android apps without having to use Android Studio or Gradle.
|
||||||
|
When I picked up Android dev for the first time, I was struck by how frustratingly slow and janky these tools were to use,
|
||||||
|
and that they seemed to only run at an acceptable pace on machines designed for gaming.
|
||||||
|
However, I still wanted to write apps for Android, so I developed this template so I could continue my work without having to use an IDE or external build system.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- Java Development Kit (JDK)
|
||||||
|
- Kotlin Compiler ***(optional)***
|
||||||
|
- Android SDK
|
||||||
|
- 7-Zip
|
||||||
|
- Bash & Perl (Cygwin/MSYS if on Windows)
|
||||||
|
|
||||||
|
### Does Not Require
|
||||||
|
- Android Studio
|
||||||
|
- Gradle
|
||||||
|
- Apache Maven / Ant
|
||||||
|
- Any external build system
|
||||||
|
|
||||||
|
## Getting the Android SDK
|
||||||
|
|
||||||
|
At the time of writing, [https://dl.google.com/android/repository/repository2-1.xml] contains a list of links to packages that form the Android SDK.
|
||||||
|
The only required SDK packages for compilation are `build-tools_<version>-<os>.zip` and `platform_<version>.zip`.
|
||||||
|
If you wish to run native code with JNI, you'll also need `android-ndk-<version>-<os>.zip`.
|
||||||
|
For running the app remotely, you'll find `adb` inside `platform-tools_<version>-<os>.zip`.
|
||||||
|
|
||||||
|
To download the SDK packages, run `sdk-package-list.py`, which will generate `sdk-package-list.html` with links to all SDK downloads.
|
||||||
|
Alternatively, you can acquire packages manually by downloading the aforementioned xml file and append each package name to `https://dl.google.com/android/repository/`.
|
||||||
|
|
||||||
|
## Installing the Tools
|
||||||
|
|
||||||
|
1) Make sure you have 7-Zip, Java Development Kit (a superset of the Java Runtime Environment), Bash & Perl, and optionally the Kotlin compiler installed. These will all need to be accessible from your $PATH variable (see [https://en.wikipedia.org/wiki/PATH_(variable)]). If you're on Windows, you'll need Cygwin/MSYS to make use of Bash and Perl.
|
||||||
|
|
||||||
|
2) Copy all files from this repository into a separate folder. In the level above that folder, create another folder called `Sdk`.
|
||||||
|
|
||||||
|
3) Download the `build-tools` and `platform` Android SDK packages - see **"Getting the Android SDK"** above for details. Extract the contents of both archives (at the top level) into the `Sdk` folder.
|
||||||
|
|
||||||
|
4) Check the variables at the top of the `includes.sh`. Edit them to match the names of the folders that were just extracted.
|
||||||
|
|
||||||
|
## Selecting a template
|
||||||
|
|
||||||
|
This repository offers three templates: vanilla, JNI and AndroidX. Only the AndroidX template has dependencies.
|
||||||
|
To select one to start from, rename `src-<template>` to `src` and `res-<template>` to `res`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1) Prepare the Kotlin standard library - *Only necessary for Kotlin projects*
|
||||||
|
- `./kotlin-pre.sh`
|
||||||
|
- This will prepare a copy of the Kotlin standard library for your project in DEX form, which is required for a Kotlin app on Android.
|
||||||
|
|
||||||
|
2) Get library packages - *Only necessary if there are dependencies*
|
||||||
|
- `./get-packages.sh pkg-list.txt`
|
||||||
|
- This will retrieve AndroidX library packages from Google's Maven repository. The included `pkg-list.txt` contains the list of packages required for "Hello World".
|
||||||
|
|
||||||
|
3) Unpack & merge libraries - *Only necessary if there are dependencies*
|
||||||
|
- `./export-libs.pl`
|
||||||
|
- Combines and compiles library resources while resolving resource name merge conflicts. Essentially, your code and the libraries/packages you use have resources which effectively must share the same namespace. This script is the first step in the merging process.
|
||||||
|
|
||||||
|
4) Build libraries - *__REQUIRED__ unless there are no resources or libraries*
|
||||||
|
- `./link.pl`
|
||||||
|
- Links all resources, fixes library resource references and compiles library classes into DEX bytecode. Most of the work for creating the app is done here. The Android VM has an *interesting* method of locating and making use of resources; this script prepares project & library code and resources to match the expected layout/format.
|
||||||
|
|
||||||
|
5) Compile native code - *Only necessary for projects that use JNI*
|
||||||
|
- `./jni-compile.sh`
|
||||||
|
- If you plan to use JNI, you'll likely need to modify this script to suit your needs
|
||||||
|
|
||||||
|
6) Create APK (you will need a KeyStore file for this. See **"Notes"** for details.)
|
||||||
|
- `./make.sh`
|
||||||
|
- This step basically just assembles the files from previous steps into an APK file, while additionally signing the app.
|
||||||
|
|
||||||
|
7) Install and run the app on a real device using ADB
|
||||||
|
- `./run.sh`
|
||||||
|
- There is also a `logs.sh` script which dumps the ADB log to the console.
|
||||||
|
- On Linux, if `run.sh` or `logs.sh` fail with `user <user> is not in the plugdev group`:
|
||||||
|
- Ensure the plugdev group is created with `groupadd plugdev`
|
||||||
|
- Ensure the current user is part of the plugdev group with `sudo usermod -a -G plugdev <user>`
|
||||||
|
- Try logging out and logging in again
|
||||||
|
- If instead you get the error `missing udev rules? user is in the plugdev group`:
|
||||||
|
- Try killing the adb process with `kill -9 $(pidof adb)`
|
||||||
|
- Try unplugging and plugging in your device again
|
||||||
|
- Try adjusting the charging/USB options on your Android under the "Use USB for" section
|
||||||
|
|
||||||
|
If your list of libraries change, go to step 3.
|
||||||
|
|
||||||
|
If you create or delete (or possibly rename) any resources, go to step 4.
|
||||||
|
|
||||||
|
Otherwise, simply running `make.sh` should be enough to ensure that you have a fresh build.
|
||||||
|
|
||||||
|
The `make.sh` script will compile anything that's in the `src` folder.
|
||||||
|
To compile the Java version, simply rename the `src` folder to something else and rename `src-java` to `src`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
**You may need to change some configuration variables found at the top of each script**.
|
||||||
|
`kotlin-pre.sh` in particular relies on a hard-coded path which is system dependent.
|
||||||
|
Other examples include KeyStore password, Android SDK location and version, etc.
|
||||||
|
Most of these variables can be found in `includes.sh`.
|
||||||
|
|
||||||
|
If you're getting `attribute <thing> (aka <package>:<thing>) not found` errors in `link.pl`, try adding `<thing>` to `@DELETE_LIST` at the top of `export-libs.pl`.
|
||||||
|
This will ensure that any attributes that can't be defined without a library you don't have (eg. a support library) are (temporarily) deleted before linking library resources.
|
||||||
|
|
||||||
|
As long as your JDK version can target Java 8, this should work. Tested with OpenJDK 13.0.2.
|
||||||
|
|
||||||
|
You will need to make sure the `bin` directories for the JDK and for 7-Zip (and the Kotlin compiler if you're using Kotlin) are in the $PATH variable.
|
||||||
|
|
||||||
|
In order to build the APK, `apksigner` needs a KeyStore file. This can be generated with `keytool`, which comes with the JDK.
|
||||||
|
The following command generates a KeyStore file (keystore.jks) which is valid for 10000 days:
|
||||||
|
|
||||||
|
`keytool -genkeypair -keystore keystore.jks -keyalg RSA -keysize 2048 -validity 10000`
|
||||||
|
|
||||||
|
These scripts use the `d8` tool from the Android SDK (as opposed to `dx`). Thus, your build-tools version must be >= 28.0.1.
|
||||||
|
|
||||||
|
To delete the library cache in your project, simply delete the `lib` folder that was created, then run `get-packages.sh` again.
|
||||||
|
|
||||||
|
If you're using Linux/OS X/etc. and you're getting a `permission denied`-esque error, try using `chmod +x` on the `.sh` and `.pl` files in this repo.
|
||||||
|
|
||||||
|
This template is loosely based off [https://github.com/authmane512/android-project-template]
|
||||||
|
|
Loading…
Reference in New Issue