-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1946 from instructure/release/parent-3.7.0-45
Release Parent 3.7.0 (45)
- Loading branch information
Showing
791 changed files
with
12,305 additions
and
7,809 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright (C) 2023 - present Instructure, Inc. | ||
* | ||
* 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 | ||
* | ||
* http://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. | ||
* | ||
*/ | ||
repositories { | ||
google() | ||
mavenCentral() | ||
} | ||
|
||
val agpVersion = "4.1.0" | ||
|
||
dependencies { | ||
implementation("com.android.tools.build:gradle:$agpVersion") | ||
implementation("com.android.tools.build:gradle-api:$agpVersion") | ||
implementation("org.javassist:javassist:3.24.1-GA") | ||
implementation("com.google.code.gson:gson:2.8.8") | ||
} | ||
|
||
plugins { | ||
`kotlin-dsl` | ||
} |
159 changes: 159 additions & 0 deletions
159
apps/flutter_parent/android/buildSrc/src/main/groovy/TimingsListener.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import org.gradle.BuildListener | ||
import org.gradle.BuildResult | ||
import org.gradle.api.Project | ||
import org.gradle.api.Task | ||
import org.gradle.api.execution.TaskExecutionListener | ||
import org.gradle.api.initialization.Settings | ||
import org.gradle.api.invocation.Gradle | ||
import org.gradle.api.tasks.TaskState | ||
|
||
import java.util.concurrent.TimeUnit | ||
|
||
// A listener that will allow us to monitor task timings and, ultimately, APK size | ||
class TimingsListener implements TaskExecutionListener, BuildListener { | ||
private long startTime | ||
private timings = [:] | ||
private Project refProject | ||
private long buildStartTime | ||
|
||
TimingsListener(Project _refProject) { | ||
refProject = _refProject | ||
buildStartTime = System.nanoTime() | ||
} | ||
|
||
|
||
@Override | ||
void beforeExecute(Task task) { | ||
startTime = System.nanoTime() | ||
} | ||
|
||
@Override | ||
void afterExecute(Task task, TaskState taskState) { | ||
def ms = TimeUnit.MILLISECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS) | ||
timings.put(task.name, ms) | ||
} | ||
|
||
@Override | ||
void buildFinished(BuildResult result) { | ||
|
||
// Compute build time | ||
def totalBuildTimeMs = TimeUnit.MILLISECONDS.convert(System.nanoTime() - buildStartTime, TimeUnit.NANOSECONDS) | ||
|
||
// Grab the Splunk-mobile token from Bitrise | ||
def splunkToken = System.getenv("SPLUNK_MOBILE_TOKEN") | ||
|
||
// Let's abort early if (1) the build failed, or (2) we're not on bitrise | ||
if(result.failure != null) { | ||
println("Build report logic aborting due to failed build") | ||
return | ||
} | ||
|
||
if(splunkToken == null || splunkToken.isEmpty()) { | ||
println("Build report logic aborting because we're not on bitrise") | ||
return | ||
} | ||
|
||
// Grab the gradle tasks passed on the command line for the job | ||
def startTaskNames = result.gradle.startParameter.taskNames.join(",") | ||
|
||
// Sort the timings in descending time order, compute our top 10 | ||
timings = timings.sort { -it.value } | ||
def top10 = timings.take(10).entrySet() | ||
|
||
// Figure out our build type | ||
def buildType = "debug" | ||
if(startTaskNames.contains("Release")) { | ||
buildType = "release" | ||
} | ||
|
||
// Figure out our build flavor | ||
def buildFlavor = "qa" | ||
if(startTaskNames.contains("Dev")) { | ||
buildFlavor = "dev" | ||
} | ||
else if(startTaskNames.contains("Prod")) { | ||
buildFlavor = "prod" | ||
} | ||
|
||
// Grab some data from the environment | ||
def bitriseWorkflow = System.getenv("BITRISE_TRIGGERED_WORKFLOW_ID") | ||
def bitriseApp = System.getenv("BITRISE_APP_TITLE") | ||
def bitriseBranch = System.getenv("BITRISE_GIT_BRANCH") | ||
def bitriseBuildNumber = System.getenv("BITRISE_BUILD_NUMBER") | ||
|
||
// Determine our project name. | ||
// It's not as simple as looking at refProject.name; since we add this listener via the | ||
// student project, refProject.name will always be "student". Glean our actual project name | ||
// via the bitrise app name. | ||
def projectName = "" | ||
if(bitriseApp.contains("Student")) { | ||
projectName = "student" | ||
} | ||
else if(bitriseApp.contains("Teacher")) { | ||
projectName = "teacher" | ||
} | ||
else if(bitriseApp.toLowerCase().contains("parent")) { | ||
projectName = "parent" | ||
} | ||
else { | ||
projectName = "unknown" // Punt | ||
} | ||
println("projectName = $projectName") | ||
|
||
// Locate the apk | ||
def file = null | ||
def fileSizeInMB = 0.0 | ||
if(projectName!="parent") { | ||
// We don't necessarily want refProject.buildDir, since it will always be the student buildDir | ||
def buildDir = refProject.buildDir.toString().replace("student",projectName) | ||
file = new File("$buildDir/outputs/apk/$buildFlavor/$buildType/$projectName-$buildFlavor-${buildType}.apk") | ||
fileSizeInMB = file.length() == 0 ? 0 : (file.length() / (1024.0 * 1024.0)).round(3) | ||
} | ||
else { | ||
// Different location logic for flutter parent apk file | ||
def buildDir = refProject.buildDir.toString() | ||
file = new File("$buildDir/outputs/apk/$buildType/app-${buildType}.apk") | ||
fileSizeInMB = file.length() == 0 ? 0 : (file.length() / (1024.0 * 1024.0)) | ||
fileSizeInMB = (fileSizeInMB * 1000.0).toInteger() / 1000.0 // Round to three decimal places | ||
} | ||
println("file name=${file.path} length=${file.length()}") | ||
|
||
|
||
// Construct the JSON payload for our "buildComplete" event | ||
def payloadBuilder = new groovy.json.JsonBuilder() | ||
payloadBuilder buildTime: totalBuildTimeMs, | ||
gradleTasks: startTaskNames, | ||
apkFilePath: file.path, | ||
apkSize: fileSizeInMB, | ||
bitriseWorkflow: bitriseWorkflow, | ||
bitriseApp: bitriseApp, | ||
bitriseBranch: bitriseBranch, | ||
bitriseBuildNumber: bitriseBuildNumber, | ||
topTasks: top10 | ||
|
||
// Create the event payload. Change key/value in top 10 tasks to task/ms. | ||
def payload = payloadBuilder.toString().replaceAll("\"key\"", "\"task\"").replaceAll("\"value\"", "\"ms\"") | ||
|
||
println("event payload: $payload") | ||
|
||
// Let's issue our curl command to emit our data | ||
refProject.exec { | ||
executable "curl" | ||
args "-k", "https://http-inputs-inst.splunkcloud.com:443/services/collector", "-H", "Authorization: Splunk $splunkToken", | ||
"-d", "{\"sourcetype\" : \"mobile-android-build\", \"event\" : $payload}" | ||
} | ||
|
||
} | ||
|
||
@Override | ||
void buildStarted(Gradle gradle) {} | ||
|
||
@Override | ||
void projectsEvaluated(Gradle gradle) {} | ||
|
||
@Override | ||
void projectsLoaded(Gradle gradle) {} | ||
|
||
@Override | ||
void settingsEvaluated(Settings settings) {} | ||
} |
99 changes: 99 additions & 0 deletions
99
apps/flutter_parent/android/buildSrc/src/main/java/MergePrivateData.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
@file:Suppress("unused") | ||
|
||
import org.gradle.api.Project | ||
import org.gradle.kotlin.dsl.extra | ||
import java.io.File | ||
import java.io.FileInputStream | ||
import java.io.FileNotFoundException | ||
import java.security.MessageDigest | ||
import java.util.* | ||
|
||
object PrivateData { | ||
|
||
private const val PRINT_PAD_SIZE = 60 | ||
|
||
@JvmStatic | ||
@JvmOverloads | ||
fun merge(project: Project, dataDirName: String = "") { | ||
|
||
val baseDir = resolvePrivateDataDir(project.projectDir) | ||
val dataDir = File(baseDir, dataDirName).canonicalFile | ||
|
||
println("") | ||
println("============= MERGE PRIVATE FILES: ${dataDirName.toUpperCase()} ".padEnd(PRINT_PAD_SIZE, '=')) | ||
|
||
/* Confirm dir exists */ | ||
if (!dataDir.exists() || !dataDir.isDirectory) { | ||
failMerge("Unable to find private data source directory at ${dataDir.canonicalPath}. Please add private project files.") | ||
} | ||
|
||
/* Confirm files list exists */ | ||
val fileList = File(dataDir, "files.list") | ||
if (!fileList.exists()) { | ||
failMerge("Unable to find file list at ${fileList.canonicalPath}. Ensure private project files have been added.") | ||
} | ||
|
||
/* Grab list of files */ | ||
val props = Properties() | ||
FileInputStream(fileList).use { props.load(it) } | ||
|
||
/* Copy files and properties */ | ||
props.stringPropertyNames().forEach { srcName -> | ||
val src = File(dataDir, srcName) | ||
if (!src.exists() || !src.isFile) { | ||
failMerge("Could not find source file at ${src.canonicalPath}") | ||
} | ||
|
||
/* Merge private.properties */ | ||
if (srcName == "private.properties") { | ||
println("Merging private.properties:") | ||
val privateProps = Properties() | ||
FileInputStream(src).use { privateProps.load(it) } | ||
privateProps.stringPropertyNames().forEach { propName -> | ||
println(" $propName") | ||
var value = privateProps.getProperty(propName) | ||
if (value.startsWith("file:")) { | ||
val fileName = value.replaceFirst("file:", "") | ||
val file = File(dataDir, fileName) | ||
if (!file.exists()) { | ||
failMerge("Could not find source file ${file.canonicalPath} for '$propName' specified in private.properties") | ||
} | ||
value = file.readText() | ||
} | ||
val escaped = value.replace("\"", "\\\"").replace("[\n\r]", "") | ||
project.extra.set(propName, escaped) | ||
} | ||
} else { | ||
val dstPath = props.getProperty(srcName) | ||
val dst = File(project.projectDir, dstPath) | ||
|
||
/* Make parent dir if necessary */ | ||
val dstParent = dst.parentFile | ||
if (!dstParent.exists()) dstParent.mkdirs() | ||
|
||
if (!dst.exists()) { | ||
println("Copying ${src.canonicalPath} to $dst") | ||
src.copyTo(dst, true) | ||
} else if (!src.md5.contentEquals(dst.md5)) { | ||
println("${dst.canonicalPath} differs from $src and will be replaced") | ||
src.copyTo(dst, true) | ||
} else { | ||
println("${dst.canonicalPath} exists and is UP-TO-DATE") | ||
} | ||
} | ||
} | ||
|
||
println("".padEnd(PRINT_PAD_SIZE, '=')) | ||
println("") | ||
} | ||
|
||
private fun failMerge(message: String): Nothing = throw Exception("Failed to merge private data. $message") | ||
|
||
private val File.md5 get() = MessageDigest.getInstance("MD5").digest(readBytes()) | ||
|
||
private tailrec fun resolvePrivateDataDir(srcDir: File?): File { | ||
if (srcDir == null) throw FileNotFoundException("Could not locate private data directory!") | ||
return srcDir.resolve("private-data").takeIf { it.exists() && it.isDirectory } | ||
?: resolvePrivateDataDir(srcDir.parentFile) | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.