initial script and publish action
This commit is contained in:
parent
a1f1eca3f6
commit
e478a4c836
70 changed files with 2602 additions and 0 deletions
1
main/.gitignore
vendored
Normal file
1
main/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
31
main/build.gradle.kts
Normal file
31
main/build.gradle.kts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
plugins {
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
id("application")
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("org.fnives.android.test.dockerfile.Main")
|
||||
}
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
setEvents(setOf("started", "passed", "skipped", "failed"))
|
||||
setExceptionFormat("full")
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val tomlParserVersion = "1.1.0"
|
||||
implementation("cc.ekblad:4koma:$tomlParserVersion")
|
||||
|
||||
val testingJunitJupiterVersion = "5.7.0"
|
||||
val testingJunitJupiterRuntimeVersion = "5.3.1"
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:$testingJunitJupiterVersion")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$testingJunitJupiterRuntimeVersion")
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package org.fnives.android.test.dockerfile
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.Configuration
|
||||
import org.fnives.android.test.dockerfile.configuration.ImageConfiguration
|
||||
import org.fnives.android.test.dockerfile.di.ServiceLocatorHolder.ServiceLocator
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Step into the Script.
|
||||
*
|
||||
* This script is intended to:
|
||||
* - Generate Dockerfiles from the given configuration.
|
||||
* - Create DockerImages from the generated Dockerfiles
|
||||
* - Push those images up to DockerHub based on the configucation.
|
||||
*
|
||||
* Needs a single argument to the path of the configuration.toml file.
|
||||
*
|
||||
* Usually run like: ./gradlew run --args="$(pwd)/configuration.toml
|
||||
*
|
||||
* **Docker needs to be installed**
|
||||
*/
|
||||
object Main {
|
||||
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun main(args: Array<String>) {
|
||||
val configuration = getConfigurationFromArgument(args)
|
||||
|
||||
val configuredServiceLocator = ServiceLocator.loadConfiguration(configuration)
|
||||
val imageConfigurations = listOf(ImageConfiguration(configuration))
|
||||
.plus(configuration.apiLevelVariations.map(::ImageConfiguration))
|
||||
|
||||
configuredServiceLocator.copyScript().invoke()
|
||||
imageConfigurations.map(configuredServiceLocator.contentGenerator()::create)
|
||||
.map(configuredServiceLocator.imageWriter()::write)
|
||||
.forEach(configuredServiceLocator.imageBuildAndPush()::buildPushClean)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private fun getConfigurationFromArgument(args: Array<String>): Configuration {
|
||||
val configFile = getFileFromArgument(args)
|
||||
|
||||
return try {
|
||||
configFile.inputStream().use(ServiceLocator.parser()::invoke)
|
||||
} catch (illegalArgumentException: IllegalArgumentException) {
|
||||
throw IllegalArgumentException("Unparseable file at given path: ${configFile.absolutePath}", illegalArgumentException)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private fun getFileFromArgument(args: Array<String>): File {
|
||||
require(args.isNotEmpty()) {
|
||||
"Required argument is missing! Required argument is the config.toml file path and just that!"
|
||||
}
|
||||
require(args.size < 2) {
|
||||
"More arguments than expected! Required argument is the config.toml file path and just that!"
|
||||
}
|
||||
val configFile = File(args[0])
|
||||
require(configFile.exists()) {
|
||||
"Can't find file at given path: ${configFile.absolutePath}"
|
||||
}
|
||||
|
||||
return configFile
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package org.fnives.android.test.dockerfile.configuration
|
||||
|
||||
import org.fnives.android.test.dockerfile.generator.DockerFileContent
|
||||
|
||||
/**
|
||||
* Describes all the configurations which can be set inside the [toml][https://toml.io/en/] file.
|
||||
*
|
||||
* @param buildTools android build tools installed into the DockerImages
|
||||
* @param sdks AndroidSDKs installed into the DockerImages
|
||||
* @param commandlineTools AndroidCommandLinesTools installed into the DockerImages
|
||||
* @param gradleVersion GradleVersion to be installed into the DockerImages
|
||||
* @param output the folder which will contain the generated Dockerfiles
|
||||
* @param dockerRepository the repository in DockerHub to push into.
|
||||
* @param dockerNamespace the namespace inside the [dockerRepository] defined on DockerHub.
|
||||
* @param dockerTagPrefix the prefix which should be added to all DockerImage tags when creating [DockerFileContent]
|
||||
* @param apiLevelVariations the specific api levels that should have their own image. This is used so everything is ready for the Emulator for a specific API version.
|
||||
*/
|
||||
data class Configuration(
|
||||
val buildTools: Set<String>,
|
||||
val sdks: Set<Int>,
|
||||
val commandlineTools: String,
|
||||
val gradleVersion: String,
|
||||
val output: String,
|
||||
val dockerRepository: String,
|
||||
val dockerNamespace: String,
|
||||
val dockerTagPrefix: String,
|
||||
val apiLevelVariations: Set<Int> = emptySet()
|
||||
)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package org.fnives.android.test.dockerfile.configuration
|
||||
|
||||
import cc.ekblad.toml.TomlMapper
|
||||
import cc.ekblad.toml.decode
|
||||
import cc.ekblad.toml.tomlMapper
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Parses the given [toml][https://toml.io/en/] file into a [Configuration] to be usable by the other parts of the app.
|
||||
*/
|
||||
class ConfigurationParser(private val mapper: TomlMapper = tomlMapper {}) {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun invoke(content: InputStream): Configuration {
|
||||
val raw = try {
|
||||
mapper.decode<RawConfiguration>(content)
|
||||
} catch (cause: Throwable) {
|
||||
throw IllegalArgumentException("Couldn't process configuration", cause)
|
||||
}
|
||||
return raw.convert()
|
||||
}
|
||||
|
||||
private fun RawConfiguration.convert(): Configuration = Configuration(
|
||||
buildTools = buildTools,
|
||||
sdks = sdks,
|
||||
commandlineTools = androidCommandlineTools,
|
||||
gradleVersion = gradleVersion,
|
||||
output = output,
|
||||
apiLevelVariations = variations?.apiLevels.orEmpty(),
|
||||
dockerNamespace = image.namespace,
|
||||
dockerRepository = image.repository,
|
||||
dockerTagPrefix = image.tagPrefix
|
||||
)
|
||||
|
||||
private data class RawConfiguration(
|
||||
val buildTools: Set<String>,
|
||||
val sdks: Set<Int>,
|
||||
val androidCommandlineTools: String,
|
||||
val gradleVersion: String,
|
||||
val output: String,
|
||||
val variations: Variations?,
|
||||
val image: Image
|
||||
) {
|
||||
|
||||
class Variations(
|
||||
val apiLevels: Set<Int> = emptySet()
|
||||
)
|
||||
|
||||
class Image(
|
||||
val namespace: String,
|
||||
val repository: String,
|
||||
val tagPrefix: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.fnives.android.test.dockerfile.configuration
|
||||
|
||||
/**
|
||||
* Describes the specific that should be used to fill out the Dockerfile templates.
|
||||
*/
|
||||
sealed class ImageConfiguration {
|
||||
data class Generic(
|
||||
val buildTools: Set<String>,
|
||||
val sdks: Set<Int>,
|
||||
val commandlineTools: String,
|
||||
val gradleVersion: String
|
||||
) : ImageConfiguration()
|
||||
|
||||
data class ApiVersion(
|
||||
val version: Int
|
||||
) : ImageConfiguration()
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.fnives.android.test.dockerfile.configuration
|
||||
|
||||
/**
|
||||
* Utility function to ease the creation of a generic [ImageConfiguration]
|
||||
*/
|
||||
fun ImageConfiguration(configuration: Configuration): ImageConfiguration =
|
||||
ImageConfiguration.Generic(
|
||||
buildTools = configuration.buildTools,
|
||||
sdks = configuration.sdks,
|
||||
commandlineTools = configuration.commandlineTools,
|
||||
gradleVersion = configuration.gradleVersion,
|
||||
)
|
||||
|
||||
/**
|
||||
* Utility function to ease the creation of a specific [ImageConfiguration], that will depend on a Generic one.
|
||||
*/
|
||||
fun ImageConfiguration(version: Int): ImageConfiguration = ImageConfiguration.ApiVersion(version)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package org.fnives.android.test.dockerfile.di
|
||||
|
||||
import org.fnives.android.test.dockerfile.generator.DockerFileContentGenerator
|
||||
import org.fnives.android.test.dockerfile.push.DockerBuildAndPush
|
||||
import org.fnives.android.test.dockerfile.write.DockerFileWriter
|
||||
import org.fnives.android.test.dockerfile.write.ScriptCopier
|
||||
|
||||
/**
|
||||
* Gives access to the Services that depend on [Configuration][org.fnives.android.test.dockerfile.configuration.Configuration] and takes care of their creation and instances.
|
||||
*/
|
||||
interface ConfiguredServiceLocator {
|
||||
fun contentGenerator(): DockerFileContentGenerator
|
||||
fun imageWriter(): DockerFileWriter
|
||||
fun imageBuildAndPush(): DockerBuildAndPush
|
||||
fun copyScript(): ScriptCopier
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package org.fnives.android.test.dockerfile.di
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.Configuration
|
||||
import org.fnives.android.test.dockerfile.generator.DockerFileContentConfig
|
||||
import org.fnives.android.test.dockerfile.generator.DockerFileContentGenerator
|
||||
import org.fnives.android.test.dockerfile.push.DockerBuildAndPush
|
||||
import org.fnives.android.test.dockerfile.push.PushConfig
|
||||
import org.fnives.android.test.dockerfile.util.resourceFileContent
|
||||
import org.fnives.android.test.dockerfile.util.clock.Clock
|
||||
import org.fnives.android.test.dockerfile.write.DockerFileWriter
|
||||
import org.fnives.android.test.dockerfile.write.ScriptCopier
|
||||
import java.io.File
|
||||
|
||||
/** Syntax Sugar */
|
||||
fun ConfiguredServiceLocator(configuration: Configuration): ConfiguredServiceLocator =
|
||||
DefaultConfiguredServiceLocator(configuration)
|
||||
|
||||
/**
|
||||
* Actual [ConfiguredServiceLocator] keeping references and creating the Services that are dependent on [Configuration].
|
||||
*/
|
||||
class DefaultConfiguredServiceLocator(private val configuration: Configuration) : ConfiguredServiceLocator {
|
||||
private val pushConfig
|
||||
get() = PushConfig(
|
||||
dockerRepository = configuration.dockerRepository,
|
||||
dockerNamespace = configuration.dockerNamespace,
|
||||
output = configuration.output
|
||||
)
|
||||
private val outputFolder get() = File(configuration.output)
|
||||
|
||||
private val processRunner get() = ServiceLocatorHolder.ServiceLocator.processRunner()
|
||||
private val clock: Clock get() = ServiceLocatorHolder.ServiceLocator.clock()
|
||||
private val scriptName: String get() = ServiceLocatorHolder.ServiceLocator.scriptToCopy()
|
||||
|
||||
private val imageBuildAndPush by lazy { DockerBuildAndPush(config = pushConfig, processRunner = processRunner) }
|
||||
private val imageWriter by lazy { DockerFileWriter(outputFolder = outputFolder) }
|
||||
private val copyScript by lazy { ScriptCopier(outputFolder = outputFolder, scriptName = scriptName) }
|
||||
|
||||
private val dockerFileContentConfig
|
||||
get() = DockerFileContentConfig(
|
||||
templateGeneric = resourceFileContent("Dockerfile.template"),
|
||||
templateSpecific = resourceFileContent("Dockerfile.api.template"),
|
||||
dockerRepository = configuration.dockerRepository,
|
||||
dockerNamespace = configuration.dockerNamespace,
|
||||
dockerTagPrefix = configuration.dockerTagPrefix,
|
||||
)
|
||||
|
||||
private val contentGenerator by lazy {
|
||||
DockerFileContentGenerator(
|
||||
config = dockerFileContentConfig,
|
||||
clock = clock
|
||||
)
|
||||
}
|
||||
|
||||
override fun contentGenerator(): DockerFileContentGenerator = contentGenerator
|
||||
|
||||
override fun imageWriter(): DockerFileWriter = imageWriter
|
||||
|
||||
override fun imageBuildAndPush(): DockerBuildAndPush = imageBuildAndPush
|
||||
|
||||
override fun copyScript(): ScriptCopier = copyScript
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package org.fnives.android.test.dockerfile.di
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.Configuration
|
||||
import org.fnives.android.test.dockerfile.configuration.ConfigurationParser
|
||||
import org.fnives.android.test.dockerfile.push.ProcessRunner
|
||||
import org.fnives.android.test.dockerfile.push.SystemProcessRunner
|
||||
import org.fnives.android.test.dockerfile.util.clock.Clock
|
||||
import org.fnives.android.test.dockerfile.util.clock.SystemCock
|
||||
|
||||
/**
|
||||
* Actual [ServiceLocator] keeping references and creating the Services.
|
||||
*/
|
||||
object DefaultServiceLocator : ServiceLocator {
|
||||
|
||||
private val parser by lazy { ConfigurationParser() }
|
||||
private val processRunner by lazy { SystemProcessRunner() }
|
||||
|
||||
override fun loadConfiguration(configuration: Configuration): ConfiguredServiceLocator =
|
||||
ConfiguredServiceLocator(configuration)
|
||||
|
||||
override fun parser(): ConfigurationParser = parser
|
||||
|
||||
override fun clock(): Clock = SystemCock
|
||||
|
||||
override fun scriptToCopy(): String = "startemulator"
|
||||
|
||||
override fun processRunner(): ProcessRunner = processRunner
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.fnives.android.test.dockerfile.di
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.Configuration
|
||||
import org.fnives.android.test.dockerfile.configuration.ConfigurationParser
|
||||
import org.fnives.android.test.dockerfile.push.ProcessRunner
|
||||
import org.fnives.android.test.dockerfile.util.clock.Clock
|
||||
|
||||
/**
|
||||
* Gives access to the Services and takes care of their creation and instances.
|
||||
*/
|
||||
interface ServiceLocator {
|
||||
fun parser(): ConfigurationParser
|
||||
fun clock(): Clock
|
||||
fun scriptToCopy(): String
|
||||
fun processRunner(): ProcessRunner
|
||||
fun loadConfiguration(configuration: Configuration): ConfiguredServiceLocator
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package org.fnives.android.test.dockerfile.di
|
||||
|
||||
/**
|
||||
* Access point to [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator] so it can be [swap]ed out in tests.
|
||||
*/
|
||||
object ServiceLocatorHolder {
|
||||
|
||||
/**
|
||||
* Syntax Sugar
|
||||
*/
|
||||
val ServiceLocator get() = get()
|
||||
|
||||
val default: ServiceLocator = DefaultServiceLocator
|
||||
private var actual: ServiceLocator = default
|
||||
|
||||
/**
|
||||
* Change the [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator] returned in [get] and used.
|
||||
*/
|
||||
fun swap(serviceLocator: ServiceLocator) {
|
||||
actual = serviceLocator
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset back to [default] [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator] to be returned in [get]
|
||||
*/
|
||||
fun reset() = swap(default)
|
||||
|
||||
/**
|
||||
* Returns the actual [ServiceLocator][org.fnives.android.test.dockerfile.di.ServiceLocator]
|
||||
*/
|
||||
fun get() = actual
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.fnives.android.test.dockerfile.generator
|
||||
|
||||
/**
|
||||
* Describes the content and necessary information to create a Dockerfile and DockerImage from that.
|
||||
*
|
||||
* @param name the name of the Dockerfile to be created
|
||||
* @param tag the id of the DockerImage to be created
|
||||
* @param content the commands which should be in the Dockerfile
|
||||
*/
|
||||
data class DockerFileContent(
|
||||
val name: String,
|
||||
val tag: String,
|
||||
val content: String
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package org.fnives.android.test.dockerfile.generator
|
||||
|
||||
/**
|
||||
* Configuration for the [DockerFileContentGenerator]
|
||||
*
|
||||
* @param templateGeneric the Dockerfile template content for a Generic image
|
||||
* @param templateSpecific the Dockerfile template content for a Specific image, which extends the Generic and adds additional installs.
|
||||
* @param dockerRepository the repository in DockerHub to push into.
|
||||
* @param dockerNamespace the namespace inside the [dockerRepository] defined on DockerHub.
|
||||
* @param dockerTagPrefix the prefix which should be added to all DockerImage tags when creating [DockerFileContent]
|
||||
*/
|
||||
data class DockerFileContentConfig(
|
||||
val templateGeneric: String,
|
||||
val templateSpecific: String,
|
||||
val dockerRepository: String,
|
||||
val dockerNamespace: String,
|
||||
val dockerTagPrefix: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package org.fnives.android.test.dockerfile.generator
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.ImageConfiguration
|
||||
import org.fnives.android.test.dockerfile.util.clock.Clock
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Creates [DockerFileContent] from an [ImageConfiguration] based on the [DockerFileContentConfig].
|
||||
*
|
||||
* Uses the templates of [DockerFileContentConfig] and replaces the specifics described in the [ImageConfiguration].
|
||||
* The templates have keywords which are used to replace with the specific commands defined by [ImageConfiguration]
|
||||
*/
|
||||
class DockerFileContentGenerator(
|
||||
private val config: DockerFileContentConfig,
|
||||
private val clock: Clock
|
||||
) {
|
||||
|
||||
fun create(imageConfiguration: ImageConfiguration): DockerFileContent {
|
||||
val dockerfileName = dockerFileName(imageConfiguration.version())
|
||||
val tag = "${config.dockerRepository}/${config.dockerNamespace}:${config.dockerTagPrefix}${tagSuffix(imageConfiguration.version())}"
|
||||
val genericTag = "${config.dockerRepository}/${config.dockerNamespace}:${config.dockerTagPrefix}${tagSuffix(null)}"
|
||||
|
||||
val content = when (imageConfiguration) {
|
||||
is ImageConfiguration.Generic ->
|
||||
config.templateGeneric.setCommandLineToolsVersion(imageConfiguration.commandlineTools)
|
||||
.setGradleVersion(imageConfiguration.gradleVersion)
|
||||
.setInstalledSDKs(imageConfiguration.sdks)
|
||||
.setInstalledBuildTools(imageConfiguration.buildTools)
|
||||
.setDateStamp()
|
||||
is ImageConfiguration.ApiVersion ->
|
||||
config.templateSpecific.setFromTag(genericTag)
|
||||
.setENVAPILevel(imageConfiguration.version)
|
||||
.setDateStamp()
|
||||
}
|
||||
|
||||
return DockerFileContent(
|
||||
name = dockerfileName,
|
||||
content = content,
|
||||
tag = tag
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.setCommandLineToolsVersion(version: String): String =
|
||||
replace(COMMAND_LINES_KEY, version)
|
||||
|
||||
private fun String.setGradleVersion(version: String): String =
|
||||
replace(GRADLE_VERSION_KEY, version)
|
||||
|
||||
private fun String.setInstalledSDKs(sdks: Set<Int>): String {
|
||||
val sdkInstallCommand = sdks.joinToString(" && \\\n ") { getSdkInstallCommand(it) }
|
||||
|
||||
return replace(SDK_INSTALL_COMMANDS_KEY, sdkInstallCommand)
|
||||
}
|
||||
|
||||
private fun String.setInstalledBuildTools(buildTools: Set<String>): String {
|
||||
val buildToolsInstallCommand = buildTools.joinToString(" && \\\n ") { getBuildToolsInstallCommand(it) }
|
||||
|
||||
return replace(BUILD_TOOLS_INSTALL_COMMANDS_KEY, buildToolsInstallCommand)
|
||||
}
|
||||
|
||||
private fun dockerFileName(version: Int?): String =
|
||||
if (version == null) {
|
||||
"Dockerfile"
|
||||
} else {
|
||||
"Dockerfile-api-$version"
|
||||
}
|
||||
|
||||
private fun tagSuffix(version: Int?): String = if (version == null) "" else "-api-$version"
|
||||
|
||||
private fun String.setFromTag(tagPrefix: String): String {
|
||||
val tag = tagPrefix + tagSuffix(null)
|
||||
|
||||
return replace(TAG_KEY, tag)
|
||||
}
|
||||
|
||||
private fun String.setENVAPILevel(apiLevel: Int): String = replace(SET_API_LEVEL, apiLevel.toString())
|
||||
|
||||
private fun String.setDateStamp(): String {
|
||||
val dateStamp = SimpleDateFormat("yyyy-MM-dd").format(Date(clock.currentTimeMillis()))
|
||||
|
||||
return replace(DATE_STAMP_KEY, "Generated on $dateStamp")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val COMMAND_LINES_KEY = "{{COMMAND_LINE_TOOLS}}"
|
||||
private const val GRADLE_VERSION_KEY = "{{GRADLE_VERSION}}"
|
||||
private const val SDK_INSTALL_COMMANDS_KEY = "{{SDK_INSTALL_COMMANDS}}"
|
||||
private const val BUILD_TOOLS_INSTALL_COMMANDS_KEY = "{{BUILD_TOOLS_INSTALL_COMMANDS}}"
|
||||
private const val TAG_KEY = "{{IMG-TAG}}"
|
||||
private const val SET_API_LEVEL = "{{API_LEVEL}}"
|
||||
private const val DATE_STAMP_KEY = "{{GENERATED_AT}}"
|
||||
|
||||
private fun getSdkInstallCommand(sdk: Int) = "echo y | sdkmanager \"platforms;android-$sdk\""
|
||||
|
||||
private fun getBuildToolsInstallCommand(buildTool: String) = "echo y | sdkmanager \"build-tools;$buildTool\""
|
||||
|
||||
private fun ImageConfiguration.version() = when (this) {
|
||||
is ImageConfiguration.ApiVersion -> version
|
||||
is ImageConfiguration.Generic -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package org.fnives.android.test.dockerfile.push
|
||||
|
||||
import org.fnives.android.test.dockerfile.write.DockerFileDescriptor
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Runs the necessary docker commands to create, push and cleanup the DockerImage from the given Dockerfile.
|
||||
*/
|
||||
class DockerBuildAndPush(
|
||||
private val config: PushConfig,
|
||||
private val processRunner: ProcessRunner
|
||||
) {
|
||||
|
||||
private val workDir get() = File(config.output)
|
||||
|
||||
fun build(dockerFileDescriptor: DockerFileDescriptor): String {
|
||||
"docker build -t ${dockerFileDescriptor.tag} -f ${dockerFileDescriptor.filePath} .".runCommand(workDir)
|
||||
|
||||
return dockerFileDescriptor.tag
|
||||
}
|
||||
|
||||
fun push(id: String) {
|
||||
"docker push $id".runCommand(workDir)
|
||||
}
|
||||
|
||||
fun removeLocalImage(id: String) {
|
||||
"docker image rm -f $id".runCommand(workDir)
|
||||
}
|
||||
|
||||
fun buildPushClean(dockerFileDescriptor: DockerFileDescriptor) {
|
||||
val id = build(dockerFileDescriptor)
|
||||
push(id)
|
||||
removeLocalImage(id)
|
||||
}
|
||||
|
||||
private fun String.runCommand(workingDir: File) =
|
||||
processRunner.run(workingDirectory = workingDir, command = this)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package org.fnives.android.test.dockerfile.push
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* Process Runner is able to run a given command in the System process.
|
||||
*/
|
||||
interface ProcessRunner {
|
||||
|
||||
/**
|
||||
* Runs the given [command] as a System process in [workingDirectory].
|
||||
*/
|
||||
@Throws(TimeoutException::class)
|
||||
fun run(workingDirectory: File, command: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.fnives.android.test.dockerfile.push
|
||||
|
||||
/**
|
||||
* Configuration for [DockerBuildAndPush].
|
||||
*
|
||||
* @param dockerRepository the repository in DockerHub to push into.
|
||||
* @param dockerNamespace the namespace inside the [dockerRepository] defined on DockerHub.
|
||||
* @param output the path to the output folder.
|
||||
*/
|
||||
data class PushConfig(
|
||||
val dockerRepository: String,
|
||||
val dockerNamespace: String,
|
||||
val output: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package org.fnives.android.test.dockerfile.push
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* Actual [ProcessRunner] which converts the command into the Process then starts and awaits it.
|
||||
*/
|
||||
class SystemProcessRunner(
|
||||
private val timeout: Long = 60,
|
||||
private val timeoutUnit: TimeUnit = TimeUnit.MINUTES
|
||||
) : ProcessRunner {
|
||||
|
||||
override fun run(workingDirectory: File, command: String) {
|
||||
val result = ProcessBuilder(*command.split(" ").toTypedArray())
|
||||
.directory(workingDirectory)
|
||||
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
|
||||
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||
.start()
|
||||
.waitFor(timeout, timeoutUnit)
|
||||
if (!result) {
|
||||
throw TimeoutException("The command have not finished in $timeout ${timeoutUnit}! `$command`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package org.fnives.android.test.dockerfile.util
|
||||
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
/**
|
||||
* Helper function to read contents of files baked into the resources folder
|
||||
*/
|
||||
internal fun Any.resourceFileContent(filePath: String): String = try {
|
||||
BufferedReader(InputStreamReader(resourceFileStream(filePath)))
|
||||
.readLines().joinToString("\n")
|
||||
} catch (nullPointerException: NullPointerException) {
|
||||
throw IllegalArgumentException("$filePath file not found!", nullPointerException)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to access contents of files baked into the resources folder
|
||||
*/
|
||||
internal fun Any.resourceFileStream(filePath: String): InputStream = try {
|
||||
this.javaClass.classLoader.getResourceAsStream(filePath)!!
|
||||
} catch (nullPointerException: NullPointerException) {
|
||||
throw IllegalArgumentException("$filePath file not found!", nullPointerException)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.fnives.android.test.dockerfile.util.clock
|
||||
|
||||
/**
|
||||
* Simple Utility interface providing the [currentTimeMillis].
|
||||
*/
|
||||
interface Clock {
|
||||
|
||||
fun currentTimeMillis(): Long
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.fnives.android.test.dockerfile.util.clock
|
||||
|
||||
/**
|
||||
* Actual [Clock] returning the System time.
|
||||
*/
|
||||
object SystemCock : Clock {
|
||||
|
||||
override fun currentTimeMillis(): Long = System.currentTimeMillis()
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.fnives.android.test.dockerfile.write
|
||||
|
||||
/**
|
||||
* Describes [DockerFileContent][org.fnives.android.test.dockerfile.generator.DockerFileContent] which was written into a file.
|
||||
* @param filePath is the absolut path of the File
|
||||
* @param tag is the id of the DockerImage.
|
||||
*/
|
||||
data class DockerFileDescriptor(val filePath: String, val tag: String)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package org.fnives.android.test.dockerfile.write
|
||||
|
||||
import org.fnives.android.test.dockerfile.generator.DockerFileContent
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Simple configured class which can write a [DockerFileContent] into a file
|
||||
* within the given [outputFolder] configuration, returns [DockerFileDescriptor] describing the written file.
|
||||
*/
|
||||
class DockerFileWriter(private val outputFolder: File) {
|
||||
|
||||
fun write(dockerFile: DockerFileContent): DockerFileDescriptor {
|
||||
outputFolder.mkdirs()
|
||||
|
||||
val file = File(outputFolder, dockerFile.name)
|
||||
file.createNewFile()
|
||||
file.writeText(dockerFile.content)
|
||||
|
||||
return DockerFileDescriptor(filePath = file.absolutePath, tag = dockerFile.tag)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package org.fnives.android.test.dockerfile.write
|
||||
|
||||
import org.fnives.android.test.dockerfile.util.resourceFileStream
|
||||
import java.io.File
|
||||
|
||||
class ScriptCopier(private val outputFolder: File, private val scriptName: String) {
|
||||
|
||||
fun invoke() {
|
||||
outputFolder.mkdirs()
|
||||
val outputScript = File(outputFolder, scriptName)
|
||||
outputScript.createNewFile()
|
||||
outputScript.outputStream().use {
|
||||
resourceFileStream(scriptName).copyTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
8
main/src/main/resources/Dockerfile.api.template
Normal file
8
main/src/main/resources/Dockerfile.api.template
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
FROM {{IMG-TAG}}
|
||||
# {{GENERATED_AT}}
|
||||
|
||||
ENV EMULATOR_API_LEVEL {{API_LEVEL}}
|
||||
|
||||
RUN /usr/local/bin/androidemulatorstart --buildOnly
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
71
main/src/main/resources/Dockerfile.template
Normal file
71
main/src/main/resources/Dockerfile.template
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
FROM openjdk:11-jdk-slim
|
||||
# {{GENERATED_AT}}
|
||||
|
||||
# installing usual required tools
|
||||
# build-essential, ruby and bundler is needed for fastlane
|
||||
RUN apt-get update && apt-get install -y \
|
||||
sudo \
|
||||
wget \
|
||||
curl \
|
||||
unzip \
|
||||
android-sdk \
|
||||
build-essential \
|
||||
ruby-full && \
|
||||
gem install bundler
|
||||
|
||||
# versions
|
||||
# latest command lines version here: https://developer.android.com/studio#command-tools
|
||||
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "{{COMMAND_LINE_TOOLS}}"
|
||||
# gradle version can be found in projects gradle-wrapper.properties
|
||||
ENV GRADLE_VERSION "{{GRADLE_VERSION}}"
|
||||
|
||||
# set homes and paths
|
||||
ENV ANDROID_HOME "/usr/lib/android-sdk"
|
||||
ENV ANDROID_SDK_ROOT $ANDROID_HOME
|
||||
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
|
||||
ENV AVD_HOME "/root/.android/avd"
|
||||
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
|
||||
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
|
||||
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
# install gradle
|
||||
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
|
||||
unzip -d /usr/local /tmp/gradle.zip && \
|
||||
rm -rf /tmp/gradle.zip && \
|
||||
echo "1" | gradle init && \
|
||||
/root/gradlew && \
|
||||
rm -rf /root/* && \
|
||||
rm /root/.gitignore
|
||||
|
||||
# install command line tools
|
||||
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
|
||||
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
|
||||
mv cmdline-tools $ANDROID_HOME/
|
||||
|
||||
# install emulator and setup sdkmanager
|
||||
RUN yes | sdkmanager --licenses > /dev/null && \
|
||||
sdkmanager --install emulator --channel=0 > /dev/null
|
||||
|
||||
# support libraries and google play services
|
||||
RUN echo y | sdkmanager "extras;android;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;google_play_services"
|
||||
|
||||
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
|
||||
# install build-tools
|
||||
# example: `echo y | sdkmanager "build-tools;33.0.0"`
|
||||
RUN {{BUILD_TOOLS_INSTALL_COMMANDS}}
|
||||
|
||||
# install sdks
|
||||
# example: `echo y | sdkmanager "platforms;android-33"`
|
||||
RUN {{SDK_INSTALL_COMMANDS}}
|
||||
|
||||
# copy script to install sdk, create emulator and boot it
|
||||
COPY ./startemulator /usr/local/bin/androidemulatorstart
|
||||
RUN chmod 744 /usr/local/bin/androidemulatorstart
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
105
main/src/main/resources/startemulator
Normal file
105
main/src/main/resources/startemulator
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ "$1" == "--help" ]]
|
||||
then
|
||||
echo "Creates default emulator and starts it."
|
||||
echo "API level and Target can be customized, for more customization use the avdmanager directly!"
|
||||
echo "API_level is read from EMULATOR_API_LEVEL variable"
|
||||
echo "target is read from EMULATOR_TARGET variable or defaults to target"
|
||||
echo "--buildOnly parameter means the emulator will be created, but not started"
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
if [[ -z $EMULATOR_API_LEVEL ]]
|
||||
then
|
||||
echo "EMULATOR_API_LEVEL not set!" >&2
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if [[ -z $EMULATOR_TARGET ]]
|
||||
then
|
||||
if [[ $EMULATOR_API_LEVEL -ge 32 ]]
|
||||
then
|
||||
echo "EMULATOR_TARGET not set using \"google_apis\", \"default\" doesn't exists above 32"
|
||||
EMULATOR_TARGET="google_apis"
|
||||
else
|
||||
echo "EMULATOR_TARGET not set using \"default\"" >&2
|
||||
EMULATOR_TARGET="default"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z $AVD_HOME ]]
|
||||
then
|
||||
AVD_HOME="$HOME/.android/avd"
|
||||
echo "AVD_HOME not set, using fallback: $AVD_HOME"
|
||||
fi
|
||||
|
||||
if [[ "$1" == "--buildOnly" ]]
|
||||
then
|
||||
echo "INFO: --buildOnly found, emulator will be created but not started!" >&2
|
||||
fi
|
||||
|
||||
echo "INFO: installing sdk" >&2
|
||||
sdkmanager --install "system-images;android-$EMULATOR_API_LEVEL;$EMULATOR_TARGET;x86_64" --channel=0 > /dev/null 2> /dev/null
|
||||
|
||||
|
||||
echo "INFO: creating emulator" >&2
|
||||
EMULATOR_NAME="test_$EMULATOR_API_LEVEL"
|
||||
echo "no" | avdmanager create avd -n "$EMULATOR_NAME" --abi "$EMULATOR_TARGET/x86_64" --package "system-images;android-$EMULATOR_API_LEVEL;$EMULATOR_TARGET;x86_64"
|
||||
|
||||
echo "INFO: config of emulator:" >&2
|
||||
cat $AVD_HOME/$EMULATOR_NAME.avd/config.ini >&2
|
||||
echo "INFO: modifying config of emulator..." >&2
|
||||
echo 'disk.dataPartition.size=2G\n' >> $AVD_HOME/$EMULATOR_NAME.avd/config.ini
|
||||
|
||||
if [[ "$1" == "--buildOnly" ]]
|
||||
then
|
||||
echo "INFO: emulator created!" >&2
|
||||
exit 0;
|
||||
fi
|
||||
|
||||
echo "INFO: checking devices, expecting nothing..." >&2
|
||||
adb devices
|
||||
|
||||
echo "INFO: starting emulator..." >&2
|
||||
emulator -avd $EMULATOR_NAME -no-snapshot-save -no-window -gpu off -noaudio -no-boot-anim -camera-back none > emulator.log &
|
||||
|
||||
echo "INFO: waiting for emulator..." >&2
|
||||
sleep 30s
|
||||
|
||||
echo "INFO: emulator logs" >&2
|
||||
cat emulator.log
|
||||
|
||||
echo "INFO: checking devices..." >&2
|
||||
adb devices
|
||||
echo "INFO: waiting for emulator to be booted..." >&2
|
||||
BOOT_WAIT_LOG_MESSAGE="INFO: still waiting for emulator to boot"
|
||||
SECONDS_PASSED=0;
|
||||
BOOT_COMPLETE=`adb shell getprop sys.boot_completed`
|
||||
while [[ -z $BOOT_COMPLETE ]];
|
||||
do
|
||||
sleep $BOOT_DELAY_IN_SECONDS;
|
||||
SECONDS_PASSED=$(( $SECONDS_PASSED+$BOOT_DELAY_IN_SECONDS ));
|
||||
echo "INFO: still waiting for emulator to boot ($SECONDS_PASSED seconds passed)"; >&2
|
||||
BOOT_COMPLETE=`adb shell getprop sys.boot_completed`
|
||||
done;
|
||||
echo "INFO: emulator booted." >&2
|
||||
# alternative: https://raw.githubusercontent.com/travis-ci/travis-cookbooks/0f497eb71291b52a703143c5cd63a217c8766dc9/community-cookbooks/android-sdk/files/default/android-wait-for-emulator
|
||||
|
||||
echo "INFO: clearing animations..." >&2
|
||||
adb shell settings put global window_animation_scale 0.0
|
||||
adb shell settings put global transition_animation_scale 0.0
|
||||
adb shell settings put global animator_duration_scale 0.0
|
||||
|
||||
echo "INFO: waiting for emulator have sdk property..." >&2
|
||||
SDK_VERSION=`adb shell getprop ro.build.version.sdk`
|
||||
while [[ -z $SDK_VERSION ]];
|
||||
do
|
||||
sleep $BOOT_DELAY_IN_SECONDS;
|
||||
echo "INFO: still waiting for emulator to have SDK property"; >&2
|
||||
SDK_VERSION=`adb shell getprop ro.build.version.sdk`
|
||||
done;
|
||||
|
||||
echo "INFO: unlocking screen..." >&2
|
||||
adb shell input keyevent 82
|
||||
echo "INFO: emulator ready!" >&2
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package org.fnives.android.test.dockerfile.configuration
|
||||
|
||||
import org.fnives.android.test.dockerfile.util.resourceFileAsInputStream
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ConfigurationParserTest {
|
||||
|
||||
private lateinit var parser: ConfigurationParser
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
parser = ConfigurationParser()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun example() {
|
||||
val expectedConfiguration = Configuration(
|
||||
buildTools = setOf("33.0.0", "32.0.0", "31.0.0", "30.0.3", "30.0.2"),
|
||||
sdks = setOf(30, 31, 32, 33),
|
||||
commandlineTools = "8512546_latest",
|
||||
gradleVersion = "7.3.3",
|
||||
output = "outputs",
|
||||
dockerRepository = "fknives",
|
||||
dockerNamespace = "android-test-img",
|
||||
dockerTagPrefix = "1.0.0",
|
||||
apiLevelVariations = setOf(21, 22, 23)
|
||||
)
|
||||
val rawConfiguration = resourceFileAsInputStream("example_configuration.toml")
|
||||
|
||||
val actual = parser.invoke(rawConfiguration)
|
||||
|
||||
Assertions.assertEquals(expectedConfiguration, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleConfiguration() {
|
||||
val expectedConfiguration = Configuration(
|
||||
buildTools = setOf("33.0.0", "32.0.0", "31.0.0"),
|
||||
sdks = setOf(32, 33),
|
||||
commandlineTools = "8512546_latest",
|
||||
gradleVersion = "7.3.3",
|
||||
output = "outputs",
|
||||
dockerRepository = "fknives",
|
||||
dockerNamespace = "android-test-img",
|
||||
dockerTagPrefix = "1.0.0",
|
||||
apiLevelVariations = emptySet()
|
||||
)
|
||||
val rawConfiguration = resourceFileAsInputStream("single_configuration.toml")
|
||||
|
||||
val actual = parser.invoke(rawConfiguration)
|
||||
|
||||
Assertions.assertEquals(expectedConfiguration, actual)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package org.fnives.android.test.dockerfile.generator
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.ImageConfiguration
|
||||
import org.fnives.android.test.dockerfile.util.FixedClock
|
||||
import org.fnives.android.test.dockerfile.util.readResourceFile
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DockerFileContentGeneratorTest {
|
||||
|
||||
private lateinit var generator: DockerFileContentGenerator
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
generator = DockerFileContentGenerator(
|
||||
config = DockerFileContentConfig(
|
||||
templateGeneric = readResourceFile("Dockerfile.template"),
|
||||
templateSpecific = readResourceFile("Dockerfile.api.template"),
|
||||
dockerRepository = "fknives",
|
||||
dockerTagPrefix = "1.0",
|
||||
dockerNamespace = "ati"
|
||||
),
|
||||
clock = FixedClock(year = 2021, month = 11, dayOfMonth = 15)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generic() {
|
||||
val expected = DockerFileContent(
|
||||
content = readResourceFile("expected_generic_dockerfile"),
|
||||
name = "Dockerfile",
|
||||
tag = "fknives/ati:1.0"
|
||||
)
|
||||
val config = ImageConfiguration.Generic(
|
||||
buildTools = setOf("30.0.0", "32.0.0", "31.0.0", "30.0.3", "30.0.2"),
|
||||
sdks = setOf(33, 32, 31, 30),
|
||||
commandlineTools = "8512546_latest",
|
||||
gradleVersion = "7.3.3"
|
||||
)
|
||||
|
||||
val actual = generator.create(config)
|
||||
|
||||
Assertions.assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun api27() {
|
||||
val expected = DockerFileContent(
|
||||
content = readResourceFile("expected_api_27_dockerfile"),
|
||||
name = "Dockerfile-api-27",
|
||||
tag = "fknives/ati:1.0-api-27"
|
||||
)
|
||||
val config = ImageConfiguration.ApiVersion(27)
|
||||
|
||||
val actual = generator.create(config)
|
||||
|
||||
Assertions.assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package org.fnives.android.test.dockerfile.integration
|
||||
|
||||
import org.fnives.android.test.dockerfile.Main
|
||||
import org.fnives.android.test.dockerfile.di.ServiceLocatorHolder
|
||||
import org.fnives.android.test.dockerfile.util.CapturingProcessRunner
|
||||
import org.fnives.android.test.dockerfile.util.FixedClock
|
||||
import org.fnives.android.test.dockerfile.util.OverrideableClockServiceLocator
|
||||
import org.fnives.android.test.dockerfile.util.assertCollectionsEquals
|
||||
import org.fnives.android.test.dockerfile.util.readResourceFile
|
||||
import org.fnives.android.test.dockerfile.util.workDirAbsolutePathWithCommands
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
|
||||
class IntegrationTest {
|
||||
|
||||
@TempDir
|
||||
lateinit var tempDir: File
|
||||
|
||||
private lateinit var capturingProcessRunner: CapturingProcessRunner
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
capturingProcessRunner = CapturingProcessRunner()
|
||||
val mockServiceLocator = OverrideableClockServiceLocator()
|
||||
mockServiceLocator.clock = FixedClock(year = 2016, month = 5, dayOfMonth = 28)
|
||||
mockServiceLocator.processRunner = capturingProcessRunner
|
||||
|
||||
ServiceLocatorHolder.swap(mockServiceLocator)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
ServiceLocatorHolder.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noArgumentsGivesError() {
|
||||
val expectedMessage = "Required argument is missing! Required argument is the config.toml file path and just that!"
|
||||
|
||||
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
|
||||
Main.main(emptyArray())
|
||||
}
|
||||
|
||||
Assertions.assertEquals(expectedMessage, actual.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tooManyArgumentsGivesError() {
|
||||
val expectedMessage = "More arguments than expected! Required argument is the config.toml file path and just that!"
|
||||
|
||||
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
|
||||
Main.main(arrayOf("", ""))
|
||||
}
|
||||
|
||||
Assertions.assertEquals(expectedMessage, actual.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonExistentConfigurationFileGivesError() {
|
||||
val nonExistentFile = File(tempDir, "alma")
|
||||
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
|
||||
Main.main(arrayOf(nonExistentFile.absolutePath))
|
||||
}
|
||||
|
||||
Assertions.assertEquals("Can't find file at given path: ${nonExistentFile.absolutePath}", actual.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotParseConfigFile() {
|
||||
val file = File(tempDir, "alma")
|
||||
|
||||
file.createNewFile()
|
||||
val actual = Assertions.assertThrows(IllegalArgumentException::class.java) {
|
||||
Main.main(arrayOf(file.absolutePath))
|
||||
}
|
||||
|
||||
Assertions.assertEquals("Unparseable file at given path: ${file.absolutePath}", actual.message)
|
||||
Assertions.assertNotNull(actual.cause)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleConfiguration() {
|
||||
val file = File(tempDir, "config.toml")
|
||||
val outputDir = File(tempDir, "output")
|
||||
outputDir.mkdir()
|
||||
val updatedConfiguration = readResourceFile(resourceName = "single_configuration.toml")
|
||||
.replace("output = \"outputs\"", "output = \"${outputDir.absolutePath}\"")
|
||||
file.writeText(updatedConfiguration)
|
||||
|
||||
val expectedDockerCommands = listOf(
|
||||
outputDir.absolutePath to "docker build -t fknives/android-test-img:1.0.0 -f ${File(outputDir, "Dockerfile").absolutePath} .",
|
||||
outputDir.absolutePath to "docker push fknives/android-test-img:1.0.0",
|
||||
outputDir.absolutePath to "docker image rm -f fknives/android-test-img:1.0.0"
|
||||
)
|
||||
val expectedDockerFileContent = readResourceFile("expected_single_configuration.Dockerfile")
|
||||
val expectedScriptContent = readResourceFile("startemulator")
|
||||
|
||||
Main.main(arrayOf(file.absolutePath))
|
||||
val actualDockerfileContent = File(outputDir, "Dockerfile").readText()
|
||||
val actualScriptFileContent = File(outputDir, "startemulator").readText()
|
||||
|
||||
assertCollectionsEquals(expectedDockerCommands, capturingProcessRunner.workDirAbsolutePathWithCommands)
|
||||
Assertions.assertEquals(expectedDockerFileContent, actualDockerfileContent)
|
||||
Assertions.assertEquals(expectedScriptContent, actualScriptFileContent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multiApiConfiguration() {
|
||||
val file = File(tempDir, "config.toml")
|
||||
val outputDir = File(tempDir, "output")
|
||||
outputDir.mkdir()
|
||||
val updatedConfiguration = readResourceFile(resourceName = "multi_api_configuration.toml")
|
||||
.replace("output = \"outputs\"", "output = \"${outputDir.absolutePath}\"")
|
||||
file.writeText(updatedConfiguration)
|
||||
|
||||
val expectedDockerCommands = listOf(
|
||||
outputDir.absolutePath to "docker build -t fknives2/android-test-img-multi:1.0.1 -f ${File(outputDir, "Dockerfile").absolutePath} .",
|
||||
outputDir.absolutePath to "docker push fknives2/android-test-img-multi:1.0.1",
|
||||
outputDir.absolutePath to "docker image rm -f fknives2/android-test-img-multi:1.0.1",
|
||||
outputDir.absolutePath to "docker build -t fknives2/android-test-img-multi:1.0.1-api-22 -f ${File(outputDir, "Dockerfile-api-22").absolutePath} .",
|
||||
outputDir.absolutePath to "docker push fknives2/android-test-img-multi:1.0.1-api-22",
|
||||
outputDir.absolutePath to "docker image rm -f fknives2/android-test-img-multi:1.0.1-api-22",
|
||||
outputDir.absolutePath to "docker build -t fknives2/android-test-img-multi:1.0.1-api-30 -f ${File(outputDir, "Dockerfile-api-30").absolutePath} .",
|
||||
outputDir.absolutePath to "docker push fknives2/android-test-img-multi:1.0.1-api-30",
|
||||
outputDir.absolutePath to "docker image rm -f fknives2/android-test-img-multi:1.0.1-api-30"
|
||||
)
|
||||
val expectedDockerFileContent = readResourceFile("expected_multi_api_configuration.Dockerfile")
|
||||
val expectedDocker22FileContent = readResourceFile("expected_multi_api_configuration.22.Dockerfile")
|
||||
val expectedDocker30FileContent = readResourceFile("expected_multi_api_configuration.30.Dockerfile")
|
||||
val expectedScriptContent = readResourceFile("startemulator")
|
||||
|
||||
Main.main(arrayOf(file.absolutePath))
|
||||
val actualDockerfileContent = File(outputDir, "Dockerfile").readText()
|
||||
val actualDocker22fileContent = File(outputDir, "Dockerfile-api-22").readText()
|
||||
val actualDocker30fileContent = File(outputDir, "Dockerfile-api-30").readText()
|
||||
val actualScriptFileContent = File(outputDir, "startemulator").readText()
|
||||
|
||||
assertCollectionsEquals(expectedDockerCommands, capturingProcessRunner.workDirAbsolutePathWithCommands)
|
||||
Assertions.assertEquals(expectedDockerFileContent, actualDockerfileContent)
|
||||
Assertions.assertEquals(expectedDocker22FileContent, actualDocker22fileContent)
|
||||
Assertions.assertEquals(expectedDocker30FileContent, actualDocker30fileContent)
|
||||
Assertions.assertEquals(expectedScriptContent, actualScriptFileContent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package org.fnives.android.test.dockerfile.push
|
||||
|
||||
import org.fnives.android.test.dockerfile.util.CapturingProcessRunner
|
||||
import org.fnives.android.test.dockerfile.util.assertCollectionsEquals
|
||||
import org.fnives.android.test.dockerfile.util.workDirAbsolutePathWithCommands
|
||||
import org.fnives.android.test.dockerfile.write.DockerFileDescriptor
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
|
||||
internal class DockerBuildAndPushTest {
|
||||
|
||||
private lateinit var sut: DockerBuildAndPush
|
||||
private lateinit var capturingProcessRunner: CapturingProcessRunner
|
||||
private lateinit var expectedFolder: String
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
capturingProcessRunner = CapturingProcessRunner(
|
||||
defaultReturnValue = true,
|
||||
fallbackToDefault = true
|
||||
)
|
||||
val folderName = "folder"
|
||||
expectedFolder = File(folderName).absolutePath
|
||||
sut = DockerBuildAndPush(
|
||||
config = PushConfig(
|
||||
dockerRepository = "repo",
|
||||
dockerNamespace = "name",
|
||||
output = folderName
|
||||
),
|
||||
processRunner = capturingProcessRunner
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyBuildCommand() {
|
||||
val expected = listOf(expectedFolder to "docker build -t 123 -f /banan/alma .")
|
||||
|
||||
sut.build(DockerFileDescriptor("/banan/alma", "123"))
|
||||
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
|
||||
|
||||
assertCollectionsEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyPushCommand() {
|
||||
val expected = listOf(expectedFolder to "docker push id")
|
||||
|
||||
sut.push("id")
|
||||
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
|
||||
|
||||
assertCollectionsEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyCleanCommand() {
|
||||
val expected = listOf(expectedFolder to "docker image rm -f id")
|
||||
|
||||
sut.removeLocalImage("id")
|
||||
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
|
||||
|
||||
assertCollectionsEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyBuildPushClean() {
|
||||
val expected = listOf(
|
||||
expectedFolder to "docker build -t 123 -f banan/alma .",
|
||||
expectedFolder to "docker push 123",
|
||||
expectedFolder to "docker image rm -f 123"
|
||||
)
|
||||
|
||||
sut.buildPushClean(DockerFileDescriptor("banan/alma", "123"))
|
||||
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
|
||||
|
||||
assertCollectionsEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyMultipleBuildPushClean() {
|
||||
val expected = listOf(
|
||||
expectedFolder to "docker build -t 123 -f banan/alma .",
|
||||
expectedFolder to "docker push 123",
|
||||
expectedFolder to "docker image rm -f 123",
|
||||
expectedFolder to "docker build -t fknives/421:512.3 -f banan/alma2 .",
|
||||
expectedFolder to "docker push fknives/421:512.3",
|
||||
expectedFolder to "docker image rm -f fknives/421:512.3"
|
||||
)
|
||||
|
||||
sut.buildPushClean(DockerFileDescriptor("banan/alma", "123"))
|
||||
sut.buildPushClean(DockerFileDescriptor("banan/alma2", "fknives/421:512.3"))
|
||||
val actual = capturingProcessRunner.workDirAbsolutePathWithCommands
|
||||
|
||||
assertCollectionsEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.fnives.android.test.dockerfile.util
|
||||
|
||||
import org.junit.jupiter.api.Assertions
|
||||
|
||||
inline fun <reified T> assertCollectionsEquals(expected: Collection<T>, actual: Collection<T>) {
|
||||
Assertions.assertArrayEquals(expected.toTypedArray(), actual.toTypedArray())
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package org.fnives.android.test.dockerfile.util
|
||||
|
||||
import org.fnives.android.test.dockerfile.push.ProcessRunner
|
||||
import java.io.File
|
||||
|
||||
class CapturingProcessRunner : ProcessRunner {
|
||||
|
||||
private val _receivedCommands = mutableListOf<Pair<File, String>>()
|
||||
val receivedCommands get() = _receivedCommands.toList()
|
||||
|
||||
override fun run(workingDirectory: File, command: String) {
|
||||
_receivedCommands.add(workingDirectory to command)
|
||||
}
|
||||
}
|
||||
|
||||
val CapturingProcessRunner.workDirAbsolutePathWithCommands: List<Pair<String, String>>
|
||||
get() = receivedCommands.map { (workdir, command) -> workdir.absolutePath to command }
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package org.fnives.android.test.dockerfile.util
|
||||
|
||||
import org.fnives.android.test.dockerfile.util.clock.Clock
|
||||
import java.util.Calendar
|
||||
|
||||
class FixedClock(val timeMillis: Long) : Clock {
|
||||
|
||||
override fun currentTimeMillis(): Long = timeMillis
|
||||
}
|
||||
|
||||
fun FixedClock(year: Int, month: Int, dayOfMonth: Int): Clock {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.set(Calendar.YEAR, year)
|
||||
calendar.set(Calendar.MONTH, month - 1 + Calendar.JANUARY)
|
||||
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
|
||||
|
||||
return FixedClock(calendar.timeInMillis)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package org.fnives.android.test.dockerfile.util
|
||||
|
||||
import org.fnives.android.test.dockerfile.configuration.Configuration
|
||||
import org.fnives.android.test.dockerfile.configuration.ConfigurationParser
|
||||
import org.fnives.android.test.dockerfile.di.ConfiguredServiceLocator
|
||||
import org.fnives.android.test.dockerfile.di.ServiceLocator
|
||||
import org.fnives.android.test.dockerfile.di.ServiceLocatorHolder
|
||||
import org.fnives.android.test.dockerfile.push.ProcessRunner
|
||||
import org.fnives.android.test.dockerfile.util.clock.Clock
|
||||
|
||||
class OverrideableClockServiceLocator(private val delegate: ServiceLocator = ServiceLocatorHolder.default) : ServiceLocator by delegate {
|
||||
|
||||
var clock: Clock? = null
|
||||
var configuredServiceLocator: ConfiguredServiceLocator? = null
|
||||
var configurationParser: ConfigurationParser? = null
|
||||
var processRunner: ProcessRunner? = null
|
||||
var scriptToCopy: String? = null
|
||||
|
||||
override fun clock(): Clock = clock ?: delegate.clock()
|
||||
|
||||
override fun loadConfiguration(configuration: Configuration): ConfiguredServiceLocator =
|
||||
configuredServiceLocator ?: delegate.loadConfiguration(configuration)
|
||||
|
||||
override fun parser(): ConfigurationParser =
|
||||
configurationParser ?: delegate.parser()
|
||||
|
||||
override fun processRunner(): ProcessRunner =
|
||||
processRunner ?: delegate.processRunner()
|
||||
|
||||
override fun scriptToCopy(): String =
|
||||
scriptToCopy ?: delegate.scriptToCopy()
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.fnives.android.test.dockerfile.util
|
||||
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
internal fun Any.readResourceFile(resourceName: String): String = try {
|
||||
BufferedReader(InputStreamReader(resourceFileAsInputStream(resourceName)))
|
||||
.readLines().joinToString("\n")
|
||||
} catch (nullPointerException: NullPointerException) {
|
||||
throw IllegalArgumentException("$resourceName file not found!", nullPointerException)
|
||||
}
|
||||
|
||||
internal fun Any.resourceFileAsInputStream(resourceName: String): InputStream = try {
|
||||
this.javaClass.classLoader.getResourceAsStream(resourceName)!!
|
||||
} catch (nullPointerException: NullPointerException) {
|
||||
throw IllegalArgumentException("$resourceName file not found!", nullPointerException)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package org.fnives.android.test.dockerfile.write
|
||||
|
||||
import org.fnives.android.test.dockerfile.generator.DockerFileContent
|
||||
import org.fnives.android.test.dockerfile.util.assertCollectionsEquals
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
|
||||
internal class DockerFileWriterTest {
|
||||
|
||||
@TempDir
|
||||
lateinit var tempDirectory: File
|
||||
|
||||
@Test
|
||||
fun createsFolder() {
|
||||
val folderToCreate = File(tempDirectory, "childFolder")
|
||||
val sut = DockerFileWriter(outputFolder = folderToCreate)
|
||||
|
||||
sut.write(DockerFileContent(name = "alma", content = "-", tag = "123"))
|
||||
|
||||
Assertions.assertTrue(folderToCreate.exists()) { "Temp folder does not exist!" }
|
||||
Assertions.assertFalse(folderToCreate.isFile) { "Temp folder is a file not a Folder!" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun writesFileProperly() {
|
||||
val sut = DockerFileWriter(outputFolder = tempDirectory)
|
||||
val expected = listOf("123", "abc")
|
||||
|
||||
sut.write(DockerFileContent(name = "alma", content = "123\nabc", tag = "123"))
|
||||
val actual = File(tempDirectory, "alma")
|
||||
|
||||
assertCollectionsEquals(expected, actual.readLines())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun assertReturnsCorrectDescriptor() {
|
||||
val sut = DockerFileWriter(outputFolder = tempDirectory)
|
||||
val expectedFile = File(tempDirectory, "alma")
|
||||
val expected = DockerFileDescriptor(filePath = expectedFile.absolutePath, tag = "123")
|
||||
|
||||
val actual = sut.write(DockerFileContent(name = "alma", content = "123\nabc", tag = "123"))
|
||||
|
||||
Assertions.assertEquals(expected, actual)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package org.fnives.android.test.dockerfile.write
|
||||
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
|
||||
internal class ScriptCopierTest {
|
||||
|
||||
@TempDir
|
||||
lateinit var tempDirectory: File
|
||||
|
||||
@Test
|
||||
fun copyExample() {
|
||||
val sut = ScriptCopier(
|
||||
outputFolder = tempDirectory,
|
||||
scriptName = "example_to_copy"
|
||||
)
|
||||
|
||||
val actual = File(tempDirectory, "example_to_copy")
|
||||
sut.invoke()
|
||||
|
||||
val expected = arrayOf("123", "ab", "456")
|
||||
Assertions.assertArrayEquals(expected, actual.readLines().toTypedArray())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkActualCopyIsNotEmpty() {
|
||||
val sut = ScriptCopier(
|
||||
outputFolder = tempDirectory,
|
||||
scriptName = "startemulator"
|
||||
)
|
||||
|
||||
val actual = File(tempDirectory, "startemulator")
|
||||
sut.invoke()
|
||||
|
||||
Assertions.assertTrue(actual.readLines().isNotEmpty()) { "Content is empty when trying to copy startemulator" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createsFolder() {
|
||||
val folderToCreate = File(tempDirectory, "childFolder")
|
||||
val sut = ScriptCopier(
|
||||
outputFolder = folderToCreate,
|
||||
scriptName = "example_to_copy"
|
||||
)
|
||||
|
||||
sut.invoke()
|
||||
|
||||
Assertions.assertTrue(folderToCreate.exists()) { "Temp folder does not exist!" }
|
||||
Assertions.assertFalse(folderToCreate.isFile) { "Temp folder is a file not a Folder!" }
|
||||
}
|
||||
}
|
||||
11
main/src/test/resources/example_configuration.toml
Normal file
11
main/src/test/resources/example_configuration.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
buildTools = ["33.0.0", "32.0.0", "31.0.0", "30.0.3", "30.0.2"]
|
||||
sdks = [30, 31, 32, 33]
|
||||
androidCommandlineTools = "8512546_latest"
|
||||
gradleVersion = "7.3.3"
|
||||
output = "outputs"
|
||||
[variations]
|
||||
apiLevels = [21, 22, 23]
|
||||
[image]
|
||||
repository = "fknives"
|
||||
namespace = "android-test-img"
|
||||
tagPrefix = "1.0.0"
|
||||
3
main/src/test/resources/example_to_copy
Normal file
3
main/src/test/resources/example_to_copy
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
123
|
||||
ab
|
||||
456
|
||||
8
main/src/test/resources/expected_api_27_dockerfile
Normal file
8
main/src/test/resources/expected_api_27_dockerfile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
FROM fknives/ati:1.0
|
||||
# Generated on 2021-11-15
|
||||
|
||||
ENV EMULATOR_API_LEVEL 27
|
||||
|
||||
RUN /usr/local/bin/androidemulatorstart --buildOnly
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
78
main/src/test/resources/expected_generic_dockerfile
Normal file
78
main/src/test/resources/expected_generic_dockerfile
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
FROM openjdk:11-jdk-slim
|
||||
# Generated on 2021-11-15
|
||||
|
||||
# installing usual required tools
|
||||
# build-essential, ruby and bundler is needed for fastlane
|
||||
RUN apt-get update && apt-get install -y \
|
||||
sudo \
|
||||
wget \
|
||||
curl \
|
||||
unzip \
|
||||
android-sdk \
|
||||
build-essential \
|
||||
ruby-full && \
|
||||
gem install bundler
|
||||
|
||||
# versions
|
||||
# latest command lines version here: https://developer.android.com/studio#command-tools
|
||||
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "8512546_latest"
|
||||
# gradle version can be found in projects gradle-wrapper.properties
|
||||
ENV GRADLE_VERSION "7.3.3"
|
||||
|
||||
# set homes and paths
|
||||
ENV ANDROID_HOME "/usr/lib/android-sdk"
|
||||
ENV ANDROID_SDK_ROOT $ANDROID_HOME
|
||||
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
|
||||
ENV AVD_HOME "/root/.android/avd"
|
||||
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
|
||||
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
|
||||
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
# install gradle
|
||||
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
|
||||
unzip -d /usr/local /tmp/gradle.zip && \
|
||||
rm -rf /tmp/gradle.zip && \
|
||||
echo "1" | gradle init && \
|
||||
/root/gradlew && \
|
||||
rm -rf /root/* && \
|
||||
rm /root/.gitignore
|
||||
|
||||
# install command line tools
|
||||
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
|
||||
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
|
||||
mv cmdline-tools $ANDROID_HOME/
|
||||
|
||||
# install emulator and setup sdkmanager
|
||||
RUN yes | sdkmanager --licenses > /dev/null && \
|
||||
sdkmanager --install emulator --channel=0 > /dev/null
|
||||
|
||||
# support libraries and google play services
|
||||
RUN echo y | sdkmanager "extras;android;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;google_play_services"
|
||||
|
||||
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
|
||||
# install build-tools
|
||||
# example: `echo y | sdkmanager "build-tools;33.0.0"`
|
||||
RUN echo y | sdkmanager "build-tools;30.0.0" && \
|
||||
echo y | sdkmanager "build-tools;32.0.0" && \
|
||||
echo y | sdkmanager "build-tools;31.0.0" && \
|
||||
echo y | sdkmanager "build-tools;30.0.3" && \
|
||||
echo y | sdkmanager "build-tools;30.0.2"
|
||||
|
||||
# install sdks
|
||||
# example: `echo y | sdkmanager "platforms;android-33"`
|
||||
RUN echo y | sdkmanager "platforms;android-33" && \
|
||||
echo y | sdkmanager "platforms;android-32" && \
|
||||
echo y | sdkmanager "platforms;android-31" && \
|
||||
echo y | sdkmanager "platforms;android-30"
|
||||
|
||||
# copy script to install sdk, create emulator and boot it
|
||||
COPY ./startemulator /usr/local/bin/androidemulatorstart
|
||||
RUN chmod 744 /usr/local/bin/androidemulatorstart
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
FROM fknives2/android-test-img-multi:1.0.1
|
||||
# Generated on 2016-05-28
|
||||
|
||||
ENV EMULATOR_API_LEVEL 22
|
||||
|
||||
RUN /usr/local/bin/androidemulatorstart --buildOnly
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
FROM fknives2/android-test-img-multi:1.0.1
|
||||
# Generated on 2016-05-28
|
||||
|
||||
ENV EMULATOR_API_LEVEL 30
|
||||
|
||||
RUN /usr/local/bin/androidemulatorstart --buildOnly
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
FROM openjdk:11-jdk-slim
|
||||
# Generated on 2016-05-28
|
||||
|
||||
# installing usual required tools
|
||||
# build-essential, ruby and bundler is needed for fastlane
|
||||
RUN apt-get update && apt-get install -y \
|
||||
sudo \
|
||||
wget \
|
||||
curl \
|
||||
unzip \
|
||||
android-sdk \
|
||||
build-essential \
|
||||
ruby-full && \
|
||||
gem install bundler
|
||||
|
||||
# versions
|
||||
# latest command lines version here: https://developer.android.com/studio#command-tools
|
||||
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "8512546_latest"
|
||||
# gradle version can be found in projects gradle-wrapper.properties
|
||||
ENV GRADLE_VERSION "7.3.3"
|
||||
|
||||
# set homes and paths
|
||||
ENV ANDROID_HOME "/usr/lib/android-sdk"
|
||||
ENV ANDROID_SDK_ROOT $ANDROID_HOME
|
||||
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
|
||||
ENV AVD_HOME "/root/.android/avd"
|
||||
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
|
||||
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
|
||||
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
# install gradle
|
||||
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
|
||||
unzip -d /usr/local /tmp/gradle.zip && \
|
||||
rm -rf /tmp/gradle.zip && \
|
||||
echo "1" | gradle init && \
|
||||
/root/gradlew && \
|
||||
rm -rf /root/* && \
|
||||
rm /root/.gitignore
|
||||
|
||||
# install command line tools
|
||||
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
|
||||
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
|
||||
mv cmdline-tools $ANDROID_HOME/
|
||||
|
||||
# install emulator and setup sdkmanager
|
||||
RUN yes | sdkmanager --licenses > /dev/null && \
|
||||
sdkmanager --install emulator --channel=0 > /dev/null
|
||||
|
||||
# support libraries and google play services
|
||||
RUN echo y | sdkmanager "extras;android;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;google_play_services"
|
||||
|
||||
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
|
||||
# install build-tools
|
||||
# example: `echo y | sdkmanager "build-tools;33.0.0"`
|
||||
RUN echo y | sdkmanager "build-tools;33.0.0" && \
|
||||
echo y | sdkmanager "build-tools;32.0.0"
|
||||
|
||||
# install sdks
|
||||
# example: `echo y | sdkmanager "platforms;android-33"`
|
||||
RUN echo y | sdkmanager "platforms;android-30" && \
|
||||
echo y | sdkmanager "platforms;android-31"
|
||||
|
||||
# copy script to install sdk, create emulator and boot it
|
||||
COPY ./startemulator /usr/local/bin/androidemulatorstart
|
||||
RUN chmod 744 /usr/local/bin/androidemulatorstart
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
FROM openjdk:11-jdk-slim
|
||||
# Generated on 2016-05-28
|
||||
|
||||
# installing usual required tools
|
||||
# build-essential, ruby and bundler is needed for fastlane
|
||||
RUN apt-get update && apt-get install -y \
|
||||
sudo \
|
||||
wget \
|
||||
curl \
|
||||
unzip \
|
||||
android-sdk \
|
||||
build-essential \
|
||||
ruby-full && \
|
||||
gem install bundler
|
||||
|
||||
# versions
|
||||
# latest command lines version here: https://developer.android.com/studio#command-tools
|
||||
ENV ANDROID_COMMAND_LINES_TOOLS_VERSION "8512546_latest"
|
||||
# gradle version can be found in projects gradle-wrapper.properties
|
||||
ENV GRADLE_VERSION "7.3.3"
|
||||
|
||||
# set homes and paths
|
||||
ENV ANDROID_HOME "/usr/lib/android-sdk"
|
||||
ENV ANDROID_SDK_ROOT $ANDROID_HOME
|
||||
ENV CMDLINE_TOOLS_ROOT "${ANDROID_HOME}/cmdline-tools/latest/bin"
|
||||
ENV AVD_HOME "/root/.android/avd"
|
||||
ENV PATH "$ANDROID_HOME/cmdline-tools/latest/bin:${PATH}"
|
||||
ENV PATH "$ANDROID_HOME/emulator:${PATH}"
|
||||
ENV PATH "/usr/local/gradle-${GRADLE_VERSION}/bin:${PATH}"
|
||||
|
||||
WORKDIR /root/
|
||||
|
||||
# install gradle
|
||||
RUN curl -sSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip && \
|
||||
unzip -d /usr/local /tmp/gradle.zip && \
|
||||
rm -rf /tmp/gradle.zip && \
|
||||
echo "1" | gradle init && \
|
||||
/root/gradlew && \
|
||||
rm -rf /root/* && \
|
||||
rm /root/.gitignore
|
||||
|
||||
# install command line tools
|
||||
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
unzip commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip -d cmdline-tools && \
|
||||
rm commandlinetools-linux-$ANDROID_COMMAND_LINES_TOOLS_VERSION.zip && \
|
||||
mv cmdline-tools/cmdline-tools/ cmdline-tools/latest && \
|
||||
mv cmdline-tools $ANDROID_HOME/
|
||||
|
||||
# install emulator and setup sdkmanager
|
||||
RUN yes | sdkmanager --licenses > /dev/null && \
|
||||
sdkmanager --install emulator --channel=0 > /dev/null
|
||||
|
||||
# support libraries and google play services
|
||||
RUN echo y | sdkmanager "extras;android;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;m2repository" && \
|
||||
echo y | sdkmanager "extras;google;google_play_services"
|
||||
|
||||
# build tools versions and sdk versions can be found via sdkmanager --list and filtering it
|
||||
# install build-tools
|
||||
# example: `echo y | sdkmanager "build-tools;33.0.0"`
|
||||
RUN echo y | sdkmanager "build-tools;33.0.0" && \
|
||||
echo y | sdkmanager "build-tools;32.0.0" && \
|
||||
echo y | sdkmanager "build-tools;31.0.0"
|
||||
|
||||
# install sdks
|
||||
# example: `echo y | sdkmanager "platforms;android-33"`
|
||||
RUN echo y | sdkmanager "platforms;android-32" && \
|
||||
echo y | sdkmanager "platforms;android-33"
|
||||
|
||||
# copy script to install sdk, create emulator and boot it
|
||||
COPY ./startemulator /usr/local/bin/androidemulatorstart
|
||||
RUN chmod 744 /usr/local/bin/androidemulatorstart
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
11
main/src/test/resources/multi_api_configuration.toml
Normal file
11
main/src/test/resources/multi_api_configuration.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
buildTools = ["33.0.0", "32.0.0"]
|
||||
sdks = [30, 31]
|
||||
androidCommandlineTools = "8512546_latest"
|
||||
gradleVersion = "7.3.3"
|
||||
output = "outputs"
|
||||
[variations]
|
||||
apiLevels = [22, 30]
|
||||
[image]
|
||||
repository = "fknives2"
|
||||
namespace = "android-test-img-multi"
|
||||
tagPrefix = "1.0.1"
|
||||
9
main/src/test/resources/single_configuration.toml
Normal file
9
main/src/test/resources/single_configuration.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
buildTools = ["33.0.0", "32.0.0", "31.0.0"]
|
||||
sdks = [32, 33]
|
||||
androidCommandlineTools = "8512546_latest"
|
||||
gradleVersion = "7.3.3"
|
||||
output = "outputs"
|
||||
[image]
|
||||
repository = "fknives"
|
||||
namespace = "android-test-img"
|
||||
tagPrefix = "1.0.0"
|
||||
Loading…
Add table
Add a link
Reference in a new issue