Support buildIds in reportModifications task

Now the task could print all modified projects and their dependents
between two given build ids.

bug: 67555870
Test: ./gradlew reportModifications -PbuildIds=4385749...4387453
Change-Id: I73a11754e4b992440f297b165c655870d2a1264a
diff --git a/buildSrc/modifications_reporter.gradle b/buildSrc/modifications_reporter.gradle
index b8fcbc5..cfa0f2b 100644
--- a/buildSrc/modifications_reporter.gradle
+++ b/buildSrc/modifications_reporter.gradle
@@ -16,20 +16,29 @@
 
 import android.support.ModifiedProjectsReporter
 
+def supportRoot = ext.supportRootFolder
 task(reportModifications) {
-    doLast{
-        if (!rootProject.hasProperty("SHAs")) {
-            throw new GradleException("You must specify SHAs range: -PSHAs=<rev1>...<rev2>")
+    doLast {
+        if (!rootProject.hasProperty("SHAs") && !rootProject.hasProperty("buildIds")) {
+            throw new GradleException("You must specify SHAs range: -PSHAs=<rev1>...<rev2>"
+                    + "or builds range: -PbuildIds=<buildId1>..<buildId2>")
         }
-        def range = SHAs.tokenize('...')
-        if (range.size() != 2) {
-            throw new GradleException("You must specify SHAs in format: <rev1>...<rev2>")
+        def modificationsReporter = new ModifiedProjectsReporter()
+        if (rootProject.hasProperty("SHAs")) {
+            def range = SHAs.tokenize('...')
+            if (range.size() != 2) {
+                throw new GradleException("You must specify SHAs in format: <rev1>...<rev2>")
+            }
+            modificationsReporter.printModifiedProjectsSHAs(supportRoot, range[0], range[1], rootProject)
         }
-        printModifications(range[0], range[1])
-    }
-}
 
-def printModifications(sha1, sha2) {
-    def modificationsReporter = new ModifiedProjectsReporter()
-    modificationsReporter.printModifiedProjects(ext.supportRootFolder, sha1, sha2, rootProject)
-}
+        if (rootProject.hasProperty("buildIds")) {
+            def range = buildIds.tokenize('...')
+            if (range.size() != 2) {
+                throw new GradleException("You must specify builds in format: <buildId1>..<buildId2")
+            }
+            modificationsReporter.printModifiedProjectsbyBuildIds(supportRoot, range[0].toInteger(),
+                    range[1].toInteger(), rootProject)
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/FetchArtifact.kt b/buildSrc/src/main/kotlin/android/support/FetchArtifact.kt
new file mode 100644
index 0000000..769a1eb
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/FetchArtifact.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.support
+
+import org.gradle.api.GradleException
+import java.io.File
+
+const val FETCH_ARTIFACT = "/google/data/ro/projects/android/fetch_artifact"
+
+/**
+ * fetch artifact from the build server it is similar to run in command line:
+ * /google/data/ro/projects/android/fetch_artifact --bid <buildId> --target <target> path
+ */
+fun fetch(workingDir: File, buildId: Int, target: String, path: String) = ioThread {
+    if (!File(FETCH_ARTIFACT).exists()) {
+        throw GradleException("$FETCH_ARTIFACT doesn't exist")
+    }
+
+    val process = ProcessBuilder(FETCH_ARTIFACT, "--bid", "$buildId", "--target", target, path)
+            .directory(workingDir)
+            .start()
+
+    process.awaitSuccess("Fetch artifact")
+    File(workingDir, path)
+}
diff --git a/buildSrc/src/main/kotlin/android/support/IoUtils.kt b/buildSrc/src/main/kotlin/android/support/IoUtils.kt
new file mode 100644
index 0000000..71b3bc9
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/IoUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.support
+
+import org.gradle.api.GradleException
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+
+const val TIMEOUT_IN_SECS = 20L //secs
+
+val IO_EXECUTOR: ExecutorService = Executors.newFixedThreadPool(5)
+fun <T> ioThread(f: () -> T): Future<T> = IO_EXECUTOR.submit(f)
+
+fun Process.awaitSuccess(processName: String) {
+    val awaitResult = waitFor(TIMEOUT_IN_SECS, TimeUnit.SECONDS)
+    if (!awaitResult) {
+        throw GradleException("Failed to await for $processName")
+    }
+
+    if (exitValue() != 0) {
+        val errorMessage = errorStream.bufferedReader().use { it.readText() }
+        throw GradleException("$processName failed: $errorMessage")
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/ModifiedProjectsReporter.kt b/buildSrc/src/main/kotlin/android/support/ModifiedProjectsReporter.kt
index 1adbd0b..2c1560e 100644
--- a/buildSrc/src/main/kotlin/android/support/ModifiedProjectsReporter.kt
+++ b/buildSrc/src/main/kotlin/android/support/ModifiedProjectsReporter.kt
@@ -16,21 +16,32 @@
 
 package android.support
 
-import org.gradle.api.GradleException
-import java.io.File
-import java.util.concurrent.Executors
-import java.util.concurrent.Future
-import java.util.concurrent.TimeUnit
 import org.gradle.api.Project
+import java.io.File
+import java.util.concurrent.Future
 
+private const val REPO_PROP = "repo.prop"
 
 @Suppress("unused")
 class ModifiedProjectsReporter {
 
-    private val ioExecutor = Executors.newFixedThreadPool(5)
-    private fun <T> ioThread(f: () -> T): Future<T> = ioExecutor.submit(f)
+    private fun fetchRepoProps(rootProject: Project, buildId: Int): Future<File> {
+        val workingDir = File(rootProject.buildDir, "$buildId")
+        if (workingDir.exists()) {
+            workingDir.deleteRecursively()
+        }
+        workingDir.mkdir()
+        return fetch(rootProject.buildDir, buildId, "support_library_app_toolkit", REPO_PROP)
+    }
 
-    fun printModifiedProjects(gitRoot: File,
+    fun printModifiedProjectsbyBuildIds(gitRoot: File, buildIdFirst: Int, buildIdSecond: Int,
+                              rootProject: Project) {
+        val sha1 = frameworksSupportSHA(fetchRepoProps(rootProject, buildIdFirst).get())
+        val sha2 = frameworksSupportSHA(fetchRepoProps(rootProject, buildIdSecond).get())
+        printModifiedProjectsSHAs(gitRoot, sha1, sha2, rootProject)
+    }
+
+    fun printModifiedProjectsSHAs(gitRoot: File,
                               shaFirst: String, shaLast: String, rootProject: Project) {
 
         val modifiedRootProjects = rootProject.subprojects
@@ -78,14 +89,7 @@
         val process = ProcessBuilder("git", "log", "$shaFirst..$shaLast", "--",
                 projectDir.absolutePath).start()
 
-        val awaitResult = process.waitFor(1, TimeUnit.SECONDS)
-        if (!awaitResult) {
-            throw GradleException("Failed to await for git")
-        }
-        if (process.exitValue() != 0) {
-            val errorMessage = process.errorStream.bufferedReader().use { it.readText() }
-            throw GradleException("Git command failed: $errorMessage")
-        }
+        process.awaitSuccess("Git log")
         process.inputStream.bufferedReader().use { !it.readLine().isNullOrEmpty() }
     }
 }
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/RepoPropParser.kt b/buildSrc/src/main/kotlin/android/support/RepoPropParser.kt
new file mode 100644
index 0000000..db45c5e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/RepoPropParser.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.support
+
+import org.gradle.api.GradleException
+import java.io.File
+
+private const val FRAMEWORKS_SUPPORT = "platform/frameworks/support"
+
+/**
+ * parses repo.prop
+ * expected format is:
+ * <project1 path> <revision sha1>
+ *     ....
+ * <projectN path> <revision shaN>
+ */
+private fun parseRepoPropFile(file: File): Map<String, String> {
+    val result = mutableMapOf<String, String>()
+    file.forEachLine { line ->
+        val split = line.split(' ')
+        if (split.size != 2) {
+            throw GradleException("Invalid format for repo.prop, line: $line")
+        }
+        val project = split[0]
+        val sha = split[1]
+        result[project] = sha
+    }
+    return result
+}
+
+fun frameworksSupportSHA(file: File): String {
+    return parseRepoPropFile(file).getOrElse(FRAMEWORKS_SUPPORT, {
+        throw GradleException("$FRAMEWORKS_SUPPORT wasn't found in $file")
+    })
+}
\ No newline at end of file