Example #1
0
 public void GetOriginalName()
 {
     if (ProjectName.Contains('.'))
     {
         originalName = ProjectName.Split('.')[0];
     }
     else
     {
         originalName = ProjectName;
     }
 }
Example #2
0
        static void Main(string[] args)
        {
            int    userInput = 1;
            string ProjectName, ActivityName, UserInput;

            Console.WriteLine("Welcome to URLEncoder");

            while (userInput == 1)
            {
                Console.WriteLine("Please input the project name: ");
                ProjectName = Console.ReadLine();

                while (ProjectName.Contains("NUL") || ProjectName.Contains("SOH") || ProjectName.Contains("STX") || ProjectName.Contains("ETX") || ProjectName.Contains("EOT") || ProjectName.Contains("ENQ") || ProjectName.Contains("ACK") || ProjectName.Contains("BEL") || ProjectName.Contains("BS") || ProjectName.Contains("HT") || ProjectName.Contains("LF") || ProjectName.Contains("VT") || ProjectName.Contains("FF") || ProjectName.Contains("CR") || ProjectName.Contains("SO") || ProjectName.Contains("SI") || ProjectName.Contains("DLE") || ProjectName.Contains("DC1") || ProjectName.Contains("DC2") || ProjectName.Contains("DC3") || ProjectName.Contains("DC4") || ProjectName.Contains("NAK") || ProjectName.Contains("SYN") || ProjectName.Contains("ETB") || ProjectName.Contains("CAN") || ProjectName.Contains("EM") || ProjectName.Contains("SUB") || ProjectName.Contains("ESC") || ProjectName.Contains("FS") || ProjectName.Contains("GS") || ProjectName.Contains("RS") || ProjectName.Contains("US"))
                {
                    Console.WriteLine("That input is invalid beause it contains a control character. Please input a new project name: ");
                    ProjectName = Console.ReadLine();
                }

                Console.WriteLine("Please input the activity name: ");
                ActivityName = Console.ReadLine();

                while (ActivityName.Contains("NUL") || ActivityName.Contains("SOH") || ActivityName.Contains("STX") || ActivityName.Contains("ETX") || ActivityName.Contains("EOT") || ActivityName.Contains("ENQ") || ActivityName.Contains("ACK") || ActivityName.Contains("BEL") || ActivityName.Contains("BS") || ActivityName.Contains("HT") || ActivityName.Contains("LF") || ActivityName.Contains("VT") || ActivityName.Contains("FF") || ActivityName.Contains("CR") || ActivityName.Contains("SO") || ActivityName.Contains("SI") || ActivityName.Contains("DLE") || ActivityName.Contains("DC1") || ActivityName.Contains("DC2") || ActivityName.Contains("DC3") || ActivityName.Contains("DC4") || ActivityName.Contains("NAK") || ActivityName.Contains("SYN") || ActivityName.Contains("ETB") || ActivityName.Contains("CAN") || ActivityName.Contains("EM") || ActivityName.Contains("SUB") || ActivityName.Contains("ESC") || ActivityName.Contains("FS") || ActivityName.Contains("GS") || ActivityName.Contains("RS") || ActivityName.Contains("US"))
                {
                    Console.WriteLine("That input is invalid beause it contains a control character. Please input a new activity name: ");
                    ActivityName = Console.ReadLine();
                }

                var projectName  = ProjectName.Replace(" ", "%20").Replace("<", "%3C").Replace(">", "%3E").Replace("#", "%23").Replace("%", "%25").Replace("\"", "%22").Replace(";", "%3B").Replace("/", "%2F").Replace("?", "%3F").Replace(":", "%3A").Replace("@", "%40").Replace("&", "%26").Replace("$", "%24").Replace("+", "%2B").Replace("=", "%3D").Replace("[", "%5B").Replace("]", "%5D").Replace("\\", "%5C").Replace("^", "%5E").Replace("`", "%60").Replace("{", "%7B").Replace("}", "%7D").Replace("|", "%7C");
                var activityName = ActivityName.Replace(" ", "%20").Replace("<", "%3C").Replace(">", "%3E").Replace("#", "%23").Replace("%", "%25").Replace("\"", "%22").Replace(";", "%3B").Replace("/", "%2F").Replace("?", "%3F").Replace(":", "%3A").Replace("@", "%40").Replace("&", "%26").Replace("$", "%24").Replace("+", "%2B").Replace("=", "%3D").Replace("[", "%5B").Replace("]", "%5D").Replace("\\", "%5C").Replace("^", "%5E").Replace("`", "%60").Replace("{", "%7B").Replace("}", "%7D").Replace("|", "%7C");

                Console.WriteLine("https://companyserver.com/content/{0}/files/{1}/{1}Report.pdf", projectName, activityName);

                Console.WriteLine("Would you like to create another URL? Type \"yes\", and then hit the \"enter\" key twice if you do, or type anything else and hit enter twice to exit the program.");
                UserInput = Console.ReadLine();

                if (UserInput == "yes")
                {
                    userInput = 1;
                }

                else
                {
                    userInput = 0;
                }

                Console.ReadLine();
            }
        }
Example #3
0
    public override bool Execute()
    {
        bool isDevice = (TargetOS == TargetNames.iOS || TargetOS == TargetNames.tvOS);

        if (!string.IsNullOrEmpty(MainLibraryFileName))
        {
            if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
            {
                throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
            }
        }

        if (ProjectName.Contains(' '))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' should not contain spaces");
        }

        string[] excludes = Array.Empty <string>();
        if (ExcludeFromAppDir != null)
        {
            excludes = ExcludeFromAppDir
                       .Where(i => !string.IsNullOrEmpty(i.ItemSpec))
                       .Select(i => i.ItemSpec)
                       .ToArray();
        }

        string binDir = Path.Combine(AppDir, $"bin-{ProjectName}-{Arch}");

        if (!string.IsNullOrEmpty(OutputDirectory))
        {
            binDir = OutputDirectory;
        }
        Directory.CreateDirectory(binDir);

        var assemblerFiles       = new List <string>();
        var assemblerFilesToLink = new List <string>();

        foreach (ITaskItem file in Assemblies)
        {
            // use AOT files if available
            var obj     = file.GetMetadata("AssemblerFile");
            var llvmObj = file.GetMetadata("LlvmObjectFile");
            if (!string.IsNullOrEmpty(obj))
            {
                assemblerFiles.Add(obj);
            }

            if (!string.IsNullOrEmpty(llvmObj))
            {
                assemblerFilesToLink.Add(llvmObj);
            }
        }

        if (((!ForceInterpreter && (isDevice || ForceAOT)) && !assemblerFiles.Any()))
        {
            throw new InvalidOperationException("Need list of AOT files for device builds.");
        }

        if (!string.IsNullOrEmpty(DiagnosticPorts))
        {
            bool validDiagnosticsConfig = false;

            if (string.IsNullOrEmpty(RuntimeComponents))
            {
                validDiagnosticsConfig = false;
            }
            else if (RuntimeComponents.Equals("*", StringComparison.OrdinalIgnoreCase))
            {
                validDiagnosticsConfig = true;
            }
            else if (RuntimeComponents.Contains("diagnostics_tracing", StringComparison.OrdinalIgnoreCase))
            {
                validDiagnosticsConfig = true;
            }

            if (!validDiagnosticsConfig)
            {
                throw new ArgumentException("Using DiagnosticPorts require diagnostics_tracing runtime component.");
            }
        }

        if (EnableAppSandbox && (string.IsNullOrEmpty(DevTeamProvisioning) || DevTeamProvisioning == "-"))
        {
            throw new ArgumentException("DevTeamProvisioning must be set to a valid value when App Sandbox is enabled, using '-' is not supported.");
        }

        var generator = new Xcode(Log, TargetOS, Arch);

        if (GenerateXcodeProject)
        {
            XcodeProjectPath = generator.GenerateXCode(ProjectName, MainLibraryFileName, assemblerFiles, assemblerFilesToLink,
                                                       AppDir, binDir, MonoRuntimeHeaders, !isDevice, UseConsoleUITemplate, ForceAOT, ForceInterpreter, InvariantGlobalization, Optimized, EnableRuntimeLogging, EnableAppSandbox, DiagnosticPorts, RuntimeComponents, NativeMainSource);

            if (BuildAppBundle)
            {
                if (isDevice && string.IsNullOrEmpty(DevTeamProvisioning))
                {
                    // DevTeamProvisioning shouldn't be empty for arm64 builds
                    Log.LogMessage(MessageImportance.High, "DevTeamProvisioning is not set, BuildAppBundle step is skipped.");
                }
                else
                {
                    AppBundlePath = generator.BuildAppBundle(XcodeProjectPath, Optimized, DevTeamProvisioning);
                }
            }
        }
        else if (GenerateCMakeProject)
        {
            generator.GenerateCMake(ProjectName, MainLibraryFileName, assemblerFiles, assemblerFilesToLink,
                                    AppDir, binDir, MonoRuntimeHeaders, !isDevice, UseConsoleUITemplate, ForceAOT, ForceInterpreter, InvariantGlobalization, Optimized, EnableRuntimeLogging, EnableAppSandbox, DiagnosticPorts, RuntimeComponents, NativeMainSource);
        }

        return(true);
    }
