Skip to content

Commit

Permalink
Merge pull request #964 from rflechner/AndroidAbiSpecific
Browse files Browse the repository at this point in the history
Android abi specific
  • Loading branch information
forki committed Oct 7, 2015
2 parents 2d00585 + 6b2dae0 commit a8f4964
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 13 deletions.
34 changes: 32 additions & 2 deletions help/androidpublisher.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,40 @@ https://developers.google.com/android-publisher/getting_started
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"
Expand Down Expand Up @@ -128,4 +159,3 @@ Default target will not start "Publish" target because apps do not need to be up
To publish your app, you can run

PS> Fake.exe .\build.fsx "target=publish"

150 changes: 139 additions & 11 deletions src/app/FakeLib/XamarinHelper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Fake.XamarinHelper
open System
open System.IO
open System.Text.RegularExpressions
open System.Xml.Linq
open System.Xml
open System.Text

let private executeCommand command args =
Shell.Exec(command, args)
Expand Down Expand Up @@ -60,6 +63,34 @@ let iOSBuildDefaults = {
Properties = []
}


type AndroidAbiTargetConfig = {
SuffixAndExtension: string
}

type AndroidAbiTarget =
| X86 of AndroidAbiTargetConfig
| ArmEabi of AndroidAbiTargetConfig
| ArmEabiV7a of AndroidAbiTargetConfig
| Arm64V8a of AndroidAbiTargetConfig
| X86And64 of AndroidAbiTargetConfig
| AllAbi

type AndroidPackageAbiParam =
| OneApkForAll
| SpecificAbis of AndroidAbiTarget list

let AllAndroidAbiTargets =
AndroidPackageAbiParam.SpecificAbis
( [ AndroidAbiTarget.X86({ SuffixAndExtension="-x86.apk"; })
AndroidAbiTarget.ArmEabi({ SuffixAndExtension="-armeabi.apk"; })
AndroidAbiTarget.ArmEabiV7a({ SuffixAndExtension="-armeabi-v7a.apk"; })
AndroidAbiTarget.Arm64V8a({ SuffixAndExtension="-arm64-v8a.apk"; })
AndroidAbiTarget.X86And64({ SuffixAndExtension="-x86_64.apk"; })
] )

type IncrementerVersion = int32 -> AndroidAbiTarget -> int32

/// Builds a project or solution using Xamarin's iOS build tools
/// ## Parameters
/// - `setParams` - Function used to override the default build parameters
Expand Down Expand Up @@ -95,6 +126,10 @@ type AndroidPackageParams = {
OutputPath: string
/// Additional MSBuild properties, defaults to empty list
Properties: (string * string) list
/// Build an APK Targetting One ABI (used to reduce the size of the APK and support different CPU architectures)
PackageAbiTargets: AndroidPackageAbiParam
/// Used for multiple APK packaging to set different version code par ABI
VersionStepper:IncrementerVersion option
}

/// The default Android packaging parameters
Expand All @@ -103,12 +138,14 @@ let AndroidPackageDefaults = {
Configuration = "Release"
OutputPath = "bin/Release"
Properties = []
PackageAbiTargets = AndroidPackageAbiParam.OneApkForAll
VersionStepper = None
}

/// Packages a Xamarin.Android app, returning a FileInfo object for the unsigned APK file
/// Packages a Xamarin.Android app, returning a multiple FileInfo objects for the unsigned APK files
/// ## Parameters
/// - `setParams` - Function used to override the default build parameters
let AndroidPackage setParams =
let AndroidBuildPackages setParams =
let validateParams param =
if param.ProjectPath = "" then failwith "You must specify a project to package"
if param.Properties
Expand All @@ -117,20 +154,104 @@ let AndroidPackage setParams =

param

let buildPackages param (abi:string option) (manifestFile:string option) =
let options = match (abi,manifestFile) with
| Some a, Some m -> let manifest = @"Properties" @@ System.IO.Path.GetFileName(m)
[ "Configuration", param.Configuration
"AndroidSupportedAbis", a
"AndroidManifest", manifest ]
| Some a, None -> [ "Configuration", param.Configuration
"AndroidSupportedAbis", a]
| _, _ -> [ "Configuration", param.Configuration ]
MSBuild param.OutputPath "PackageForAndroid" options [ param.ProjectPath ] |> ignore

let rewriteManifestFile (manifestFile:string) outfile (transformVersion:IncrementerVersion) target =
let manifest = XDocument.Load(manifestFile)
let vc = manifest.Element("manifest" |> XName.Get).Attributes() |> Seq.filter(fun a -> a.Name.LocalName = "versionCode") |> Seq.exactlyOne
let v = transformVersion (Convert.ToInt32(vc.Value)) target
vc.Value <- v.ToString()
use fs = new FileStream(outfile, FileMode.OpenOrCreate)
use wr = new XmlTextWriter(fs, Encoding.UTF8)
wr.Formatting <- Formatting.Indented
manifest.Save(wr)

