forked from nuttycom/sbt-proguard-plugin
/
AndroidProject.scala
220 lines (187 loc) · 10.2 KB
/
AndroidProject.scala
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import proguard.{Configuration=>ProGuardConfiguration, ProGuard, ConfigurationParser}
import io.Source
import java.io._
import sbt._
import Process._
object AndroidProject {
val DefaultAaptName = "aapt"
val DefaultAdbName = "adb"
val DefaultAidlName = "aidl"
val DefaultApkbuilderName = "apkbuilder"
val DefaultDxName = "dx"
val DefaultAndroidManifestName = "AndroidManifest.xml"
val DefaultAndroidJarName = "android.jar"
val DefaultMapsJarName = "maps.jar"
val DefaultAssetsDirectoryName = "assets"
val DefaultResDirectoryName = "res"
val DefaultClassesMinJarName = "classes.min.jar"
val DefaultClassesDexName = "classes.dex"
val DefaultResourcesApkName = "resources.apk"
val DefaultDxJavaOpts = "-JXmx512m"
}
abstract class AndroidProject(info: ProjectInfo) extends DefaultProject(info) {
def proguardOption = ""
def proguardInJars = runClasspath --- proguardExclude
def proguardExclude = libraryJarPath +++ mainCompilePath +++ mainResourcesPath +++ managedClasspath(Configurations.Provided)
def libraryJarPath = androidJarPath +++ addonsJarPath
override def unmanagedClasspath = super.unmanagedClasspath +++ libraryJarPath
import AndroidProject._
def androidPlatformName:String
def aaptName = DefaultAaptName // note: this is a .exe file in windows
def adbName = DefaultAdbName
def aidlName = DefaultAidlName
def apkbuilderName = DefaultApkbuilderName + osBatchSuffix
def dxName = DefaultDxName + osBatchSuffix
def androidManifestName = DefaultAndroidManifestName
def androidJarName = DefaultAndroidJarName
def mapsJarName = DefaultMapsJarName
def assetsDirectoryName = DefaultAssetsDirectoryName
def resDirectoryName = DefaultResDirectoryName
def classesMinJarName = DefaultClassesMinJarName
def classesDexName = DefaultClassesDexName
def packageApkName = artifactBaseName + ".apk"
def resourcesApkName = DefaultResourcesApkName
def dxJavaOpts = DefaultDxJavaOpts
def scalaHomePath = Path.fromFile(new File(System.getProperty("scala.home")))
def androidSdkPath = {
val sdk = System.getenv("ANDROID_SDK_HOME")
if (sdk == null) error("You need to set ANDROID_SDK_HOME")
Path.fromFile(new File(sdk))
}
def apiLevel = minSdkVersion.getOrElse(platformName2ApiLevel)
def isWindows = System.getProperty("os.name").startsWith("Windows")
def osBatchSuffix = if (isWindows) ".bat" else ""
def dxMemoryParameter = {
// per http://code.google.com/p/android/issues/detail?id=4217, dx.bat
// doesn't currently support -JXmx arguments. For now, omit them in windows.
if (isWindows) "" else dxJavaOpts
}
def platformName2ApiLevel:Int = androidPlatformName match {
case "android-1.0" => 1
case "android-1.1" => 2
case "android-1.5" => 3
case "android-1.6" => 4
case "android-2.0" => 5
}
def androidToolsPath = androidSdkPath / "tools"
def apkbuilderPath = androidToolsPath / apkbuilderName
def adbPath = androidToolsPath / adbName
def androidPlatformPath = androidSdkPath / "platforms" / androidPlatformName
def platformToolsPath = androidPlatformPath / "tools"
def aaptPath = platformToolsPath / aaptName
def aidlPath = platformToolsPath / aidlName
def dxPath = platformToolsPath / dxName
def androidManifestPath = mainSourcePath / androidManifestName
def androidJarPath = androidPlatformPath / androidJarName
def addonsPath = androidSdkPath / "add-ons" / ("google_apis-" + apiLevel) / "libs"
def mapsJarPath = addonsPath / DefaultMapsJarName
def mainAssetsPath = mainSourcePath / assetsDirectoryName
def mainResPath = mainSourcePath / resDirectoryName
def classesMinJarPath = outputPath / classesMinJarName
def classesDexPath = outputPath / classesDexName
def resourcesApkPath = outputPath / resourcesApkName
def packageApkPath = outputPath / packageApkName
lazy val aaptGenerate = aaptGenerateAction
def aaptGenerateAction = aaptGenerateTask describedAs("Generate R.java.")
def aaptGenerateTask = execTask {<x>
{aaptPath.absolutePath} package -m -M {androidManifestPath.absolutePath} -S {mainResPath.absolutePath}
-I {androidJarPath.absolutePath} -J {mainJavaSourcePath.absolutePath}
</x>} dependsOn directory(mainJavaSourcePath)
lazy val aidl = aidlAction
def aidlAction = aidlTask describedAs("Generate Java classes from .aidl files.")
def aidlTask = execTask {
val aidlPaths = descendents(mainSourceRoots, "*.aidl").getPaths
if(aidlPaths.isEmpty)
Process(true)
else
aidlPath.absolutePath :: "-o" :: mainJavaSourcePath.absolutePath :: aidlPaths.toList
}
override def compileAction = super.compileAction dependsOn(aaptGenerate, aidl)
/** Projects using xsbt may want to ovrride and return `buildScalaInstance.libraryJar` */
def scalaLibraryJar = FileUtilities.scalaLibraryJar
lazy val proguard = proguardAction
def proguardAction = proguardTask dependsOn(compile) describedAs("Optimize class files.")
def proguardTask = task {
val args = "-injars" :: mainCompilePath.absolutePath+File.pathSeparator+
scalaLibraryJar.getAbsolutePath+"(!META-INF/MANIFEST.MF,!library.properties)"+
(if (!proguardInJars.getPaths.isEmpty) File.pathSeparator+proguardInJars.getPaths.map(_+"(!META-INF/MANIFEST.MF)").mkString(File.pathSeparator) else "") ::
"-outjars" :: classesMinJarPath.absolutePath ::
"-libraryjars" :: libraryJarPath.getPaths.mkString(File.pathSeparator) ::
"-dontwarn" :: "-dontoptimize" :: "-dontobfuscate" ::
"-keep public class * extends android.app.Activity" ::
"-keep public class * extends android.app.Service" ::
"-keep public class * extends android.appwidget.AppWidgetProvider" ::
"-keep public class * implements junit.framework.Test { public void test*(); }" :: proguardOption :: Nil
val config = new ProGuardConfiguration
new ConfigurationParser(args.toArray[String], info.projectPath.asFile).parse(config)
new ProGuard(config).execute
None
}
lazy val dx = dxAction
def dxAction = dxTask dependsOn(proguard) describedAs("Convert class files to dex files")
def dxTask = fileTask(classesDexPath from classesMinJarPath) {
execTask {<x> {dxPath.absolutePath} {dxMemoryParameter}
--dex --output={classesDexPath.absolutePath} {classesMinJarPath.absolutePath}
</x> } run }
lazy val aaptPackage = aaptPackageAction
def aaptPackageAction = aaptPackageTask dependsOn(dx) describedAs("Package resources and assets.")
def aaptPackageTask = execTask {<x>
{aaptPath.absolutePath} package -f -M {androidManifestPath.absolutePath} -S {mainResPath.absolutePath}
-A {mainAssetsPath.absolutePath} -I {androidJarPath.absolutePath} -F {resourcesApkPath.absolutePath}
</x>} dependsOn directory(mainAssetsPath)
lazy val packageDebug = packageDebugAction
def packageDebugAction = packageTask(true) dependsOn(aaptPackage) describedAs("Package and sign with a debug key.")
lazy val packageRelease = packageReleaseAction
def packageReleaseAction = packageTask(false) dependsOn(aaptPackage) describedAs("Package without signing.")
lazy val cleanApk = cleanTask(packageApkPath) describedAs("Remove apk package")
def packageTask(signPackage: Boolean) = execTask {<x>
{apkbuilderPath.absolutePath} {packageApkPath.absolutePath}
{if (signPackage) "" else "-u"} -z {resourcesApkPath.absolutePath} -f {classesDexPath.absolutePath}
{proguardInJars.get.map(" -rj " + _.absolutePath)}
</x>} dependsOn(cleanApk)
lazy val installEmulator = installEmulatorAction
def installEmulatorAction = installTask(true) dependsOn(packageDebug) describedAs("Install package on the default emulator.")
lazy val installDevice = installDeviceAction
def installDeviceAction = installTask(false) dependsOn(packageDebug) describedAs("Install package on the default device.")
lazy val reinstallEmulator = reinstallEmulatorAction
def reinstallEmulatorAction = reinstallTask(true) dependsOn(packageDebug) describedAs("Reinstall package on the default emulator.")
lazy val reinstallDevice = reinstallDeviceAction
def reinstallDeviceAction = reinstallTask(false) dependsOn(packageDebug) describedAs("Reinstall package on the default device.")
lazy val uninstallEmulator = uninstallEmulatorAction
def uninstallEmulatorAction = uninstallTask(true) describedAs("Uninstall package on the default emulator.")
lazy val uninstallDevice = uninstallDeviceAction
def uninstallDeviceAction = uninstallTask(false) describedAs("Uninstall package on the default device.")
def installTask(emulator: Boolean) = adbTask(emulator, "install "+packageApkPath.absolutePath)
def reinstallTask(emulator: Boolean) = adbTask(emulator, "install -r "+packageApkPath.absolutePath)
def uninstallTask(emulator: Boolean) = adbTask(emulator, "uninstall "+manifestPackage)
def adbTask(emulator: Boolean, action: String) = execTask {<x>
{adbPath.absolutePath} {if (emulator) "-e" else "-d"} {action}
</x>}
lazy val manifest:scala.xml.Elem = scala.xml.XML.loadFile(androidManifestPath.asFile)
lazy val minSdkVersion = usesSdk("minSdkVersion")
lazy val maxSdkVersion = usesSdk("maxSdkVersion")
lazy val manifestPackage = manifest.attribute("package").getOrElse(error("package not defined")).text
def usesSdk(s: String):Option[Int] = (manifest \ "uses-sdk").first.attribute("http://schemas.android.com/apk/res/android", s).map(_.text.toInt)
def addonsJarPath = Path.lazyPathFinder {
for {
lib <- manifest \ "application" \ "uses-library"
p = lib.attribute("http://schemas.android.com/apk/res/android", "name").flatMap {
_.text match {
case "com.google.android.maps" => Some(mapsJarPath)
case _ => None
}
}
if p.isDefined
} yield p.get
}
def directory(dir: Path) = fileTask(dir :: Nil) {
FileUtilities.createDirectory(dir, log)
}
override def ivyXML =
<dependencies>
<exclude module="httpclient" conf="compile"/>
<exclude module="httpcore" conf="compile"/>
<exclude module="commons-logging" conf="compile"/>
<exclude module="commons-codec" conf="compile"/>
</dependencies>
}