Example #4
0
    public (string apk, string packageId) BuildApk(
        string sourceDir, string abi, string entryPointLib, string monoRuntimeHeaders)
    {
        if (!Directory.Exists(sourceDir))
        {
            throw new ArgumentException($"sourceDir='{sourceDir}' is empty or doesn't exist");
        }

        if (string.IsNullOrEmpty(abi))
        {
            throw new ArgumentException("abi shoudln't be empty (e.g. x86, x86_64, armeabi-v7a or arm64-v8a");
        }

        if (string.IsNullOrEmpty(entryPointLib))
        {
            throw new ArgumentException("entryPointLib shouldn't be empty");
        }

        if (!File.Exists(Path.Combine(sourceDir, entryPointLib)))
        {
            throw new ArgumentException($"{entryPointLib} was not found in sourceDir='{sourceDir}'");
        }

        if (string.IsNullOrEmpty(ProjectName))
        {
            ProjectName = Path.GetFileNameWithoutExtension(entryPointLib);
        }

        if (string.IsNullOrEmpty(OutputDir))
        {
            OutputDir = Path.Combine(sourceDir, "bin-" + abi);
        }

        if (ProjectName.Contains(' '))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' shouldn't not contain spaces.");
        }

        if (string.IsNullOrEmpty(AndroidSdk))
        {
            AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidNdk))
        {
            AndroidNdk = Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
        {
            throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
        }

        if (string.IsNullOrEmpty(AndroidNdk) || !Directory.Exists(AndroidNdk))
        {
            throw new ArgumentException($"Android NDK='{AndroidNdk}' was not found or empty (can be set via ANDROID_NDK_ROOT envvar).");
        }

        // Try to get the latest build-tools version if not specified
        if (string.IsNullOrEmpty(BuildToolsVersion))
        {
            BuildToolsVersion = GetLatestBuildTools(AndroidSdk);
        }

        // Try to get the latest API level if not specified
        if (string.IsNullOrEmpty(BuildApiLevel))
        {
            BuildApiLevel = GetLatestApiLevel(AndroidSdk);
        }

        if (string.IsNullOrEmpty(MinApiLevel))
        {
            MinApiLevel = DefaultMinApiLevel;
        }

        // make sure BuildApiLevel >= MinApiLevel
        // only if these api levels are not "preview" (not integers)
        if (int.TryParse(BuildApiLevel, out int intApi) &&
            int.TryParse(MinApiLevel, out int intMinApi) &&
            intApi < intMinApi)
        {
            throw new ArgumentException($"BuildApiLevel={BuildApiLevel} <= MinApiLevel={MinApiLevel}. " +
                                        "Make sure you've downloaded some recent build-tools in Android SDK");
        }

        string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);

        if (!Directory.Exists(buildToolsFolder))
        {
            throw new ArgumentException($"{buildToolsFolder} was not found.");
        }

        Directory.CreateDirectory(OutputDir);
        Directory.CreateDirectory(Path.Combine(OutputDir, "bin"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets-tozip"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));

        var extensionsToIgnore = new List <string> {
            ".so", ".a", ".gz"
        };

        if (StripDebugSymbols)
        {
            extensionsToIgnore.Add(".pdb");
            extensionsToIgnore.Add(".dbg");
        }

        // Copy AppDir to OutputDir/assets-tozip (ignore native files)
        // these files then will be zipped and copied to apk/assets/assets.zip
        Utils.DirectoryCopy(sourceDir, Path.Combine(OutputDir, "assets-tozip"), file =>
        {
            string fileName  = Path.GetFileName(file);
            string extension = Path.GetExtension(file);

            if (extensionsToIgnore.Contains(extension))
            {
                // ignore native files, those go to lib/%abi%
                // also, aapt is not happy about zip files
                return(false);
            }
            if (fileName.StartsWith("."))
            {
                // aapt complains on such files
                return(false);
            }
            return(true);
        });

        // tools:
        string dx               = Path.Combine(buildToolsFolder, "dx");
        string aapt             = Path.Combine(buildToolsFolder, "aapt");
        string zipalign         = Path.Combine(buildToolsFolder, "zipalign");
        string apksigner        = Path.Combine(buildToolsFolder, "apksigner");
        string androidJar       = Path.Combine(AndroidSdk, "platforms", "android-" + BuildApiLevel, "android.jar");
        string androidToolchain = Path.Combine(AndroidNdk, "build", "cmake", "android.toolchain.cmake");
        string keytool          = "keytool";
        string javac            = "javac";
        string cmake            = "cmake";
        string zip              = "zip";

        Utils.RunProcess(zip, workingDir: Path.Combine(OutputDir, "assets-tozip"), args: "-q -r ../assets/assets.zip .");
        Directory.Delete(Path.Combine(OutputDir, "assets-tozip"), true);

        if (!File.Exists(androidJar))
        {
            throw new ArgumentException($"API level={BuildApiLevel} is not downloaded in Android SDK");
        }

        // 1. Build libmonodroid.so` via cmake

        string monoRuntimeLib = Path.Combine(sourceDir, "libmonosgen-2.0.a");

        if (!File.Exists(monoRuntimeLib))
        {
            throw new ArgumentException($"libmonosgen-2.0.a was not found in {sourceDir}");
        }

        string cmakeLists = Utils.GetEmbeddedResource("CMakeLists-android.txt")
                            .Replace("%MonoInclude%", monoRuntimeHeaders)
                            .Replace("%NativeLibrariesToLink%", monoRuntimeLib);

        File.WriteAllText(Path.Combine(OutputDir, "CMakeLists.txt"), cmakeLists);

        string monodroidSrc = Utils.GetEmbeddedResource("monodroid.c")
                              .Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib)
                                       .Replace("%RID%", GetRid(abi)));

        File.WriteAllText(Path.Combine(OutputDir, "monodroid.c"), monodroidSrc);

        string cmakeGenArgs = $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " +
                              $"-DANDROID_NATIVE_API_LEVEL={MinApiLevel} -B monodroid";

        string cmakeBuildArgs = "--build monodroid";

        if (StripDebugSymbols)
        {
            // Use "-s" to strip debug symbols, it complains it's unused but it works
            cmakeGenArgs   += " -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_C_FLAGS=\"-s -Wno-unused-command-line-argument\"";
            cmakeBuildArgs += " --config MinSizeRel";
        }
        else
        {
            cmakeGenArgs   += " -DCMAKE_BUILD_TYPE=Debug";
            cmakeBuildArgs += " --config Debug";
        }

        Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeGenArgs);
        Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeBuildArgs);

        // 2. Compile Java files

        string javaSrcFolder = Path.Combine(OutputDir, "src", "net", "dot");

        Directory.CreateDirectory(javaSrcFolder);

        string packageId = $"net.dot.{ProjectName}";

        File.WriteAllText(Path.Combine(javaSrcFolder, "MainActivity.java"),
                          Utils.GetEmbeddedResource("MainActivity.java"));
        File.WriteAllText(Path.Combine(javaSrcFolder, "MonoRunner.java"),
                          Utils.GetEmbeddedResource("MonoRunner.java"));
        File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
                          Utils.GetEmbeddedResource("AndroidManifest.xml")
                          .Replace("%PackageName%", packageId)
                          .Replace("%MinSdkLevel%", MinApiLevel));

        string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 ";

        Utils.RunProcess(javac, javaCompilerArgs + Path.Combine(javaSrcFolder, "MainActivity.java"), workingDir: OutputDir);
        Utils.RunProcess(javac, javaCompilerArgs + Path.Combine(javaSrcFolder, "MonoRunner.java"), workingDir: OutputDir);
        Utils.RunProcess(dx, "--dex --output=classes.dex obj", workingDir: OutputDir);

        // 3. Generate APK

        string apkFile = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk");

        Utils.RunProcess(aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar}", workingDir: OutputDir);

        var dynamicLibs = new List <string>();

        dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so"));
        dynamicLibs.AddRange(Directory.GetFiles(sourceDir, "*.so"));

        // add all *.so files to lib/%abi%/
        Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
        foreach (var dynamicLib in dynamicLibs)
        {
            string dynamicLibName = Path.GetFileName(dynamicLib);
            if (dynamicLibName == "libmonosgen-2.0.so")
            {
                // we link mono runtime statically into libmonodroid.so
                continue;
            }

            // NOTE: we can run android-strip tool from NDK to shrink native binaries here even more.

            string destRelative = Path.Combine("lib", abi, dynamicLibName);
            File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true);
            Utils.RunProcess(aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir);
        }
        Utils.RunProcess(aapt, $"add {apkFile} classes.dex", workingDir: OutputDir);

        // 4. Align APK

        string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");

        Utils.RunProcess(zipalign, $"-v 4 {apkFile} {alignedApk}", workingDir: OutputDir);
        // we don't need the unaligned one any more
        File.Delete(apkFile);

        // 5. Generate key

        string signingKey = Path.Combine(OutputDir, "debug.keystore");

        if (!File.Exists(signingKey))
        {
            Utils.RunProcess(keytool, "-genkey -v -keystore debug.keystore -storepass android -alias " +
                             "androiddebugkey -keypass android -keyalg RSA -keysize 2048 -noprompt " +
                             "-dname \"CN=Android Debug,O=Android,C=US\"", workingDir: OutputDir, silent: true);
        }

        // 6. Sign APK

        Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " +
                         $"--ks-pass pass:android --key-pass pass:android {alignedApk}", workingDir: OutputDir);

        Utils.LogInfo($"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n");

        return(alignedApk, packageId);
    }