let mostRecentFileInDirMatching path =
directoryInfo path
|> filesInDirMatching "*.apk"
|> Seq.sortBy (fun file -> file.LastWriteTime)
|> Seq.last

let createPackage param =
let effectiveProperties = [ "Configuration", param.Configuration ] @ param.Properties

MSBuild param.OutputPath "PackageForAndroid" effectiveProperties [ param.ProjectPath ] |> ignore
[ mostRecentFileInDirMatching param.OutputPath ]

let buildSpecificApk param manifestFile name transformVersion target =
let specificManifest = (manifestFile |> Path.GetDirectoryName) @@ ("AndroidManifest-" + name + ".xml")
rewriteManifestFile manifestFile specificManifest transformVersion target
// workaround for xamarin bug: https://bugzilla.xamarin.com/show_bug.cgi?id=30571
let backupFn = (manifestFile |> Path.GetDirectoryName) @@ ("AndroidManifest-original.xml")
CopyFile backupFn manifestFile
CopyFile manifestFile specificManifest
try
//buildPackages param (Some name) (Some specificManifest) // to uncomment after xamarin fix there bug
buildPackages param (Some name) None
finally
CopyFile manifestFile backupFn

let translateAbi = function
| AndroidAbiTarget.X86 _ -> "x86"
| AndroidAbiTarget.ArmEabi _ -> "armeabi"
| AndroidAbiTarget.ArmEabiV7a _ -> "armeabi-v7a"
| AndroidAbiTarget.Arm64V8a _ -> "arm64-v8a"
| AndroidAbiTarget.X86And64 _ -> "X86_64"
| _ -> ""

let createTargetPackage param (manifestFile:string) (target:AndroidAbiTarget) transformVersion =
let name = target |> translateAbi
match target with
| AndroidAbiTarget.X86 c
| AndroidAbiTarget.ArmEabi c
| AndroidAbiTarget.ArmEabiV7a c
| AndroidAbiTarget.Arm64V8a c
| AndroidAbiTarget.X86And64 c -> buildSpecificApk param manifestFile name transformVersion target
| _ -> buildPackages param None None

let createPackageAbiSpecificApk param (targets:AndroidAbiTarget list) transformVersion =
let manifestPath = (param.ProjectPath |> Path.GetDirectoryName) @@ @"Properties" @@ "AndroidManifest.xml"
seq { for t in targets do
createTargetPackage param manifestPath t transformVersion
let apk = mostRecentFileInDirMatching param.OutputPath
let name = t |> translateAbi
if name.Length > 0 then
let apkname = Path.GetFileNameWithoutExtension(apk.Name) + "-" + name + ".apk"
yield apk.CopyTo (param.OutputPath @@ apkname)
else
yield apk
} |> Seq.toList

let param = AndroidPackageDefaults |> setParams |> validateParams

let transformVersion = match param.VersionStepper with
| Some f -> f
| None -> (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)

match param.PackageAbiTargets with
| AndroidPackageAbiParam.OneApkForAll -> param |> createPackage
| AndroidPackageAbiParam.SpecificAbis targets -> createPackageAbiSpecificApk param targets transformVersion

directoryInfo param.OutputPath
|> filesInDirMatching "*.apk"
|> Seq.sortBy (fun file -> file.LastWriteTime)
|> Seq.last

AndroidPackageDefaults
|> setParams
|> validateParams
|> createPackage
/// Packages a Xamarin.Android app, returning a FileInfo object for the unsigned APK file
/// ## Parameters
/// - `setParams` - Function used to override the default build parameters
let AndroidPackage setParams =
AndroidBuildPackages setParams |> Seq.exactlyOne

// Parameters for signing and aligning an Android package
type AndroidSignAndAlignParams = {
Expand Down Expand Up @@ -187,6 +308,13 @@ let AndroidSignAndAlign setParams apkFile =
|> validateParams
|> signAndAlign apkFile

/// Signs and aligns multiple Xamarin.Android packages, returning multiple FileInfo objects for the signed APK file
/// ## Parameters
/// - `setParams` - Function used to override the default build parameters
/// - `apkFiles` - FileInfo object for an unsigned APK file to sign and align
let AndroidSignAndAlignPackages setParams apkFiles =
apkFiles |> Seq.map (fun f -> AndroidSignAndAlign setParams f)

/// The iOS archive paramater type
type iOSArchiveParams = {
/// Path to desired solution file. If not provided, mdtool finds the first solution in the current directory.
Expand Down

0 comments on commit a8f4964

Please sign in to comment.