Skip to content

Commit

Permalink
Merge pull request #30 from teogor/feature/add-build-fingerprint-reader
Browse files Browse the repository at this point in the history
Add BuildFingerprint Data Class and File Reader for Unity Build Information
  • Loading branch information
teogor authored Aug 20, 2024
2 parents 87aa093 + 7acc875 commit 9573fa7
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 4 deletions.
50 changes: 50 additions & 0 deletions gradle-plugin/api/gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,56 @@ public final class dev/teogor/drifter/plugin/models/UnityOptions {
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/drifter/plugin/unity/BuildFingerprint {
public fun <init> (Ldev/teogor/drifter/plugin/unity/UnityVersion;Ljava/lang/String;Ljava/lang/String;II)V
public final fun component1 ()Ldev/teogor/drifter/plugin/unity/UnityVersion;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()I
public final fun component5 ()I
public final fun copy (Ldev/teogor/drifter/plugin/unity/UnityVersion;Ljava/lang/String;Ljava/lang/String;II)Ldev/teogor/drifter/plugin/unity/BuildFingerprint;
public static synthetic fun copy$default (Ldev/teogor/drifter/plugin/unity/BuildFingerprint;Ldev/teogor/drifter/plugin/unity/UnityVersion;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/Object;)Ldev/teogor/drifter/plugin/unity/BuildFingerprint;
public fun equals (Ljava/lang/Object;)Z
public final fun getBuildType ()Ljava/lang/String;
public final fun getOptimizedFramePacing ()I
public final fun getScriptingBackend ()Ljava/lang/String;
public final fun getStripEngineCode ()I
public final fun getUnityVersion ()Ldev/teogor/drifter/plugin/unity/UnityVersion;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/drifter/plugin/unity/BuildFingerprintKt {
public static final fun readBuildFingerprint (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ldev/teogor/drifter/plugin/unity/BuildFingerprint;
public static synthetic fun readBuildFingerprint$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/teogor/drifter/plugin/unity/BuildFingerprint;
}

public final class dev/teogor/drifter/plugin/unity/UnityVersion {
public static final field Companion Ldev/teogor/drifter/plugin/unity/UnityVersion$Companion;
public fun <init> (IIILjava/lang/String;)V
public final fun component1 ()I
public final fun component2 ()I
public final fun component3 ()I
public final fun component4 ()Ljava/lang/String;
public final fun copy (IIILjava/lang/String;)Ldev/teogor/drifter/plugin/unity/UnityVersion;
public static synthetic fun copy$default (Ldev/teogor/drifter/plugin/unity/UnityVersion;IIILjava/lang/String;ILjava/lang/Object;)Ldev/teogor/drifter/plugin/unity/UnityVersion;
public fun equals (Ljava/lang/Object;)Z
public final fun getMajor ()I
public final fun getMinor ()I
public final fun getPatch ()Ljava/lang/String;
public final fun getYear ()I
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/teogor/drifter/plugin/unity/UnityVersion$Companion {
public final fun parseString (Ljava/lang/String;)Ldev/teogor/drifter/plugin/unity/UnityVersion;
}

public final class dev/teogor/drifter/plugin/unity/UnityVersionKt {
public static final fun toUnityVersion (Ljava/lang/String;)Ldev/teogor/drifter/plugin/unity/UnityVersion;
}

public final class dev/teogor/drifter/plugin/utils/error/UnityOptionsNotInitializedException : java/lang/RuntimeException {
public fun <init> ()V
}
Expand Down
1 change: 1 addition & 0 deletions gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ tasks.withType<KotlinCompile>().configureEach {
dependencies {
api(gradleApi())

compileOnly(libs.jetbrains.kotlinx.serialization.json)
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ open class RefreshUnityAssetsTask : DefaultTask() {
File(unityOptions.exportedProjectLocation)
}

private val targetDir: File by lazy {
project.projectDir
}
private val targetDir: File by lazy { project.projectDir }

init {
group = "dev.teogor.drifter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package dev.teogor.drifter.plugin

import com.android.build.api.dsl.CommonExtension
import dev.teogor.drifter.plugin.models.Configuration
import dev.teogor.drifter.plugin.models.UnityOptions
import dev.teogor.drifter.plugin.unity.readBuildFingerprint
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies

Expand All @@ -30,7 +32,11 @@ fun Project.unityOptions(
androidConfig: CommonExtension<*, *, *, *, *, *>,
configure: UnityOptions.() -> Unit,
) {
val unityOptions = UnityOptions().apply(configure)
val unityOptions = UnityOptions()
.apply(configure)
.let { parseBuildFingerprint(it) }

parseBuildFingerprint(unityOptions)

androidConfig.applyUnityManifestPlaceholders(
unityOptions = unityOptions,
Expand All @@ -55,3 +61,33 @@ fun Project.unityOptions(
}
}
}

/**
* Updates the given [UnityOptions] based on the build fingerprint read from a file.
*
* This function reads the build fingerprint file located at the root project directory
* and updates the `configuration` property of the provided [UnityOptions] instance
* based on the build type specified in the fingerprint.
*
* @param unityOptions The [UnityOptions] instance to be updated.
* @return The updated [UnityOptions] instance with configuration set according to the build fingerprint.
*/
private fun Project.parseBuildFingerprint(
unityOptions: UnityOptions,
): UnityOptions {
val parsedFingerprint = readBuildFingerprint(
dirPath = projectDir.absolutePath,
)

if (parsedFingerprint != null) {
unityOptions.apply {
configuration = when (parsedFingerprint.buildType) {
"Release" -> Configuration.Release
"Debug" -> Configuration.Debug
else -> configuration
}
}
}

return unityOptions
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2024 teogor (Teodor Grigor)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.teogor.drifter.plugin.unity

import java.io.File
import java.io.IOException

/**
* Data class to represent the build fingerprint information.
*
* @param unityVersion The Unity version used.
* @param scriptingBackend The scripting backend used (e.g., IL2CPP).
* @param buildType The build configuration (e.g., Release).
* @param stripEngineCode Indicates if engine code is stripped.
* @param optimizedFramePacing Indicates if frame pacing is optimized.
*/
data class BuildFingerprint(
val unityVersion: UnityVersion,
val scriptingBackend: String,
val buildType: String,
val stripEngineCode: Int,
val optimizedFramePacing: Int,
)

/**
* Reads and parses the build fingerprint information from a file.
*
* @param dirPath The directory path where the file is located.
* @param filePath The path within the directory where the file is located.
* @param fileName The name of the file to read.
* @return A [BuildFingerprint] object containing the parsed data, or null if an error occurs.
*/
fun readBuildFingerprint(
dirPath: String,
filePath: String = "src/main/resources/META-INF",
fileName: String = "com.android.games.engine.build_fingerprint",
): BuildFingerprint? {
// Construct the full file path
val fullFilePath = "$dirPath/$filePath/$fileName"

// Attempt to access the file
val file = runCatching { File(fullFilePath) }
.getOrElse {
return null
}

return try {
// Read the file content
val content = file.readText().trim()

// Split the content by semicolons
val parts = content.split(';')
.dropLastWhile { it.isEmpty() }

// Ensure we have the expected number of parts
if (parts.size != 5) {
println("Unexpected file format. Expected 5 parts but found ${parts.size}.")
println(parts)
return null
}

// Extract and validate each part
val unityVersion = parts[0].takeIf { it.isNotBlank() }?.toUnityVersion() ?: run {
println("Invalid Unity Version.")
return null
}

val scriptingBackend = parts[1].takeIf { it.isNotBlank() } ?: run {
println("Invalid Scripting Backend.")
return null
}

val buildType = parts[2].takeIf { it.isNotBlank() } ?: run {
println("Invalid Build Type.")
return null
}

val stripEngineCode = parts[3].removePrefix("StripEngineCode:").toIntOrNull() ?: run {
println("Invalid Strip Engine Code.")
return null
}

val optimizedFramePacing = parts[4].removePrefix("OptimizedFramePacing:").toIntOrNull() ?: run {
println("Invalid Optimized Frame Pacing.")
return null
}

// Create and return the BuildFingerprint object
BuildFingerprint(
unityVersion = unityVersion,
scriptingBackend = scriptingBackend,
buildType = buildType,
stripEngineCode = stripEngineCode,
optimizedFramePacing = optimizedFramePacing,
)
} catch (e: IOException) {
println("Error reading the build fingerprint file: ${e.message}")
null
} catch (e: NumberFormatException) {
println("Error parsing the build fingerprint values: ${e.message}")
null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2024 teogor (Teodor Grigor)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.teogor.drifter.plugin.unity

/**
* Data class to represent the Unity version.
*
* This class encapsulates the components of a Unity version, which typically includes
* the year, major version, minor version, and an optional patch version suffix.
*
* @param year The year of the Unity version, represented as a 4-digit integer.
* @param major The major version number, represented as an integer.
* @param minor The minor version number, represented as an integer.
* @param patch The patch version with optional suffix, represented as a string.
*/
data class UnityVersion(
val year: Int,
val major: Int,
val minor: Int,
val patch: String,
) {

/**
* Converts the [UnityVersion] object to a string in the format "YYYY.M.mfX".
*
* @return The Unity version string representation.
*/
override fun toString(): String {
return "v$year.$major.$minor$patch"
}

companion object {
/**
* Parses a Unity version string into a [UnityVersion] object.
*
* This method takes a Unity version string in the format "YYYY.M.mX", where:
* - `YYYY` is the 4-digit year,
* - `M` is the major version number,
* - `m` is the minor version number,
* - `X` is an optional patch suffix (can be letters and/or numbers).
*
* @param versionString The Unity version string to be parsed, e.g., "2022.3.7f1".
* @return A [UnityVersion] object if the format is valid, or null if parsing fails.
*/
fun parseString(versionString: String): UnityVersion? {
// Define a regular expression to match the Unity version format
val regex = """(\d{4})\.(\d+)\.(\d+)([a-zA-Z0-9]*)""".toRegex()

return regex.matchEntire(versionString)?.let { matchResult ->
// Extract components from the match
val (yearStr, majorStr, minorStr, patchStr) = matchResult.destructured

// Parse components into integers and strings
val year = yearStr.toIntOrNull() ?: return null
val major = majorStr.toIntOrNull() ?: return null
val minor = minorStr.toIntOrNull() ?: return null

// Return the UnityVersion object
UnityVersion(
year = year,
major = major,
minor = minor,
patch = patchStr,
)
} ?: run {
// Return null if the version string does not match the expected format
null
}
}
}
}

/**
* Extension function to convert a string into a [UnityVersion] object.
*
* This function is a convenient wrapper around the [UnityVersion.parseString] method
* and allows for a more fluent API when converting strings to UnityVersion instances.
*
* @return A [UnityVersion] object if the string is a valid Unity version format, or null if parsing fails.
*/
fun String.toUnityVersion(): UnityVersion? {
return UnityVersion.parseString(this)
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ compose-bom = "2024.06.00"
appcompat = "1.7.0"
kotlin = "2.0.10"
ksp = "2.0.10-1.0.24"
kotlinx-serialization = "1.7.1"
material = "1.12.0"
startup-runtime = "1.2.0-alpha02"
gson = "2.11.0"
Expand Down Expand Up @@ -107,6 +108,7 @@ material = { group = "com.google.android.material", name = "material", version.r
kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlin-poet" }
kotlin-poet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
jetbrains-kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }

# Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
Expand Down

0 comments on commit 9573fa7

Please sign in to comment.