Example #5
0
        protected BaseProjectOptions(
            DirectoryInfo projectSrcGeneratePath,
            DirectoryInfo?projectTestGeneratePath,
            OpenApiDocument openApiDocument,
            FileInfo openApiDocumentFile,
            string projectPrefixName,
            string?projectSuffixName,
            ApiOptions.ApiOptions apiOptions,
            bool forClient          = false,
            string?clientFolderName = null)
        {
            if (projectSrcGeneratePath == null)
            {
                throw new ArgumentNullException(nameof(projectSrcGeneratePath));
            }

            ProjectName        = projectPrefixName ?? throw new ArgumentNullException(nameof(projectPrefixName));
            PathForSrcGenerate = projectSrcGeneratePath.Name.StartsWith(ProjectName, StringComparison.OrdinalIgnoreCase)
                ? projectSrcGeneratePath.Parent !
                : projectSrcGeneratePath;

            if (projectTestGeneratePath != null)
            {
                PathForTestGenerate = projectTestGeneratePath.Name.StartsWith(ProjectName, StringComparison.OrdinalIgnoreCase)
                    ? projectTestGeneratePath.Parent !
                    : projectTestGeneratePath;
            }

            Document     = openApiDocument ?? throw new ArgumentNullException(nameof(openApiDocument));
            DocumentFile = openApiDocumentFile ?? throw new ArgumentNullException(nameof(openApiDocumentFile));

            ToolName    = "ApiGenerator";
            ToolVersion = GenerateHelper.GetAtcToolVersion();
            ApiOptions  = apiOptions;

            ApiVersion  = GetApiVersion(openApiDocument);
            ProjectName = string.IsNullOrEmpty(projectSuffixName)
                ? projectPrefixName
                          .Replace(" ", ".", StringComparison.Ordinal)
                          .Replace("-", ".", StringComparison.Ordinal)
                          .Trim()
                : projectPrefixName
                          .Replace(" ", ".", StringComparison.Ordinal)
                          .Replace("-", ".", StringComparison.Ordinal)
                          .Trim() + $".{projectSuffixName}";

            ProjectPrefixName = ProjectName.Contains('.', StringComparison.Ordinal)
                ? ProjectName.Substring(0, ProjectName.IndexOf('.', StringComparison.Ordinal))
                : ProjectName;

            PathForSrcGenerate = new DirectoryInfo(Path.Combine(PathForSrcGenerate.FullName, ProjectName));
            ProjectSrcCsProj   = new FileInfo(Path.Combine(PathForSrcGenerate.FullName, $"{ProjectName}.csproj"));
            if (PathForTestGenerate != null)
            {
                PathForTestGenerate = new DirectoryInfo(Path.Combine(PathForTestGenerate.FullName, $"{ProjectName}.Tests"));
                ProjectTestCsProj   = new FileInfo(Path.Combine(PathForTestGenerate.FullName, $"{ProjectName}.Tests.csproj"));
            }

            this.ForClient        = forClient;
            this.ClientFolderName = clientFolderName;

            BasePathSegmentNames = OpenApiDocumentHelper.GetBasePathSegmentNames(openApiDocument);
        }
    public override bool Execute()
    {
        Utils.Logger = Log;
        bool isDevice = Arch.Equals("arm64", StringComparison.InvariantCultureIgnoreCase) && TargetOS != TargetNames.MacCatalyst;

        if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
        {
            throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
        }

        if (ProjectName.Contains(" "))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' should not contain spaces");
        }

        string[] excludes = Array.Empty <string>();
        if (ExcludeFromAppDir != null)
        {
            excludes = ExcludeFromAppDir
                       .Where(i => !string.IsNullOrEmpty(i.ItemSpec))
                       .Select(i => i.ItemSpec)
                       .ToArray();
        }

        string binDir = Path.Combine(AppDir, $"bin-{ProjectName}-{Arch}");

        if (!string.IsNullOrEmpty(OutputDirectory))
        {
            binDir = OutputDirectory;
        }
        Directory.CreateDirectory(binDir);

        var assemblerFiles = new List <string>();

        foreach (ITaskItem file in Assemblies)
        {
            // use AOT files if available
            var obj = file.GetMetadata("AssemblerFile");
            if (!string.IsNullOrEmpty(obj))
            {
                assemblerFiles.Add(obj);
            }
        }

        if (((!ForceInterpreter && (isDevice || ForceAOT)) && !assemblerFiles.Any()))
        {
            throw new InvalidOperationException("Need list of AOT files for device builds.");
        }

        if (ForceInterpreter && ForceAOT)
        {
            throw new InvalidOperationException("Interpreter and AOT cannot be enabled at the same time");
        }

        if (GenerateXcodeProject)
        {
            Xcode generator = new Xcode(TargetOS);
            generator.EnableRuntimeLogging = EnableRuntimeLogging;

            XcodeProjectPath = generator.GenerateXCode(ProjectName, MainLibraryFileName, assemblerFiles,
                                                       AppDir, binDir, MonoRuntimeHeaders, !isDevice, UseConsoleUITemplate, ForceAOT, ForceInterpreter, InvariantGlobalization, Optimized, NativeMainSource);

            if (BuildAppBundle)
            {
                if (isDevice && string.IsNullOrEmpty(DevTeamProvisioning))
                {
                    // DevTeamProvisioning shouldn't be empty for arm64 builds
                    Utils.LogInfo("DevTeamProvisioning is not set, BuildAppBundle step is skipped.");
                }
                else
                {
                    AppBundlePath = generator.BuildAppBundle(XcodeProjectPath, Arch, Optimized, DevTeamProvisioning);
                }
            }
        }

        return(true);
    }
