Explorar o código

Begin work on `odin package-android` command

gingerBill hai 5 meses
pai
achega
f13a075cd1
Modificáronse 4 ficheiros con 295 adicións e 255 borrados
  1. 111 106
      src/build_settings.cpp
  2. 0 146
      src/linker.cpp
  3. 21 3
      src/main.cpp
  4. 163 0
      src/package_android.cpp

+ 111 - 106
src/build_settings.cpp

@@ -209,15 +209,15 @@ enum BuildModeKind {
 enum CommandKind : u32 {
 	Command_run             = 1<<0,
 	Command_build           = 1<<1,
-	Command_check           = 1<<3,
-	Command_doc             = 1<<5,
-	Command_version         = 1<<6,
-	Command_test            = 1<<7,
+	Command_check           = 1<<2,
+	Command_doc             = 1<<3,
+	Command_version         = 1<<4,
+	Command_test            = 1<<5,
 	
-	Command_strip_semicolon = 1<<8,
-	Command_bug_report      = 1<<9,
+	Command_strip_semicolon = 1<<6,
+	Command_bug_report      = 1<<7,
 
-	Command_pkg_android     = 1<<16,
+	Command_package_android = 1<<8,
 
 	Command__does_check = Command_run|Command_build|Command_check|Command_doc|Command_test|Command_strip_semicolon,
 	Command__does_build = Command_run|Command_build|Command_test,
@@ -232,6 +232,8 @@ gb_global char const *odin_command_strings[32] = {
 	"version",
 	"test",
 	"strip-semicolon",
+	"",
+	"package-android",
 };
 
 
@@ -1489,6 +1491,107 @@ gb_internal bool has_ansi_terminal_colours(void) {
 	return build_context.has_ansi_terminal_colours && !json_errors();
 }
 
+gb_internal void init_android_values(bool with_sdk) {
+	auto *bc = &build_context;
+	{ // Android SDK/API Level
+		String default_level = str_lit("34");
+		if (!bc->minimum_os_version_string_given) {
+			bc->minimum_os_version_string = default_level;
+		}
+		BigInt level = {};
+		bool success = false;
+		big_int_from_string(&level, bc->minimum_os_version_string, &success);
+		if (!success) {
+			gb_printf_err("Warning: Invalid -minimum-os-version:%.*s for -subtarget:Android, defaulting to %.*s\n", LIT(bc->minimum_os_version_string), LIT(default_level));
+			bc->minimum_os_version_string = default_level;
+			big_int_from_string(&level, bc->minimum_os_version_string, &success);
+			GB_ASSERT(success);
+		}
+
+		i64 new_level = big_int_to_i64(&level);
+
+		if (new_level >= 21) {
+			bc->ODIN_ANDROID_API_LEVEL = cast(int)new_level;
+		} else {
+			gb_printf_err("Warning: Invalid -minimum-os-version:%.*s for -subtarget:Android, defaulting to %.*s\n", LIT(bc->minimum_os_version_string), LIT(default_level));
+			bc->ODIN_ANDROID_API_LEVEL = atoi(cast(char const *)default_level.text);
+		}
+	}
+	bc->ODIN_ANDROID_NDK           = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_NDK", permanent_allocator())), NIX_SEPARATOR_STRING);
+	bc->ODIN_ANDROID_NDK_TOOLCHAIN = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_NDK_TOOLCHAIN", permanent_allocator())), NIX_SEPARATOR_STRING);
+	bc->ODIN_ANDROID_SDK           = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_SDK", permanent_allocator())), NIX_SEPARATOR_STRING);
+
+	#if defined(GB_SYSTEM_WINDOWS)
+		if (bc->ODIN_ANDROID_SDK.len == 0) {
+			bc->ODIN_ANDROID_SDK = normalize_path(permanent_allocator(),
+				path_to_fullpath(permanent_allocator(), str_lit("%LocalAppData%/Android/Sdk"), nullptr),
+				NIX_SEPARATOR_STRING);
+		}
+	#endif
+
+	if (bc->ODIN_ANDROID_NDK.len != 0 && bc->ODIN_ANDROID_NDK_TOOLCHAIN.len == 0) {
+		String arch = str_lit("x86_64");
+		#if defined (GB_CPU_ARM)
+			// TODO(bill): this is a complete guess
+			arch = str_lit("aarch64");
+		#endif
+		#if defined(GB_SYSTEM_WINDOWS)
+			bc->ODIN_ANDROID_NDK_TOOLCHAIN = concatenate4_strings(temporary_allocator(), bc->ODIN_ANDROID_NDK, str_lit("toolchains/llvm/prebuilt/"), str_lit("windows-"), arch);
+		#elif defined(GB_SYSTEM_OSX)
+			// TODO(bill): is this name even correct?
+			bc->ODIN_ANDROID_NDK_TOOLCHAIN = concatenate4_strings(temporary_allocator(), bc->ODIN_ANDROID_NDK, str_lit("toolchains/llvm/prebuilt/"), str_lit("darwin-"), arch);
+		#elif defined(GB_SYSTEM_LINUX)
+			bc->ODIN_ANDROID_NDK_TOOLCHAIN = concatenate4_strings(temporary_allocator(), bc->ODIN_ANDROID_NDK, str_lit("toolchains/llvm/prebuilt/"), str_lit("linux-"), arch);
+		#endif
+
+		bc->ODIN_ANDROID_NDK_TOOLCHAIN = normalize_path(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, NIX_SEPARATOR_STRING);
+	}
+
+	if (bc->ODIN_ANDROID_NDK.len == 0)  {
+		gb_printf_err("Error: ODIN_ANDROID_NDK not set");
+		gb_exit(1);
+
+	}
+
+	if (bc->ODIN_ANDROID_NDK_TOOLCHAIN.len == 0)  {
+		gb_printf_err("Error: ODIN_ANDROID_NDK not set");
+		gb_exit(1);
+	}
+
+	bc->ODIN_ANDROID_NDK_TOOLCHAIN_LIB = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, str_lit("sysroot/usr/lib/aarch64-linux-android/"));
+
+	char buf[32] = {};
+	gb_snprintf(buf, gb_size_of(buf), "%d/", bc->ODIN_ANDROID_API_LEVEL);
+	bc->ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN_LIB, make_string_c(buf));
+
+	bc->ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, str_lit("sysroot/"));
+
+
+	bc->ODIN_ANDROID_JAR_SIGNER = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_JAR_SIGNER", permanent_allocator())), NIX_SEPARATOR_STRING);
+	if (with_sdk) {
+		if (bc->ODIN_ANDROID_SDK.len == 0)  {
+			gb_printf_err("Error: ODIN_ANDROID_SDK not set, which is required for -build-mode:executable for -subtarget:android");
+			gb_exit(1);
+		}
+		if (bc->ODIN_ANDROID_JAR_SIGNER.len == 0) {
+			gb_printf_err("Error: ODIN_ANDROID_JAR_SIGNER not set, which is required for -build-mode:executable for -subtarget:android");
+			gb_exit(1);
+		}
+		if (bc->android_keystore.len == 0) {
+			gb_printf_err("Error: -android-keystore:<string> has not been set\n");
+			gb_exit(1);
+		}
+		if (bc->android_keystore_alias.len == 0) {
+			gb_printf_err("Error: -android-keystore_alias:<string> has not been set\n");
+			gb_exit(1);
+		}
+		if (bc->android_manifest.len == 0) {
+			gb_printf_err("Error: -android-manifest:<string> has not been set\n");
+			gb_exit(1);
+		}
+	}
+}
+
 gb_internal bool has_asm_extension(String const &path) {
 	String ext = path_extension(path);
 	if (ext == ".asm") {
@@ -1744,105 +1847,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 			bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string);
 		}
 	} else if (selected_subtarget == Subtarget_Android) {
-
-		{ // Android SDK/API Level
-			String default_level = str_lit("34");
-			if (!bc->minimum_os_version_string_given) {
-				bc->minimum_os_version_string = default_level;
-			}
-			BigInt level = {};
-			bool success = false;
-			big_int_from_string(&level, bc->minimum_os_version_string, &success);
-			if (!success) {
-				gb_printf_err("Warning: Invalid -minimum-os-version:%.*s for -subtarget:Android, defaulting to %.*s\n", LIT(bc->minimum_os_version_string), LIT(default_level));
-				bc->minimum_os_version_string = default_level;
-				big_int_from_string(&level, bc->minimum_os_version_string, &success);
-				GB_ASSERT(success);
-			}
-
-			i64 new_level = big_int_to_i64(&level);
-
-			if (new_level >= 21) {
-				bc->ODIN_ANDROID_API_LEVEL = cast(int)new_level;
-			} else {
-				gb_printf_err("Warning: Invalid -minimum-os-version:%.*s for -subtarget:Android, defaulting to %.*s\n", LIT(bc->minimum_os_version_string), LIT(default_level));
-				bc->ODIN_ANDROID_API_LEVEL = atoi(cast(char const *)default_level.text);
-			}
-		}
-		bc->ODIN_ANDROID_NDK           = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_NDK", permanent_allocator())), NIX_SEPARATOR_STRING);
-		bc->ODIN_ANDROID_NDK_TOOLCHAIN = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_NDK_TOOLCHAIN", permanent_allocator())), NIX_SEPARATOR_STRING);
-		bc->ODIN_ANDROID_SDK           = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_SDK", permanent_allocator())), NIX_SEPARATOR_STRING);
-
-		#if defined(GB_SYSTEM_WINDOWS)
-			if (bc->ODIN_ANDROID_SDK.len == 0) {
-				bc->ODIN_ANDROID_SDK = normalize_path(permanent_allocator(),
-					path_to_fullpath(permanent_allocator(), str_lit("%LocalAppData%/Android/Sdk"), nullptr),
-					NIX_SEPARATOR_STRING);
-			}
-		#endif
-
-		if (bc->ODIN_ANDROID_NDK.len != 0 && bc->ODIN_ANDROID_NDK_TOOLCHAIN.len == 0) {
-			String arch = str_lit("x86_64");
-			#if defined (GB_CPU_ARM)
-				// TODO(bill): this is a complete guess
-				arch = str_lit("aarch64");
-			#endif
-			#if defined(GB_SYSTEM_WINDOWS)
-				bc->ODIN_ANDROID_NDK_TOOLCHAIN = concatenate4_strings(temporary_allocator(), bc->ODIN_ANDROID_NDK, str_lit("toolchains/llvm/prebuilt/"), str_lit("windows-"), arch);
-			#elif defined(GB_SYSTEM_OSX)
-				// TODO(bill): is this name even correct?
-				bc->ODIN_ANDROID_NDK_TOOLCHAIN = concatenate4_strings(temporary_allocator(), bc->ODIN_ANDROID_NDK, str_lit("toolchains/llvm/prebuilt/"), str_lit("darwin-"), arch);
-			#elif defined(GB_SYSTEM_LINUX)
-				bc->ODIN_ANDROID_NDK_TOOLCHAIN = concatenate4_strings(temporary_allocator(), bc->ODIN_ANDROID_NDK, str_lit("toolchains/llvm/prebuilt/"), str_lit("linux-"), arch);
-			#endif
-
-			bc->ODIN_ANDROID_NDK_TOOLCHAIN = normalize_path(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, NIX_SEPARATOR_STRING);
-		}
-
-		if (bc->ODIN_ANDROID_NDK.len == 0)  {
-			gb_printf_err("Error: ODIN_ANDROID_NDK not set");
-			gb_exit(1);
-
-		}
-
-		if (bc->ODIN_ANDROID_NDK_TOOLCHAIN.len == 0)  {
-			gb_printf_err("Error: ODIN_ANDROID_NDK not set");
-			gb_exit(1);
-		}
-
-		bc->ODIN_ANDROID_NDK_TOOLCHAIN_LIB = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, str_lit("sysroot/usr/lib/aarch64-linux-android/"));
-
-		char buf[32] = {};
-		gb_snprintf(buf, gb_size_of(buf), "%d/", bc->ODIN_ANDROID_API_LEVEL);
-		bc->ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN_LIB, make_string_c(buf));
-
-		bc->ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, str_lit("sysroot/"));
-
-
-		bc->ODIN_ANDROID_JAR_SIGNER = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_JAR_SIGNER", permanent_allocator())), NIX_SEPARATOR_STRING);
-		if (bc->build_mode == BuildMode_Executable) {
-			if (bc->ODIN_ANDROID_SDK.len == 0)  {
-				gb_printf_err("Error: ODIN_ANDROID_SDK not set, which is required for -build-mode:executable for -subtarget:android");
-				gb_exit(1);
-			}
-			if (bc->ODIN_ANDROID_JAR_SIGNER.len == 0) {
-				gb_printf_err("Error: ODIN_ANDROID_JAR_SIGNER not set, which is required for -build-mode:executable for -subtarget:android");
-				gb_exit(1);
-			}
-			if (bc->android_keystore.len == 0) {
-				gb_printf_err("Error: -android-keystore:<string> has not been set\n");
-				gb_exit(1);
-			}
-			if (bc->android_keystore_alias.len == 0) {
-				gb_printf_err("Error: -android-keystore_alias:<string> has not been set\n");
-				gb_exit(1);
-			}
-			if (bc->android_manifest.len == 0) {
-				gb_printf_err("Error: -android-manifest:<string> has not been set\n");
-				gb_exit(1);
-			}
-		}
-
+		init_android_values(bc->build_mode == BuildMode_Executable);
 	}
 
 	if (!bc->custom_optimization_level) {

+ 0 - 146
src/linker.cpp

@@ -868,152 +868,6 @@ try_cross_linking:;
 					return result;
 				}
 			}
