Publish Android apk

Note: This documentation is for FAKE.exe before version 5 (or the non-netcore version). The documentation needs te be updated, please help!

This module helps android developers to automatically publish their APKs

Prerequisite

Before using this module, you will need an android keystore for apk signing.

Next, you will need a Google service account described here: https://developers.google.com/accounts/docs/OAuth2ServiceAccount#creatinganaccount

and here: https://developers.google.com/android-publisher/getting_started

Usage example:

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
#r "packages/FAKE/tools/FakeLib.dll"

let androidBuildDir = "./build/"
let androidProdDir = "./pack/"

androidProdDir |> ensureDirectory

//Clean old apk
Target "Clean" (fun _ ->
    CleanDir androidBuildDir
    CleanDir androidProdDir
)

Target "Android-Package" (fun () ->
    AndroidPackage(fun defaults ->
                    { defaults with 
                        ProjectPath = "Path to my project Droid.csproj"
                        Configuration = "Release"
                        OutputPath = androidBuildDir
						Properties = ["MSBuild property", "MSBuild property value"]
                    })

    |> AndroidSignAndAlign (fun defaults ->
        { defaults with 
            KeystorePath = @"path to my file.keystore"
            KeystorePassword = "my password"
            KeystoreAlias = "my key alias"
        })
    |> fun file -> file.CopyTo(Path.Combine(androidProdDir, file.Name)) |> ignore
)

// You can also build one APK per ABI
Target "Android-MultiPackages" (fun () ->
	let versionStepper = (fun v t -> match t with
									 | AndroidAbiTarget.X86 c -> v + 1
									 | AndroidAbiTarget.X86And64 c -> v + 2
									 | AndroidAbiTarget.ArmEabi c -> v + 3
									 | AndroidAbiTarget.ArmEabiV7a c -> v + 4
									 | AndroidAbiTarget.Arm64V8a c -> v + 5
									 | _ -> v)
	let abis = AndroidPackageAbiParam.SpecificAbis
					([ AndroidAbiTarget.X86({ SuffixAndExtension="-x86.apk"; })
					   AndroidAbiTarget.ArmEabi({ SuffixAndExtension="-armeabi.apk"; })
					   AndroidAbiTarget.ArmEabiV7a({ SuffixAndExtension="-armeabi-v7a.apk"; })
					   AndroidAbiTarget.X86And64({ SuffixAndExtension="-x86_64.apk"; })
					 ])
	let files = AndroidBuildPackages(fun defaults ->
							{ defaults with 
								ProjectPath = "Path to my project Droid.csproj"
								Configuration = "Release"
								OutputPath = androidBuildDir
								PackageAbiTargets = abis
								VersionStepper = Some(versionStepper)
							})

	for f in files do
		printfn "- apk: %s" f.Name

	files 
	|> Seq.iter (fun file -> file.CopyTo(Path.Combine(androidProdDir, file.Name)) |> ignore)
)


Target "Publish" (fun _ -> 
    // I like verbose script
    trace "publishing Android App"
    let apk = androidProdDir 
                    |> directoryInfo 
                    |> filesInDir 
                    |> Seq.filter(fun f -> f.Name.EndsWith(".apk"))
                    |> Seq.exactlyOne
    let apkPath = apk.FullName
    tracefn "Apk found: %s" apkPath
    let mail = "my service account [email protected]"
    // Path to the certificate file probably named 'Google Play Android Developer-xxxxxxxxxxxx.p12'
    let certificate = new X509Certificate2
                                (
                                    @"Google Play Android Developer-xxxxxxxxxxxx.p12",
                                    "notasecret",
                                    X509KeyStorageFlags.Exportable
                                )
    let packageName = "my Android package name"

    // to publish an alpha version: 
    PublishApk 
        { AlphaSettings with 
            Config = 
                { 
                    Certificate = certificate;
                    PackageName = packageName;
                    AccountId = mail;
                    Apk = apkPath; 
                }
        }

    // to publish a beta version: 
    //
    //PublishApk 
    //    { BetaSettings with 
    //        Config = 
    //            { 
    //                Certificate = certificate;
    //                PackageName = packageName;
    //                AccountId = mail;
    //                Apk = apkPath; 
    //            }
    //    }
    
    // to publish a production version: 
    //
    //PublishApk 
    //    { ProductionSettings with 
    //        Config = 
    //            { 
    //                Certificate = certificate;
    //                PackageName = packageName;
    //                AccountId = mail;
    //                Apk = apkPath; 
    //            }
    //    }
)

Target "Android-Build" (fun _ ->
    !! "**/my project Droid.csproj"
        |> MSBuildRelease androidBuildDir "Build"
        |> Log "BuildAndroidLib-Output: "
)

Target "Default" (fun _ ->
    trace "Building default target"
    RestorePackages()
)

"Clean"
    ==> "Android-Package"
    ==> "Default"

RunTargetOrDefault "Default"

Default target will not start "Publish" target because apps do not need to be updated too frequently (as explained here: https://developers.google.com/android-publisher/api_usage)

To publish your app, you can run

1: 
PS> Fake.exe .
val androidBuildDir : string

Full name: todoandroidpublisher.androidBuildDir
val androidProdDir : string

Full name: todoandroidpublisher.androidProdDir
val file : obj
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
union case Option.Some: Value: 'T -> Option<'T>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
module Seq

from Microsoft.FSharp.Collections
val iter : action:('T -> unit) -> source:seq<'T> -> unit

Full name: Microsoft.FSharp.Collections.Seq.iter
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.filter
val exactlyOne : source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.exactlyOne