Example #7
0
    public (string apk, string packageId) BuildApk(
        string abi,
        string mainLibraryFileName,
        string monoRuntimeHeaders)
    {
        if (string.IsNullOrEmpty(AppDir) || !Directory.Exists(AppDir))
        {
            throw new ArgumentException($"AppDir='{AppDir}' is empty or doesn't exist");
        }

        if (!string.IsNullOrEmpty(mainLibraryFileName) && !File.Exists(Path.Combine(AppDir, mainLibraryFileName)))
        {
            throw new ArgumentException($"MainLibraryFileName='{mainLibraryFileName}' was not found in AppDir='{AppDir}'");
        }

        var networkSecurityConfigFilePath = Path.Combine(AppDir, "res", "xml", "network_security_config.xml");

        if (IncludeNetworkSecurityConfig && !File.Exists(networkSecurityConfigFilePath))
        {
            throw new ArgumentException($"IncludeNetworkSecurityConfig is set but the file '{networkSecurityConfigFilePath}' was not found");
        }

        if (string.IsNullOrEmpty(abi))
        {
            throw new ArgumentException("abi should not be empty (e.g. x86, x86_64, armeabi-v7a or arm64-v8a");
        }

        if (!string.IsNullOrEmpty(ProjectName) && ProjectName.Contains(' '))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' should not not contain spaces.");
        }

        if (string.IsNullOrEmpty(AndroidSdk))
        {
            AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidNdk))
        {
            AndroidNdk = Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
        {
            throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
        }

        if (string.IsNullOrEmpty(AndroidNdk) || !Directory.Exists(AndroidNdk))
        {
            throw new ArgumentException($"Android NDK='{AndroidNdk}' was not found or empty (can be set via ANDROID_NDK_ROOT envvar).");
        }

        if (ForceInterpreter && ForceAOT)
        {
            throw new InvalidOperationException("Interpreter and AOT cannot be enabled at the same time");
        }

        if (!string.IsNullOrEmpty(DiagnosticPorts))
        {
            bool validDiagnosticsConfig = false;

            if (string.IsNullOrEmpty(RuntimeComponents))
            {
                validDiagnosticsConfig = false;
            }
            else if (RuntimeComponents.Equals("*", StringComparison.OrdinalIgnoreCase))
            {
                validDiagnosticsConfig = true;
            }
            else if (RuntimeComponents.Contains("diagnostics_tracing", StringComparison.OrdinalIgnoreCase))
            {
                validDiagnosticsConfig = true;
            }

            if (!validDiagnosticsConfig)
            {
                throw new ArgumentException("Using DiagnosticPorts require diagnostics_tracing runtime component.");
            }
        }

        // Try to get the latest build-tools version if not specified
        if (string.IsNullOrEmpty(BuildToolsVersion))
        {
            BuildToolsVersion = GetLatestBuildTools(AndroidSdk);
        }

        // Try to get the latest API level if not specified
        if (string.IsNullOrEmpty(BuildApiLevel))
        {
            BuildApiLevel = GetLatestApiLevel(AndroidSdk);
        }

        if (string.IsNullOrEmpty(MinApiLevel))
        {
            MinApiLevel = DefaultMinApiLevel;
        }

        // make sure BuildApiLevel >= MinApiLevel
        // only if these api levels are not "preview" (not integers)
        if (int.TryParse(BuildApiLevel, out int intApi) &&
            int.TryParse(MinApiLevel, out int intMinApi) &&
            intApi < intMinApi)
        {
            throw new ArgumentException($"BuildApiLevel={BuildApiLevel} <= MinApiLevel={MinApiLevel}. " +
                                        "Make sure you've downloaded some recent build-tools in Android SDK");
        }

        string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);

        if (!Directory.Exists(buildToolsFolder))
        {
            throw new ArgumentException($"{buildToolsFolder} was not found.");
        }

        var assemblerFiles       = new StringBuilder();
        var assemblerFilesToLink = new StringBuilder();
        var aotLibraryFiles      = new List <string>();

        foreach (ITaskItem file in Assemblies)
        {
            // use AOT files if available
            var obj     = file.GetMetadata("AssemblerFile");
            var llvmObj = file.GetMetadata("LlvmObjectFile");
            var lib     = file.GetMetadata("LibraryFile");

            if (!string.IsNullOrEmpty(obj))
            {
                var name = Path.GetFileNameWithoutExtension(obj);
                assemblerFiles.AppendLine($"add_library({name} OBJECT {obj})");
                assemblerFilesToLink.AppendLine($"    {name}");
            }

            if (!string.IsNullOrEmpty(llvmObj))
            {
                var name = Path.GetFileNameWithoutExtension(llvmObj);
                assemblerFilesToLink.AppendLine($"    {llvmObj}");
            }

            if (!string.IsNullOrEmpty(lib))
            {
                aotLibraryFiles.Add(lib);
            }
        }

        if (ForceAOT && assemblerFiles.Length == 0 && aotLibraryFiles.Count == 0)
        {
            throw new InvalidOperationException("Need list of AOT files.");
        }

        Directory.CreateDirectory(OutputDir);
        Directory.CreateDirectory(Path.Combine(OutputDir, "bin"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets-tozip"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "res"));

        var extensionsToIgnore = new List <string> {
            ".so", ".a"
        };

        if (StripDebugSymbols)
        {
            extensionsToIgnore.Add(".pdb");
            extensionsToIgnore.Add(".dbg");
        }

        // Copy sourceDir to OutputDir/assets-tozip (ignore native files)
        // these files then will be zipped and copied to apk/assets/assets.zip
        var assetsToZipDirectory = Path.Combine(OutputDir, "assets-tozip");

        Utils.DirectoryCopy(AppDir, assetsToZipDirectory, file =>
        {
            string fileName  = Path.GetFileName(file);
            string extension = Path.GetExtension(file);

            if (extensionsToIgnore.Contains(extension))
            {
                // ignore native files, those go to lib/%abi%
                // also, aapt is not happy about zip files
                return(false);
            }
            if (fileName.StartsWith("."))
            {
                // aapt complains on such files
                return(false);
            }
            if (file.Contains("/res/"))
            {
                // exclude everything in the `res` folder
                return(false);
            }
            return(true);
        });

        // copy the res directory as is
        if (Directory.Exists(Path.Combine(AppDir, "res")))
        {
            Utils.DirectoryCopy(Path.Combine(AppDir, "res"), Path.Combine(OutputDir, "res"));
        }

        // add AOT .so libraries
        foreach (var aotlib in aotLibraryFiles)
        {
            File.Copy(aotlib, Path.Combine(assetsToZipDirectory, Path.GetFileName(aotlib)));
        }

        // tools:
        string dx               = Path.Combine(buildToolsFolder, "dx");
        string d8               = Path.Combine(buildToolsFolder, "d8");
        string aapt             = Path.Combine(buildToolsFolder, "aapt");
        string zipalign         = Path.Combine(buildToolsFolder, "zipalign");
        string apksigner        = Path.Combine(buildToolsFolder, "apksigner");
        string androidJar       = Path.Combine(AndroidSdk, "platforms", "android-" + BuildApiLevel, "android.jar");
        string androidToolchain = Path.Combine(AndroidNdk, "build", "cmake", "android.toolchain.cmake");
        string javac            = "javac";
        string cmake            = "cmake";
        string zip              = "zip";

        Utils.RunProcess(logger, zip, workingDir: assetsToZipDirectory, args: "-q -r ../assets/assets.zip .");
        Directory.Delete(assetsToZipDirectory, true);

        if (!File.Exists(androidJar))
        {
            throw new ArgumentException($"API level={BuildApiLevel} is not downloaded in Android SDK");
        }

        // 1. Build libmonodroid.so` via cmake

        string nativeLibraries = "";
        string monoRuntimeLib  = "";

        if (StaticLinkedRuntime)
        {
            monoRuntimeLib = Path.Combine(AppDir, "libmonosgen-2.0.a");
        }
        else
        {
            monoRuntimeLib = Path.Combine(AppDir, "libmonosgen-2.0.so");
        }

        if (!File.Exists(monoRuntimeLib))
        {
            throw new ArgumentException($"{monoRuntimeLib} was not found");
        }
        else
        {
            nativeLibraries += $"{monoRuntimeLib}{Environment.NewLine}";
        }

        if (StaticLinkedRuntime)
        {
            string[] staticComponentStubLibs = Directory.GetFiles(AppDir, "libmono-component-*-stub-static.a");
            bool     staticLinkAllComponents = false;
            string[] staticLinkedComponents  = Array.Empty <string>();

            if (!string.IsNullOrEmpty(RuntimeComponents) && RuntimeComponents.Equals("*", StringComparison.OrdinalIgnoreCase))
            {
                staticLinkAllComponents = true;
            }
            else if (!string.IsNullOrEmpty(RuntimeComponents))
            {
                staticLinkedComponents = RuntimeComponents.Split(";");
            }

            // by default, component stubs will be linked and depending on how mono runtime has been build,
            // stubs can disable or dynamic load components.
            foreach (string staticComponentStubLib in staticComponentStubLibs)
            {
                string componentLibToLink = staticComponentStubLib;
                if (staticLinkAllComponents)
                {
                    // static link component.
                    componentLibToLink = componentLibToLink.Replace("-stub-static.a", "-static.a", StringComparison.OrdinalIgnoreCase);
                }
                else
                {
                    foreach (string staticLinkedComponent in staticLinkedComponents)
                    {
                        if (componentLibToLink.Contains(staticLinkedComponent, StringComparison.OrdinalIgnoreCase))
                        {
                            // static link component.
                            componentLibToLink = componentLibToLink.Replace("-stub-static.a", "-static.a", StringComparison.OrdinalIgnoreCase);
                            break;
                        }
                    }
                }

                // if lib doesn't exist (primarily due to runtime build without static lib support), fallback linking stub lib.
                if (!File.Exists(componentLibToLink))
                {
                    logger.LogMessage(MessageImportance.High, $"\nCouldn't find static component library: {componentLibToLink}, linking static component stub library: {staticComponentStubLib}.\n");
                    componentLibToLink = staticComponentStubLib;
                }

                nativeLibraries += $"    {componentLibToLink}{Environment.NewLine}";
            }

            // There's a circular dependency between static mono runtime lib and static component libraries.
            // Adding mono runtime lib before and after component libs will resolve issues with undefined symbols
            // due to circular dependency.
            nativeLibraries += $"    {monoRuntimeLib}{Environment.NewLine}";
        }

        nativeLibraries += assemblerFilesToLink.ToString();

        string aotSources = assemblerFiles.ToString();

        string cmakeLists = Utils.GetEmbeddedResource("CMakeLists-android.txt")
                            .Replace("%MonoInclude%", monoRuntimeHeaders)
                            .Replace("%NativeLibrariesToLink%", nativeLibraries)
                            .Replace("%AotSources%", aotSources)
                            .Replace("%AotModulesSource%", string.IsNullOrEmpty(aotSources) ? "" : "modules.c");

        var defines = new StringBuilder();

        if (ForceInterpreter)
        {
            defines.AppendLine("add_definitions(-DFORCE_INTERPRETER=1)");
        }
        else if (ForceAOT)
        {
            defines.AppendLine("add_definitions(-DFORCE_AOT=1)");
            if (aotLibraryFiles.Count == 0)
            {
                defines.AppendLine("add_definitions(-DSTATIC_AOT=1)");
            }
        }

        if (ForceFullAOT)
        {
            defines.AppendLine("add_definitions(-DFULL_AOT=1)");
        }

        if (!string.IsNullOrEmpty(DiagnosticPorts))
        {
            defines.AppendLine("add_definitions(-DDIAGNOSTIC_PORTS=\"" + DiagnosticPorts + "\")");
        }

        cmakeLists = cmakeLists.Replace("%Defines%", defines.ToString());

        File.WriteAllText(Path.Combine(OutputDir, "CMakeLists.txt"), cmakeLists);

        File.WriteAllText(Path.Combine(OutputDir, "monodroid.c"), Utils.GetEmbeddedResource("monodroid.c"));

        string cmakeGenArgs = $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " +
                              $"-DANDROID_PLATFORM=android-{MinApiLevel} -B monodroid";

        string cmakeBuildArgs = "--build monodroid";

        if (StripDebugSymbols)
        {
            // Use "-s" to strip debug symbols, it complains it's unused but it works
            cmakeGenArgs   += " -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_C_FLAGS=\"-s -Wno-unused-command-line-argument\"";
            cmakeBuildArgs += " --config MinSizeRel";
        }
        else
        {
            cmakeGenArgs   += " -DCMAKE_BUILD_TYPE=Debug";
            cmakeBuildArgs += " --config Debug";
        }

        Utils.RunProcess(logger, cmake, workingDir: OutputDir, args: cmakeGenArgs);
        Utils.RunProcess(logger, cmake, workingDir: OutputDir, args: cmakeBuildArgs);

        // 2. Compile Java files

        string javaSrcFolder = Path.Combine(OutputDir, "src", "net", "dot");

        Directory.CreateDirectory(javaSrcFolder);

        string javaActivityPath = Path.Combine(javaSrcFolder, "MainActivity.java");
        string monoRunnerPath   = Path.Combine(javaSrcFolder, "MonoRunner.java");

        Regex checkNumerics = new Regex(@"\.(\d)");

        if (!string.IsNullOrEmpty(ProjectName) && checkNumerics.IsMatch(ProjectName))
        {
            ProjectName = checkNumerics.Replace(ProjectName, @"_$1");
        }

        string packageId = $"net.dot.{ProjectName}";

        File.WriteAllText(javaActivityPath,
                          Utils.GetEmbeddedResource("MainActivity.java")
                          .Replace("%EntryPointLibName%", Path.GetFileName(mainLibraryFileName)));
        if (!string.IsNullOrEmpty(NativeMainSource))
        {
            File.Copy(NativeMainSource, javaActivityPath, true);
        }

        string networkSecurityConfigAttribute =
            IncludeNetworkSecurityConfig
                ? "a:networkSecurityConfig=\"@xml/network_security_config\""
                : string.Empty;

        string envVariables = "";

        foreach (ITaskItem item in EnvironmentVariables)
        {
            string name  = item.ItemSpec;
            string value = item.GetMetadata("Value");
            envVariables += $"\t\tsetEnv(\"{name}\", \"{value}\");\n";
        }

        string monoRunner = Utils.GetEmbeddedResource("MonoRunner.java")
                            .Replace("%EntryPointLibName%", Path.GetFileName(mainLibraryFileName))
                            .Replace("%EnvVariables%", envVariables);

        File.WriteAllText(monoRunnerPath, monoRunner);

        File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
                          Utils.GetEmbeddedResource("AndroidManifest.xml")
                          .Replace("%PackageName%", packageId)
                          .Replace("%NetworkSecurityConfig%", networkSecurityConfigAttribute)
                          .Replace("%MinSdkLevel%", MinApiLevel));

        string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 ";

        Utils.RunProcess(logger, javac, javaCompilerArgs + javaActivityPath, workingDir: OutputDir);
        Utils.RunProcess(logger, javac, javaCompilerArgs + monoRunnerPath, workingDir: OutputDir);

        if (File.Exists(d8))
        {
            string[] classFiles = Directory.GetFiles(Path.Combine(OutputDir, "obj"), "*.class", SearchOption.AllDirectories);

            if (!classFiles.Any())
            {
                throw new InvalidOperationException("Didn't find any .class files");
            }

            Utils.RunProcess(logger, d8, $"--no-desugaring {string.Join(" ", classFiles)}", workingDir: OutputDir);
        }
        else
        {
            Utils.RunProcess(logger, dx, "--dex --output=classes.dex obj", workingDir: OutputDir);
        }

        // 3. Generate APK

        string debugModeArg = StripDebugSymbols ? string.Empty : "--debug-mode";
        string apkFile      = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk");
        string resources    = IncludeNetworkSecurityConfig ? "-S res" : string.Empty;

        Utils.RunProcess(logger, aapt, $"package -f -m -F {apkFile} -A assets {resources} -M AndroidManifest.xml -I {androidJar} {debugModeArg}", workingDir: OutputDir);

        var dynamicLibs = new List <string>();

        dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so"));
        dynamicLibs.AddRange(Directory.GetFiles(AppDir, "*.so").Where(file => Path.GetFileName(file) != "libmonodroid.so"));

        // add all *.so files to lib/%abi%/

        string[] dynamicLinkedComponents  = Array.Empty <string>();
        bool     dynamicLinkAllComponents = false;

        if (!StaticLinkedRuntime && !string.IsNullOrEmpty(RuntimeComponents) && RuntimeComponents.Equals("*", StringComparison.OrdinalIgnoreCase))
        {
            dynamicLinkAllComponents = true;
        }
        if (!string.IsNullOrEmpty(RuntimeComponents) && !StaticLinkedRuntime)
        {
            dynamicLinkedComponents = RuntimeComponents.Split(";");
        }

        Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
        foreach (var dynamicLib in dynamicLibs)
        {
            string dynamicLibName = Path.GetFileName(dynamicLib);
            string destRelative   = Path.Combine("lib", abi, dynamicLibName);

            if (dynamicLibName == "libmonosgen-2.0.so" && StaticLinkedRuntime)
            {
                // we link mono runtime statically into libmonodroid.so
                // make sure dynamic runtime is not included in package.
                if (File.Exists(destRelative))
                {
                    File.Delete(destRelative);
                }
                continue;
            }

            if (dynamicLibName.Contains("libmono-component-", StringComparison.OrdinalIgnoreCase))
            {
                bool includeComponent = dynamicLinkAllComponents;
                if (!StaticLinkedRuntime && !includeComponent)
                {
                    foreach (string dynamicLinkedComponent in dynamicLinkedComponents)
                    {
                        if (dynamicLibName.Contains(dynamicLinkedComponent, StringComparison.OrdinalIgnoreCase))
                        {
                            includeComponent = true;
                            break;
                        }
                    }
                }
                if (!includeComponent)
                {
                    // make sure dynamic component is not included in package.
                    if (File.Exists(destRelative))
                    {
                        File.Delete(destRelative);
                    }
                    continue;
                }
            }

            // NOTE: we can run android-strip tool from NDK to shrink native binaries here even more.

            File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true);
            Utils.RunProcess(logger, aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir);
        }
        Utils.RunProcess(logger, aapt, $"add {apkFile} classes.dex", workingDir: OutputDir);

        // 4. Align APK

        string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");

        AlignApk(apkFile, alignedApk, zipalign);
        // we don't need the unaligned one any more
        File.Delete(apkFile);

        // 5. Generate key (if needed) & sign the apk
        SignApk(alignedApk, apksigner);

        logger.LogMessage(MessageImportance.High, $"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n");

        return(alignedApk, packageId);
    }