-			if (is_android && build_context.build_mode == BuildMode_Executable) {
-				String android_sdk_build_tools = concatenate3_strings(temporary_allocator(),
-					build_context.ODIN_ANDROID_SDK, str_lit("build-tools"), NIX_SEPARATOR_STRING);
-
-				Array<FileInfo> list = {};
-				ReadDirectoryError rd_err = read_directory(android_sdk_build_tools, &list);
-				defer (array_free(&list));
-
-				switch (rd_err) {
-				case ReadDirectory_InvalidPath:
-					gb_printf_err("Invalid path: %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				case ReadDirectory_NotExists:
-					gb_printf_err("Path does not exist: %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				case ReadDirectory_Permission:
-					gb_printf_err("Unknown error whilst reading path %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				case ReadDirectory_NotDir:
-					gb_printf_err("Expected a directory for a package, got a file: %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				case ReadDirectory_Empty:
-					gb_printf_err("Empty directory: %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				case ReadDirectory_Unknown:
-					gb_printf_err("Unknown error whilst reading path %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				}
-
-				auto possible_valid_dirs = array_make<FileInfo>(heap_allocator(), 0, list.count);
-				defer (array_free(&possible_valid_dirs));
-
-
-				for (FileInfo fi : list) if (fi.is_dir) {
-					bool all_numbers = true;
-					for (isize i = 0; i < fi.name.len; i++) {
-						u8 c = fi.name[i];
-						if ('0' <= c && c <= '9') {
-							// true
-						} else if (i == 0) {
-							all_numbers = false;
-						} else if (c == '.') {
-							break;
-						} else {
-							all_numbers = false;
-						}
-					}
-
-					if (all_numbers) {
-						array_add(&possible_valid_dirs, fi);
-					}
-				}
-
-				if (possible_valid_dirs.count == 0) {
-					gb_printf_err("Unable to find any Android SDK/API Level in %.*s\n", LIT(android_sdk_build_tools));
-					return 1;
-				}
-
-				int *dir_numbers = gb_alloc_array(temporary_allocator(), int, possible_valid_dirs.count);
-
-				char buf[1024] = {};
-				for_array(i, possible_valid_dirs) {
-					FileInfo fi = possible_valid_dirs[i];
-					isize n = gb_min(gb_size_of(buf)-1, fi.name.len);
-					memcpy(buf, fi.name.text, n);
-					buf[n] = 0;
-
-					dir_numbers[i] = atoi(buf);
-				}
-
-				isize closest_number_idx = -1;
-				for (isize i = 0; i < possible_valid_dirs.count; i++) {
-					if (dir_numbers[i] >= ODIN_ANDROID_API_LEVEL) {
-						if (closest_number_idx < 0) {
-							closest_number_idx = i;
-						} else if (dir_numbers[i] < dir_numbers[closest_number_idx]) {
-							closest_number_idx = i;
-						}
-					}
-				}
-
-				if (closest_number_idx < 0) {
-					gb_printf_err("Unable to find any Android SDK/API Level in %.*s meeting the minimum API level of %d\n", LIT(android_sdk_build_tools), ODIN_ANDROID_API_LEVEL);
-					return 1;
-				}
-
-				String api_number = possible_valid_dirs[closest_number_idx].name;
-
-				android_sdk_build_tools = concatenate_strings(temporary_allocator(), android_sdk_build_tools, api_number);
-				String android_sdk_platforms = concatenate_strings(temporary_allocator(),
-					build_context.ODIN_ANDROID_SDK,
-					make_string_c(gb_bprintf("platforms/android-%d/", dir_numbers[closest_number_idx]))
-				);
-
-
-
-				android_sdk_build_tools = normalize_path(temporary_allocator(), android_sdk_build_tools, NIX_SEPARATOR_STRING);
-				android_sdk_platforms   = normalize_path(temporary_allocator(), android_sdk_platforms,   NIX_SEPARATOR_STRING);
-
-				gbString cmd = gb_string_make(heap_allocator(), "");
-				defer (gb_string_free(cmd));
-
-				TIME_SECTION("Android aapt");
-
-				String output_apk = path_remove_extension(output_filename);
-
-				cmd = gb_string_append_length(cmd, android_sdk_build_tools.text, android_sdk_build_tools.len);
-				cmd = gb_string_appendc(cmd, "aapt");
-				cmd = gb_string_appendc(cmd, " package -f");
-				cmd = gb_string_append_fmt(cmd, " -M \"%.*s\"", LIT(build_context.android_manifest));
-				cmd = gb_string_append_fmt(cmd, " -I \"%.*sandroid.jar\"", LIT(android_sdk_platforms));
-				cmd = gb_string_append_fmt(cmd, " -F \"%.*s.apk-build\"", LIT(output_apk));
-
-				result = system_exec_command_line_app("android-aapt", cmd);
-				if (result) {
-					return result;
-				}
-
-				TIME_SECTION("Android jarsigner");
-				gb_string_clear(cmd);
-
-				cmd = gb_string_append_length(cmd, build_context.ODIN_ANDROID_JAR_SIGNER.text, build_context.ODIN_ANDROID_JAR_SIGNER.len);
-				cmd = gb_string_append_fmt(cmd, " -storepass android -keystore \"%.*s\" \"%.*s.apk-build\" \"%.*s\"",
-					LIT(build_context.android_keystore),
-					LIT(output_apk),
-					LIT(build_context.android_keystore_alias)
-				);
-				result = system_exec_command_line_app("android-jarsigner", cmd);
-				if (result) {
-					return result;
-				}
-
-				TIME_SECTION("Android zipalign");
-				gb_string_clear(cmd);
-
-				cmd = gb_string_append_length(cmd, android_sdk_build_tools.text, android_sdk_build_tools.len);
-				cmd = gb_string_appendc(cmd, "zipalign");
-				cmd = gb_string_appendc(cmd, " -f 4");
-				cmd = gb_string_append_fmt(cmd, " \"%.*s.apk-build\" \"%.*s.apk\"", LIT(output_apk), LIT(output_apk));
-
-
-				result = system_exec_command_line_app("android-zipalign", cmd);
-				if (result) {
-					return result;
-				}
-			}
 		}
 	}
 

+ 21 - 3
src/main.cpp

@@ -74,6 +74,7 @@ gb_global Timings global_timings = {0};
 #include "cached.cpp"
 
 #include "linker.cpp"
+#include "package_android.cpp"
 
 #if defined(GB_SYSTEM_WINDOWS) && defined(ODIN_TILDE_BACKEND)
 #define ALLOW_TILDE 1
@@ -628,9 +629,9 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_Subsystem,               str_lit("subsystem"),                 BuildFlagParam_String,  Command__does_build);
 #endif
 
-	add_flag(&build_flags, BuildFlag_AndroidKeystore,         str_lit("android-keystore"),          BuildFlagParam_String,  Command__does_build);
-	add_flag(&build_flags, BuildFlag_AndroidKeystoreAlias,    str_lit("android-keystore-alias"),    BuildFlagParam_String,  Command__does_build);
-	add_flag(&build_flags, BuildFlag_AndroidManifest,         str_lit("android-manifest"),          BuildFlagParam_String,  Command__does_build);
+	add_flag(&build_flags, BuildFlag_AndroidKeystore,         str_lit("android-keystore"),          BuildFlagParam_String,  Command__does_build | Command_package_android);
+	add_flag(&build_flags, BuildFlag_AndroidKeystoreAlias,    str_lit("android-keystore-alias"),    BuildFlagParam_String,  Command__does_build | Command_package_android);
+	add_flag(&build_flags, BuildFlag_AndroidManifest,         str_lit("android-manifest"),          BuildFlagParam_String,  Command__does_build | Command_package_android);
 
 
 	GB_ASSERT(args.count >= 3);
@@ -2259,6 +2260,8 @@ gb_internal void print_show_help(String const arg0, String command, String optio
 	} else if (command == "strip-semicolon") {
 		print_usage_line(1, "strip-semicolon");
 		print_usage_line(2, "Parses and type checks .odin file(s) and then removes unneeded semicolons from the entire project.");
+	} else if (command == "package-android")  {
+		print_usage_line(1, "package-android   Packages directory in a specific layout as an APK");
 	}
 
 	bool doc             = command == "doc";
@@ -3322,6 +3325,13 @@ int main(int arg_count, char const **arg_ptr) {
 			print_show_help(args[0], args[1], args[2]);
 			return 0;
 		}
+	} else if (command == "package-android") {
+		if (args.count < 3) {
+			usage(args[0]);
+			return 1;
+		}
+		build_context.command_kind = Command_package_android;
+		init_filename = args[2];
 	} else if (command == "root") {
 		gb_printf("%.*s", LIT(odin_root_dir()));
 		return 0;
@@ -3360,6 +3370,10 @@ int main(int arg_count, char const **arg_ptr) {
 				if (init_filename == "-file") {
 					gb_printf_err("Did you mean `%.*s %.*s <filename.odin> -file`?\n", LIT(args[0]), LIT(command));
 				} else {
+					if (!gb_file_exists(cast(const char*)init_filename.text)) {
+						gb_printf_err("The file '%.*s' was not found.\n", LIT(init_filename));
+						return 1;
+					}
 					gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename));
 				}
 
@@ -3393,6 +3407,10 @@ int main(int arg_count, char const **arg_ptr) {
 		return 0;
 	}
 
+	if (command == "package-android") {
+		return package_android(args);
+	}
+
 	// NOTE(bill): add 'shared' directory if it is not already set
 	if (!find_library_collection_path(str_lit("shared"), nullptr)) {
 		add_library_collection(str_lit("shared"),

+ 163 - 0
src/package_android.cpp

@@ -0,0 +1,163 @@
+i32 package_android(Array<String> args) {
+	i32 result = 0;
+
+	init_android_values(/*with_sdk*/true);
+
+	int const ODIN_ANDROID_API_LEVEL = build_context.ODIN_ANDROID_API_LEVEL;
+
+	String ODIN_ANDROID_NDK                     = build_context.ODIN_ANDROID_NDK;
+	String ODIN_ANDROID_NDK_TOOLCHAIN           = build_context.ODIN_ANDROID_NDK_TOOLCHAIN;
+	String ODIN_ANDROID_NDK_TOOLCHAIN_LIB       = build_context.ODIN_ANDROID_NDK_TOOLCHAIN_LIB;
+	String ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL = build_context.ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL;
+	String ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT   = build_context.ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT;
+
+
+	String android_sdk_build_tools = concatenate3_strings(temporary_allocator(),
+		build_context.ODIN_ANDROID_SDK, str_lit("build-tools"), NIX_SEPARATOR_STRING);
+
+	Array<FileInfo> list = {};
+	ReadDirectoryError rd_err = read_directory(android_sdk_build_tools, &list);
+	defer (array_free(&list));
+
+	switch (rd_err) {
+	case ReadDirectory_InvalidPath:
+		gb_printf_err("Invalid path: %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	case ReadDirectory_NotExists:
+		gb_printf_err("Path does not exist: %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	case ReadDirectory_Permission:
+		gb_printf_err("Unknown error whilst reading path %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	case ReadDirectory_NotDir:
+		gb_printf_err("Expected a directory for a package, got a file: %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	case ReadDirectory_Empty:
+		gb_printf_err("Empty directory: %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	case ReadDirectory_Unknown:
+		gb_printf_err("Unknown error whilst reading path %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	}
+
+	auto possible_valid_dirs = array_make<FileInfo>(heap_allocator(), 0, list.count);
+	defer (array_free(&possible_valid_dirs));
+
+
+	for (FileInfo fi : list) if (fi.is_dir) {
+		bool all_numbers = true;
+		for (isize i = 0; i < fi.name.len; i++) {
+			u8 c = fi.name[i];
+			if ('0' <= c && c <= '9') {
+				// true
+			} else if (i == 0) {
+				all_numbers = false;
+			} else if (c == '.') {
+				break;
+			} else {
+				all_numbers = false;
+			}
+		}
+
+		if (all_numbers) {
+			array_add(&possible_valid_dirs, fi);
+		}
+	}
+
+	if (possible_valid_dirs.count == 0) {
+		gb_printf_err("Unable to find any Android SDK/API Level in %.*s\n", LIT(android_sdk_build_tools));
+		return 1;
+	}
+
+	int *dir_numbers = gb_alloc_array(temporary_allocator(), int, possible_valid_dirs.count);
+
+	char buf[1024] = {};
+	for_array(i, possible_valid_dirs) {
+		FileInfo fi = possible_valid_dirs[i];
+		isize n = gb_min(gb_size_of(buf)-1, fi.name.len);
+		memcpy(buf, fi.name.text, n);
+		buf[n] = 0;
+
+		dir_numbers[i] = atoi(buf);
+	}
+
+	isize closest_number_idx = -1;
+	for (isize i = 0; i < possible_valid_dirs.count; i++) {
+		if (dir_numbers[i] >= ODIN_ANDROID_API_LEVEL) {
+			if (closest_number_idx < 0) {
+				closest_number_idx = i;
+			} else if (dir_numbers[i] < dir_numbers[closest_number_idx]) {
+				closest_number_idx = i;
+			}
+		}
+	}
+
+	if (closest_number_idx < 0) {
+		gb_printf_err("Unable to find any Android SDK/API Level in %.*s meeting the minimum API level of %d\n", LIT(android_sdk_build_tools), ODIN_ANDROID_API_LEVEL);
+		return 1;
+	}
+
+	String api_number = possible_valid_dirs[closest_number_idx].name;
+
+	android_sdk_build_tools = concatenate_strings(temporary_allocator(), android_sdk_build_tools, api_number);
+	String android_sdk_platforms = concatenate_strings(temporary_allocator(),
+		build_context.ODIN_ANDROID_SDK,
+		make_string_c(gb_bprintf("platforms/android-%d/", dir_numbers[closest_number_idx]))
+	);
+
+
+
+	android_sdk_build_tools = normalize_path(temporary_allocator(), android_sdk_build_tools, NIX_SEPARATOR_STRING);
+	android_sdk_platforms   = normalize_path(temporary_allocator(), android_sdk_platforms,   NIX_SEPARATOR_STRING);
+
+	gbString cmd = gb_string_make(heap_allocator(), "");
+	defer (gb_string_free(cmd));
+
+	TIME_SECTION("Android aapt");
+
+	String output_filename = str_lit("test");
+
+	String output_apk = path_remove_extension(output_filename);
+
+	cmd = gb_string_append_length(cmd, android_sdk_build_tools.text, android_sdk_build_tools.len);
+	cmd = gb_string_appendc(cmd, "aapt");
+	cmd = gb_string_appendc(cmd, " package -f");
+	cmd = gb_string_append_fmt(cmd, " -M \"%.*s\"", LIT(build_context.android_manifest));
+	cmd = gb_string_append_fmt(cmd, " -I \"%.*sandroid.jar\"", LIT(android_sdk_platforms));
+	cmd = gb_string_append_fmt(cmd, " -F \"%.*s.apk-build\"", LIT(output_apk));
+
+	result = system_exec_command_line_app("android-aapt", cmd);
+	if (result) {
+		return result;
+	}
+
+	TIME_SECTION("Android jarsigner");
+	gb_string_clear(cmd);
+
+	cmd = gb_string_append_length(cmd, build_context.ODIN_ANDROID_JAR_SIGNER.text, build_context.ODIN_ANDROID_JAR_SIGNER.len);
+	cmd = gb_string_append_fmt(cmd, " -storepass android -keystore \"%.*s\" \"%.*s.apk-build\" \"%.*s\"",
+		LIT(build_context.android_keystore),
+		LIT(output_apk),
+		LIT(build_context.android_keystore_alias)
+	);
+	result = system_exec_command_line_app("android-jarsigner", cmd);
+	if (result) {
+		return result;
+	}
+
+	TIME_SECTION("Android zipalign");
+	gb_string_clear(cmd);
+
+	cmd = gb_string_append_length(cmd, android_sdk_build_tools.text, android_sdk_build_tools.len);
+	cmd = gb_string_appendc(cmd, "zipalign");
+	cmd = gb_string_appendc(cmd, " -f 4");
+	cmd = gb_string_append_fmt(cmd, " \"%.*s.apk-build\" \"%.*s.apk\"", LIT(output_apk), LIT(output_apk));
+
+
+	result = system_exec_command_line_app("android-zipalign", cmd);
+	if (result) {
+		return result;
+	}
+
+	return 0;
+}