Example #8
0
    public override bool Execute()
    {
        Utils.Logger = Log;
        bool isDevice = Arch.Equals("arm64", StringComparison.InvariantCultureIgnoreCase);

        if (isDevice && string.IsNullOrEmpty(CrossCompiler))
        {
            throw new ArgumentException("arm64 arch requires CrossCompiler");
        }

        if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
        {
            throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
        }

        if (ProjectName.Contains(" "))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' should not contain spaces");
        }

        if (UseLlvm && string.IsNullOrEmpty(LlvmPath))
        {
            // otherwise we might accidentally use some random llc/opt from PATH (installed with clang)
            throw new ArgumentException($"LlvmPath shoun't be empty when UseLlvm is set");
        }

        string[] excludes = new string[0];
        if (ExcludeFromAppDir != null)
        {
            excludes = ExcludeFromAppDir
                       .Where(i => !string.IsNullOrEmpty(i.ItemSpec))
                       .Select(i => i.ItemSpec)
                       .ToArray();
        }
        string[] libsToAot = Directory.GetFiles(AppDir, "*.dll")
                             .Where(f => !excludes.Contains(Path.GetFileName(f)))
                             .ToArray();

        string binDir = Path.Combine(AppDir, $"bin-{ProjectName}-{Arch}");

        if (!string.IsNullOrEmpty(OutputDirectory))
        {
            binDir = OutputDirectory;
        }
        Directory.CreateDirectory(binDir);

        // run AOT compilation only for devices
        if (isDevice)
        {
            if (string.IsNullOrEmpty(CrossCompiler))
            {
                throw new InvalidOperationException("cross-compiler is not set");
            }

            AotCompiler.PrecompileLibraries(CrossCompiler, Arch, !DisableParallelAot, binDir, libsToAot,
                                            new Dictionary <string, string> {
                { "MONO_PATH", AppDir }
            },
                                            Optimized, UseLlvm, LlvmPath);
        }

        // generate modules.m
        AotCompiler.GenerateLinkAllFile(
            Directory.GetFiles(binDir, "*.dll.o"),
            Path.Combine(binDir, "modules.m"));

        if (GenerateXcodeProject)
        {
            XcodeProjectPath = Xcode.GenerateXCode(ProjectName, MainLibraryFileName,
                                                   AppDir, binDir, MonoRuntimeHeaders, !isDevice, UseConsoleUITemplate, NativeMainSource);

            if (BuildAppBundle)
            {
                if (isDevice && string.IsNullOrEmpty(DevTeamProvisioning))
                {
                    // DevTeamProvisioning shouldn't be empty for arm64 builds
                    Utils.LogInfo("DevTeamProvisioning is not set, BuildAppBundle step is skipped.");
                }
                else
                {
                    AppBundlePath = Xcode.BuildAppBundle(
                        Path.Combine(binDir, ProjectName, ProjectName + ".xcodeproj"),
                        Arch, Optimized, DevTeamProvisioning);
                }
            }
        }

        return(true);
    }
Example #9
0
    public override bool Execute()
    {
        Utils.Logger = Log;
        bool isDevice = Arch.Equals("arm64", StringComparison.InvariantCultureIgnoreCase);

        if (!File.Exists(Path.Combine(AppDir, MainLibraryFileName)))
        {
            throw new ArgumentException($"MainLibraryFileName='{MainLibraryFileName}' was not found in AppDir='{AppDir}'");
        }

        if (ProjectName.Contains(" "))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' should not contain spaces");
        }

        string[] excludes = new string[0];
        if (ExcludeFromAppDir != null)
        {
            excludes = ExcludeFromAppDir
                       .Where(i => !string.IsNullOrEmpty(i.ItemSpec))
                       .Select(i => i.ItemSpec)
                       .ToArray();
        }

        string binDir = Path.Combine(AppDir, $"bin-{ProjectName}-{Arch}");

        if (!string.IsNullOrEmpty(OutputDirectory))
        {
            binDir = OutputDirectory;
        }
        Directory.CreateDirectory(binDir);

        var assemblerFiles = new List <string>();

        foreach (ITaskItem file in Assemblies)
        {
            // use AOT files if available
            var obj = file.GetMetadata("AssemblerFile");
            if (!String.IsNullOrEmpty(obj))
            {
                assemblerFiles.Add(obj);
            }
        }

        if (isDevice && !assemblerFiles.Any())
        {
            throw new InvalidOperationException("Need list of AOT files for device builds.");
        }

        // generate modules.m
        GenerateLinkAllFile(
            assemblerFiles,
            Path.Combine(binDir, "modules.m"));

        if (GenerateXcodeProject)
        {
            XcodeProjectPath = Xcode.GenerateXCode(ProjectName, MainLibraryFileName, assemblerFiles,
                                                   AppDir, binDir, MonoRuntimeHeaders, !isDevice, UseConsoleUITemplate, NativeMainSource);

            if (BuildAppBundle)
            {
                if (isDevice && string.IsNullOrEmpty(DevTeamProvisioning))
                {
                    // DevTeamProvisioning shouldn't be empty for arm64 builds
                    Utils.LogInfo("DevTeamProvisioning is not set, BuildAppBundle step is skipped.");
                }
                else
                {
                    AppBundlePath = Xcode.BuildAppBundle(
                        Path.Combine(binDir, ProjectName, ProjectName + ".xcodeproj"),
                        Arch, Optimized, DevTeamProvisioning);
                }
            }
        }

        return(true);
    }
Example #10
0
    public (string apk, string packageId) BuildApk(
        string abi,
        string mainLibraryFileName,
        string monoRuntimeHeaders)
    {
        if (string.IsNullOrEmpty(AppDir) || !Directory.Exists(AppDir))
        {
            throw new ArgumentException($"AppDir='{AppDir}' is empty or doesn't exist");
        }

        if (!string.IsNullOrEmpty(mainLibraryFileName) && !File.Exists(Path.Combine(AppDir, mainLibraryFileName)))
        {
            throw new ArgumentException($"MainLibraryFileName='{mainLibraryFileName}' was not found in AppDir='{AppDir}'");
        }

        if (string.IsNullOrEmpty(abi))
        {
            throw new ArgumentException("abi should not be empty (e.g. x86, x86_64, armeabi-v7a or arm64-v8a");
        }

        if (!string.IsNullOrEmpty(ProjectName) && ProjectName.Contains(' '))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' should not not contain spaces.");
        }

        if (string.IsNullOrEmpty(AndroidSdk))
        {
            AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidNdk))
        {
            AndroidNdk = Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
        {
            throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
        }

        if (string.IsNullOrEmpty(AndroidNdk) || !Directory.Exists(AndroidNdk))
        {
            throw new ArgumentException($"Android NDK='{AndroidNdk}' was not found or empty (can be set via ANDROID_NDK_ROOT envvar).");
        }

        if (ForceInterpreter && ForceAOT)
        {
            throw new InvalidOperationException("Interpreter and AOT cannot be enabled at the same time");
        }

        // Try to get the latest build-tools version if not specified
        if (string.IsNullOrEmpty(BuildToolsVersion))
        {
            BuildToolsVersion = GetLatestBuildTools(AndroidSdk);
        }

        // Try to get the latest API level if not specified
        if (string.IsNullOrEmpty(BuildApiLevel))
        {
            BuildApiLevel = GetLatestApiLevel(AndroidSdk);
        }

        if (string.IsNullOrEmpty(MinApiLevel))
        {
            MinApiLevel = DefaultMinApiLevel;
        }

        // make sure BuildApiLevel >= MinApiLevel
        // only if these api levels are not "preview" (not integers)
        if (int.TryParse(BuildApiLevel, out int intApi) &&
            int.TryParse(MinApiLevel, out int intMinApi) &&
            intApi < intMinApi)
        {
            throw new ArgumentException($"BuildApiLevel={BuildApiLevel} <= MinApiLevel={MinApiLevel}. " +
                                        "Make sure you've downloaded some recent build-tools in Android SDK");
        }

        string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);

        if (!Directory.Exists(buildToolsFolder))
        {
            throw new ArgumentException($"{buildToolsFolder} was not found.");
        }

        var assemblerFiles = new List <string>();

        foreach (ITaskItem file in Assemblies)
        {
            // use AOT files if available
            var obj = file.GetMetadata("AssemblerFile");
            if (!string.IsNullOrEmpty(obj))
            {
                assemblerFiles.Add(obj);
            }
        }

        if (ForceAOT && !assemblerFiles.Any())
        {
            throw new InvalidOperationException("Need list of AOT files.");
        }

        Directory.CreateDirectory(OutputDir);
        Directory.CreateDirectory(Path.Combine(OutputDir, "bin"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets-tozip"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));

        var extensionsToIgnore = new List <string> {
            ".so", ".a", ".gz"
        };

        if (StripDebugSymbols)
        {
            extensionsToIgnore.Add(".pdb");
            extensionsToIgnore.Add(".dbg");
        }

        // Copy sourceDir to OutputDir/assets-tozip (ignore native files)
        // these files then will be zipped and copied to apk/assets/assets.zip
        Utils.DirectoryCopy(AppDir, Path.Combine(OutputDir, "assets-tozip"), file =>
        {
            string fileName  = Path.GetFileName(file);
            string extension = Path.GetExtension(file);

            if (extensionsToIgnore.Contains(extension))
            {
                // ignore native files, those go to lib/%abi%
                // also, aapt is not happy about zip files
                return(false);
            }
            if (fileName.StartsWith("."))
            {
                // aapt complains on such files
                return(false);
            }
            return(true);
        });

        // tools:
        string dx               = Path.Combine(buildToolsFolder, "dx");
        string aapt             = Path.Combine(buildToolsFolder, "aapt");
        string zipalign         = Path.Combine(buildToolsFolder, "zipalign");
        string apksigner        = Path.Combine(buildToolsFolder, "apksigner");
        string androidJar       = Path.Combine(AndroidSdk, "platforms", "android-" + BuildApiLevel, "android.jar");
        string androidToolchain = Path.Combine(AndroidNdk, "build", "cmake", "android.toolchain.cmake");
        string javac            = "javac";
        string cmake            = "cmake";
        string zip              = "zip";

        Utils.RunProcess(zip, workingDir: Path.Combine(OutputDir, "assets-tozip"), args: "-q -r ../assets/assets.zip .");
        Directory.Delete(Path.Combine(OutputDir, "assets-tozip"), true);

        if (!File.Exists(androidJar))
        {
            throw new ArgumentException($"API level={BuildApiLevel} is not downloaded in Android SDK");
        }

        // 1. Build libmonodroid.so` via cmake

        string nativeLibraries = "";
        string monoRuntimeLib  = Path.Combine(AppDir, "libmonosgen-2.0.a");

        if (!File.Exists(monoRuntimeLib))
        {
            throw new ArgumentException($"libmonosgen-2.0.a was not found in {AppDir}");
        }
        else
        {
            nativeLibraries += $"{monoRuntimeLib}{Environment.NewLine}";
        }

        string[] staticComponentStubLibs = Directory.GetFiles(AppDir, "libmono-component-*-stub-static.a");
        bool     staticLinkAllComponents = false;

        string[] componentNames = Array.Empty <string>();

        if (!string.IsNullOrEmpty(StaticLinkedComponentNames) && StaticLinkedComponentNames.Equals("*", StringComparison.OrdinalIgnoreCase))
        {
            staticLinkAllComponents = true;
        }
        else if (!string.IsNullOrEmpty(StaticLinkedComponentNames))
        {
            componentNames = StaticLinkedComponentNames.Split(";");
        }

        // by default, component stubs will be linked and depending on how mono runtime has been build,
        // stubs can disable or dynamic load components.
        foreach (string staticComponentStubLib in staticComponentStubLibs)
        {
            string componentLibToLink = staticComponentStubLib;
            if (staticLinkAllComponents)
            {
                // static link component.
                componentLibToLink = componentLibToLink.Replace("-stub-static.a", "-static.a", StringComparison.OrdinalIgnoreCase);
            }
            else
            {
                foreach (string componentName in componentNames)
                {
                    if (componentLibToLink.Contains(componentName, StringComparison.OrdinalIgnoreCase))
                    {
                        // static link component.
                        componentLibToLink = componentLibToLink.Replace("-stub-static.a", "-static.a", StringComparison.OrdinalIgnoreCase);
                        break;
                    }
                }
            }

            // if lib doesn't exist (primarly due to runtime build without static lib support), fallback linking stub lib.
            if (!File.Exists(componentLibToLink))
            {
                Utils.LogInfo($"\nCouldn't find static component library: {componentLibToLink}, linking static component stub library: {staticComponentStubLib}.\n");
                componentLibToLink = staticComponentStubLib;
            }

            nativeLibraries += $"    {componentLibToLink}{Environment.NewLine}";
        }

        // There's a circular dependecy between static mono runtime lib and static component libraries.
        // Adding mono runtime lib before and after component libs will resolve issues with undefined symbols
        // due to circular dependecy.
        nativeLibraries += $"    {monoRuntimeLib}{Environment.NewLine}";

        string aotSources = "";

        foreach (string asm in assemblerFiles)
        {
            // these libraries are linked via modules.c
            aotSources += $"    {asm}{Environment.NewLine}";
        }

        string cmakeLists = Utils.GetEmbeddedResource("CMakeLists-android.txt")
                            .Replace("%MonoInclude%", monoRuntimeHeaders)
                            .Replace("%NativeLibrariesToLink%", nativeLibraries)
                            .Replace("%AotSources%", aotSources)
                            .Replace("%AotModulesSource%", string.IsNullOrEmpty(aotSources) ? "" : "modules.c");

        string defines = "";

        if (ForceInterpreter)
        {
            defines = "add_definitions(-DFORCE_INTERPRETER=1)";
        }
        else if (ForceAOT)
        {
            defines = "add_definitions(-DFORCE_AOT=1)";
        }

        cmakeLists = cmakeLists.Replace("%Defines%", defines);

        File.WriteAllText(Path.Combine(OutputDir, "CMakeLists.txt"), cmakeLists);

        File.WriteAllText(Path.Combine(OutputDir, "monodroid.c"), Utils.GetEmbeddedResource("monodroid.c"));

        string cmakeGenArgs = $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " +
                              $"-DANDROID_NATIVE_API_LEVEL={MinApiLevel} -B monodroid";

        string cmakeBuildArgs = "--build monodroid";

        if (StripDebugSymbols)
        {
            // Use "-s" to strip debug symbols, it complains it's unused but it works
            cmakeGenArgs   += " -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_C_FLAGS=\"-s -Wno-unused-command-line-argument\"";
            cmakeBuildArgs += " --config MinSizeRel";
        }
        else
        {
            cmakeGenArgs   += " -DCMAKE_BUILD_TYPE=Debug";
            cmakeBuildArgs += " --config Debug";
        }

        Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeGenArgs);
        Utils.RunProcess(cmake, workingDir: OutputDir, args: cmakeBuildArgs);

        // 2. Compile Java files

        string javaSrcFolder = Path.Combine(OutputDir, "src", "net", "dot");

        Directory.CreateDirectory(javaSrcFolder);

        string javaActivityPath = Path.Combine(javaSrcFolder, "MainActivity.java");
        string monoRunnerPath   = Path.Combine(javaSrcFolder, "MonoRunner.java");

        string packageId = $"net.dot.{ProjectName}";

        File.WriteAllText(javaActivityPath,
                          Utils.GetEmbeddedResource("MainActivity.java")
                          .Replace("%EntryPointLibName%", Path.GetFileName(mainLibraryFileName)));
        if (!string.IsNullOrEmpty(NativeMainSource))
        {
            File.Copy(NativeMainSource, javaActivityPath, true);
        }

        string monoRunner = Utils.GetEmbeddedResource("MonoRunner.java")
                            .Replace("%EntryPointLibName%", Path.GetFileName(mainLibraryFileName));

        File.WriteAllText(monoRunnerPath, monoRunner);

        File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
                          Utils.GetEmbeddedResource("AndroidManifest.xml")
                          .Replace("%PackageName%", packageId)
                          .Replace("%MinSdkLevel%", MinApiLevel));

        string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 ";

        Utils.RunProcess(javac, javaCompilerArgs + javaActivityPath, workingDir: OutputDir);
        Utils.RunProcess(javac, javaCompilerArgs + monoRunnerPath, workingDir: OutputDir);
        Utils.RunProcess(dx, "--dex --output=classes.dex obj", workingDir: OutputDir);

        // 3. Generate APK

        string debugModeArg = StripDebugSymbols ? string.Empty : "--debug-mode";
        string apkFile      = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk");

        Utils.RunProcess(aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar} {debugModeArg}", workingDir: OutputDir);

        var dynamicLibs = new List <string>();

        dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so"));
        dynamicLibs.AddRange(Directory.GetFiles(AppDir, "*.so").Where(file => Path.GetFileName(file) != "libmonodroid.so"));

        // add all *.so files to lib/%abi%/
        Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
        foreach (var dynamicLib in dynamicLibs)
        {
            string dynamicLibName = Path.GetFileName(dynamicLib);
            if (dynamicLibName == "libmonosgen-2.0.so")
            {
                // we link mono runtime statically into libmonodroid.so
                continue;
            }

            // NOTE: we can run android-strip tool from NDK to shrink native binaries here even more.

            string destRelative = Path.Combine("lib", abi, dynamicLibName);
            File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true);
            Utils.RunProcess(aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir);
        }
        Utils.RunProcess(aapt, $"add {apkFile} classes.dex", workingDir: OutputDir);

        // 4. Align APK

        string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");

        Utils.RunProcess(zipalign, $"-v 4 {apkFile} {alignedApk}", workingDir: OutputDir);
        // we don't need the unaligned one any more
        File.Delete(apkFile);

        // 5. Generate key (if needed) & sign the apk
        SignApk(alignedApk, apksigner);

        Utils.LogInfo($"\nAPK size: {(new FileInfo(alignedApk).Length / 1000_000.0):0.#} Mb.\n");

        return(alignedApk, packageId);
    }
Example #11
0
        protected virtual void OnButtonOkClicked(object sender, System.EventArgs e)
        {
            if (WorkspaceName.Contains(" "))
            {
                MessageDialogs md =
                    new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("error_whitespace_work"), "", Gtk.MessageType.Error);
                md.ShowDialog();
                return;
            }

            if (WorkspaceRoot.Contains(" "))
            {
                MessageDialogs md =
                    new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("error_whitespace_work_path"), "", Gtk.MessageType.Error);
                md.ShowDialog();
                return;
            }


            if (String.IsNullOrEmpty(WorkspaceName))
            {
                MessageDialogs md =
                    new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("please_set_workspace_name"), "", Gtk.MessageType.Error);
                md.ShowDialog();
                return;
            }

            if (String.IsNullOrEmpty(WorkspaceOutput))
            {
                MessageDialogs md =
                    new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("please_set_workspace_output"), "", Gtk.MessageType.Error);
                md.ShowDialog();
                return;
            }

            if (String.IsNullOrEmpty(WorkspaceRoot))
            {
                MessageDialogs md =
                    new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("please_set_workspace_root"), "", Gtk.MessageType.Error);
                md.ShowDialog();
                return;
            }

            if (project)
            {
                if (String.IsNullOrEmpty(ProjectName))
                {
                    MessageDialogs md =
                        new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("please_set_project_name"), "", Gtk.MessageType.Error);
                    md.ShowDialog();
                    return;
                }

                if (ProjectName.Contains(" "))
                {
                    MessageDialogs md =
                        new MessageDialogs(MessageDialogs.DialogButtonType.Ok, MainClass.Languages.Translate("error_whitespace_proj"), "", Gtk.MessageType.Error);
                    md.ShowDialog();
                    return;
                }
            }

            this.Respond(ResponseType.Ok);
        }
Example #12
0
    public (string apk, string packageId) BuildApk(
        string sourceDir, string abi, string entryPointLib, string monoRuntimeHeaders)
    {
        if (!Directory.Exists(sourceDir))
        {
            throw new ArgumentException($"sourceDir='{sourceDir}' is empty or doesn't exist");
        }

        if (string.IsNullOrEmpty(abi))
        {
            throw new ArgumentException("abi shoudln't be empty (e.g. x86, x86_64, armeabi, armeabi-v7a or arm64-v8a");
        }

        if (string.IsNullOrEmpty(entryPointLib))
        {
            throw new ArgumentException("entryPointLib shouldn't be empty");
        }

        if (!File.Exists(Path.Combine(sourceDir, entryPointLib)))
        {
            throw new ArgumentException($"{entryPointLib} was not found in sourceDir='{sourceDir}'");
        }

        if (string.IsNullOrEmpty(ProjectName))
        {
            ProjectName = Path.GetFileNameWithoutExtension(entryPointLib);
        }

        if (string.IsNullOrEmpty(OutputDir))
        {
            OutputDir = Path.Combine(sourceDir, "bin-" + abi);
        }

        if (ProjectName.Contains(' '))
        {
            throw new ArgumentException($"ProjectName='{ProjectName}' shouldn't not contain spaces.");
        }

        if (string.IsNullOrEmpty(AndroidSdk))
        {
            AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidNdk))
        {
            AndroidNdk = Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT");
        }

        if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
        {
            throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
        }

        if (string.IsNullOrEmpty(AndroidNdk) || !Directory.Exists(AndroidNdk))
        {
            throw new ArgumentException($"Android NDK='{AndroidNdk}' was not found or empty (can be set via ANDROID_NDK_ROOT envvar).");
        }

        // Try to get the latest build-tools version if not specified
        if (string.IsNullOrEmpty(BuildToolsVersion))
        {
            BuildToolsVersion = GetLatestBuildTools(AndroidSdk);
        }

        // Try to get the latest API level if not specified
        if (string.IsNullOrEmpty(BuildApiLevel))
        {
            BuildApiLevel = GetLatestApiLevel(AndroidSdk);
        }

        if (string.IsNullOrEmpty(MinApiLevel))
        {
            MinApiLevel = DefaultMinApiLevel;
        }

        // make sure BuildApiLevel >= MinApiLevel
        // only if these api levels are not "preview" (not integers)
        if (int.TryParse(BuildApiLevel, out int intApi) &&
            int.TryParse(MinApiLevel, out int intMinApi) &&
            intApi < intMinApi)
        {
            throw new ArgumentException($"BuildApiLevel={BuildApiLevel} <= MinApiLevel={MinApiLevel}. " +
                                        "Make sure you've downloaded some recent build-tools in Android SDK");
        }

        string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);

        if (!Directory.Exists(buildToolsFolder))
        {
            throw new ArgumentException($"{buildToolsFolder} was not found.");
        }

        Directory.CreateDirectory(OutputDir);
        Directory.CreateDirectory(Path.Combine(OutputDir, "bin"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "obj"));
        Directory.CreateDirectory(Path.Combine(OutputDir, "assets"));

        // Copy AppDir to OutputDir/assets (ignore native files)
        Utils.DirectoryCopy(sourceDir, Path.Combine(OutputDir, "assets"), file =>
        {
            var extension = Path.GetExtension(file);
            // ignore native files, those go to lib/%abi%
            if (extension == ".so" || extension == ".a")
            {
                // ignore ".pdb" and ".dbg" to make APK smaller
                return(false);
            }
            return(true);
        });

        // tools:
        string dx               = Path.Combine(buildToolsFolder, "dx");
        string aapt             = Path.Combine(buildToolsFolder, "aapt");
        string zipalign         = Path.Combine(buildToolsFolder, "zipalign");
        string apksigner        = Path.Combine(buildToolsFolder, "apksigner");
        string androidJar       = Path.Combine(AndroidSdk, "platforms", "android-" + BuildApiLevel, "android.jar");
        string androidToolchain = Path.Combine(AndroidNdk, "build", "cmake", "android.toolchain.cmake");
        string keytool          = "keytool";
        string javac            = "javac";
        string cmake            = "cmake";

        if (!File.Exists(androidJar))
        {
            throw new ArgumentException($"API level={BuildApiLevel} is not downloaded in Android SDK");
        }

        // 1. Build libruntime-android.so` via cmake

        string monoRuntimeLib = Path.Combine(sourceDir, "libmonosgen-2.0.a");

        if (!File.Exists(monoRuntimeLib))
        {
            throw new ArgumentException($"libmonosgen-2.0.a was not found in {sourceDir}");
        }

        string cmakeLists = Utils.GetEmbeddedResource("CMakeLists-android.txt")
                            .Replace("%MonoInclude%", monoRuntimeHeaders)
                            .Replace("%NativeLibrariesToLink%", monoRuntimeLib);

        File.WriteAllText(Path.Combine(OutputDir, "CMakeLists.txt"), cmakeLists);

        string runtimeAndroidSrc = Utils.GetEmbeddedResource("runtime-android.c")
                                   .Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib));

        File.WriteAllText(Path.Combine(OutputDir, "runtime-android.c"), runtimeAndroidSrc);

        Utils.RunProcess(cmake, workingDir: OutputDir,
                         args: $"-DCMAKE_TOOLCHAIN_FILE={androidToolchain} -DANDROID_ABI=\"{abi}\" -DANDROID_STL=none " +
                         $"-DANDROID_NATIVE_API_LEVEL={MinApiLevel} -B runtime-android");
        Utils.RunProcess("make", workingDir: Path.Combine(OutputDir, "runtime-android"));

        // 2. Compile Java files

        string javaSrcFolder = Path.Combine(OutputDir, "src", "net", "dot");

        Directory.CreateDirectory(javaSrcFolder);

        string packageId = $"net.dot.{ProjectName}";

        File.WriteAllText(Path.Combine(javaSrcFolder, "MainActivity.java"),
                          Utils.GetEmbeddedResource("MainActivity.java"));
        File.WriteAllText(Path.Combine(javaSrcFolder, "MonoRunner.java"),
                          Utils.GetEmbeddedResource("MonoRunner.java"));
        File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
                          Utils.GetEmbeddedResource("AndroidManifest.xml")
                          .Replace("%PackageName%", packageId)
                          .Replace("%MinSdkLevel%", MinApiLevel));

        string javaCompilerArgs = $"-d obj -classpath src -bootclasspath {androidJar} -source 1.8 -target 1.8 ";

        Utils.RunProcess(javac, javaCompilerArgs + Path.Combine(javaSrcFolder, "MainActivity.java"), workingDir: OutputDir);
        Utils.RunProcess(javac, javaCompilerArgs + Path.Combine(javaSrcFolder, "MonoRunner.java"), workingDir: OutputDir);
        Utils.RunProcess(dx, "--dex --output=classes.dex obj", workingDir: OutputDir);

        // 3. Generate APK

        string apkFile = Path.Combine(OutputDir, "bin", $"{ProjectName}.unaligned.apk");

        Utils.RunProcess(aapt, $"package -f -m -F {apkFile} -A assets -M AndroidManifest.xml -I {androidJar}", workingDir: OutputDir);

        var dynamicLibs = new List <string>();

        dynamicLibs.Add(Path.Combine(OutputDir, "runtime-android", "libruntime-android.so"));
        dynamicLibs.AddRange(Directory.GetFiles(sourceDir, "*.so"));

        // add all *.so files to lib/%abi%/
        Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
        foreach (var dynamicLib in dynamicLibs)
        {
            string destRelative = Path.Combine("lib", abi, Path.GetFileName(dynamicLib));
            File.Copy(dynamicLib, Path.Combine(OutputDir, destRelative), true);
            Utils.RunProcess(aapt, $"add {apkFile} {destRelative}", workingDir: OutputDir);
        }
        Utils.RunProcess(aapt, $"add {apkFile} classes.dex", workingDir: OutputDir);

        // 4. Align APK

        string alignedApk = Path.Combine(OutputDir, "bin", $"{ProjectName}.apk");

        Utils.RunProcess(zipalign, $"-v 4 {apkFile} {alignedApk}", workingDir: OutputDir);

        // 5. Generate key

        string signingKey = Path.Combine(OutputDir, "debug.keystore");

        if (!File.Exists(signingKey))
        {
            Utils.RunProcess(keytool, "-genkey -v -keystore debug.keystore -storepass android -alias " +
                             "androiddebugkey -keypass android -keyalg RSA -keysize 2048 -noprompt " +
                             "-dname \"CN=Android Debug,O=Android,C=US\"", workingDir: OutputDir, silent: true);
        }

        // 6. Sign APK

        Utils.RunProcess(apksigner, $"sign --min-sdk-version {MinApiLevel} --ks debug.keystore " +
                         $"--ks-pass pass:android --key-pass pass:android {alignedApk}", workingDir: OutputDir);

        return(alignedApk, packageId);
    }
        // -------------------------------------------------------------------
        // CreateProject
        // -------------------------------------------------------------------

        public string CreateProject()
        {
            if (Directory.Exists(DirPath))
            {
                if (ProjectName.Contains("/") || ProjectName.Contains("\\") || ProjectName.Contains(":") || ProjectName.Contains("*") || ProjectName.Contains("?") || ProjectName.Contains("<") || ProjectName.Contains(">") || ProjectName.Contains("\"") || ProjectName.Contains("|") || ProjectName.Trim().Equals("") || ProjectName.Replace('.', ' ').Trim().Equals("") || ProjectName.Contains("..") || ProjectName.Trim()[ProjectName.Trim().Length - 1] == '.')
                {
                    return("Could not create a directory with that name.Do not use / \\ : ? * | < > \". You can't name with an empty field, or \".\" or \"..\" field.");
                }
                else
                {
                    string fullPath = Path.Combine(DirPath, ProjectName);
                    if (!Directory.Exists(fullPath))
                    {
                        try
                        {
                            Directory.CreateDirectory(fullPath);
                            try
                            {
                                string executablePath = Path.GetDirectoryName(WANOK.ExcecutablePath);
                                string basicPath      = Path.Combine(executablePath, "Basic");
                                WANOK.CopyAll(new DirectoryInfo(basicPath), new DirectoryInfo(fullPath));
                                WANOK.SaveBinaryDatas(new SystemDatas(ProjectName), Path.Combine(fullPath, "Content", "Datas", "System.rpmd"));
                                WANOK.SaveBinaryDatas(new BattleSystemDatas(), Path.Combine(fullPath, "Content", "Datas", "BattleSystem.rpmd"));
                                WANOK.SaveBinaryDatas(new TilesetsDatas(), Path.Combine(fullPath, "Content", "Datas", "Tilesets.rpmd"));
                                WANOK.SaveBinaryDatas(new HeroesDatas(), Path.Combine(fullPath, "Content", "Datas", "Heroes.rpmd"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "Textures2D"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "Textures2D", "Characters"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "Textures2D", "Tilesets"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "Textures2D", "Autotiles"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "Textures2D", "Reliefs"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "UI", "Others"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "UI", "Icons"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Pictures", "UI", "Bars"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Datas", "Maps", "MAP0001"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Datas", "Maps", "MAP0001", "temp"));
                                Directory.CreateDirectory(Path.Combine(fullPath, "Content", "Datas", "Maps", "MAP0001", "tempCancelRedo"));
                                WANOK.SaveBinaryDatas(new MapInfos(), Path.Combine(fullPath, "Content", "Datas", "Maps", "MAP0001", "infos.map"));
                                WANOK.SaveBinaryDatas(new Events(), Path.Combine(fullPath, "Content", "Datas", "Maps", "MAP0001", "events.map"));
                                ProjectName = ProjectName.Trim();
                                DirPath     = Path.Combine(DirPath, ProjectName);

                                return(null);
                            }
                            catch
                            {
                                return("Could not generate the project. See if you have \"Basic\" folder in the main folder.");
                            }
                        }
                        catch
                        {
                            return("Could not create the directory. Report it to the dev.");
                        }
                    }
                    else
                    {
                        return("This project already exist in this folder. Please change your project name or your folder.");
                    }
                }
            }
            else
            {
                return("The directory path is incorrect.");
            }
        }