Snap for 8730993 from a181fe59a34803a1ba7300bd1a32532b6dc3e9fb to mainline-tzdata3-release

Change-Id: Id81849f555e88f3157df0d96f1d03afafa4ea4fb
diff --git a/.gitignore b/.gitignore
index 8f8ed69..14e1cbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,5 @@
 .gradle
 /build/
-/buildSrc/build/
-/curl/build/
-/googletest/build/
-/jsoncpp/build/
-/openssl/build/
 
 # Ignore Gradle GUI config
 gradle-app.setting
@@ -48,11 +43,11 @@
 # When using Gradle or Maven with auto-import, you should exclude module files,
 # since they will be recreated, and may cause churn.  Uncomment if using
 # auto-import.
-.idea/modules.xml
-.idea/*.iml
-.idea/modules
-*.iml
-*.ipr
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
 
 # CMake
 cmake-build-*/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 93ae7ac..e3439a9 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -10,7 +10,6 @@
     </JetCodeStyleSettings>
     <codeStyleSettings language="kotlin">
       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
-      <option name="KEEP_LINE_BREAKS" value="false" />
       <option name="WRAP_ON_TYPING" value="0" />
     </codeStyleSettings>
   </code_scheme>
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index 443b5d2..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="1.6" />
-  </component>
-</project>
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index 6fde95a..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="RemoteRepositoriesConfiguration">
-    <remote-repository>
-      <option name="id" value="central" />
-      <option name="name" value="Maven Central repository" />
-      <option name="url" value="https://repo1.maven.org/maven2" />
-    </remote-repository>
-    <remote-repository>
-      <option name="id" value="jboss.community" />
-      <option name="name" value="JBoss Community repository" />
-      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
-    </remote-repository>
-    <remote-repository>
-      <option name="id" value="MavenRepo" />
-      <option name="name" value="MavenRepo" />
-      <option name="url" value="https://repo.maven.apache.org/maven2/" />
-    </remote-repository>
-    <remote-repository>
-      <option name="id" value="maven" />
-      <option name="name" value="maven" />
-      <option name="url" value="https://dl.bintray.com/s1m0nw1/KtsRunner" />
-    </remote-repository>
-    <remote-repository>
-      <option name="id" value="BintrayJCenter" />
-      <option name="name" value="BintrayJCenter" />
-      <option name="url" value="https://jcenter.bintray.com/" />
-    </remote-repository>
-    <remote-repository>
-      <option name="id" value="Google" />
-      <option name="name" value="Google" />
-      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
-    </remote-repository>
-  </component>
-</project>
\ No newline at end of file
diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml
index e56386c..a6fe551 100644
--- a/.idea/kotlinScripting.xml
+++ b/.idea/kotlinScripting.xml
@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="KotlinScriptingSettings">
-    <scriptDefinition className="org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate" definitionName="KotlinBuildScript">
-      <order>2147483647</order>
-      <autoReloadConfigurations>true</autoReloadConfigurations>
-    </scriptDefinition>
+    <option name="isAutoReloadEnabled" value="true" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 15b559a..bc8d0a3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/out" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..347b2d7
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/ndkports.iml" filepath="$PROJECT_DIR$/.idea/ndkports.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/ndkports.iml b/.idea/ndkports.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/ndkports.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 899b2f1..73193ea 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -27,34 +27,6 @@
           <option name="issueRegexp" value="\bcs/([^\s]+[\w$])" />
           <option name="linkRegexp" value="https://cs.corp.google.com/search/?q=$1" />
         </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\bb/(\d+)(#\w+)?\b" />
-          <option name="linkRegexp" value="https://buganizer.corp.google.com/issues/$1$2" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\b(?:BUG=|FIXED=)(\d+)\b" />
-          <option name="linkRegexp" value="https://buganizer.corp.google.com/issues/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\b(?:cl/|cr/|OCL=|DIFFBASE=|ROLLBACK_OF=)(\d+)\b" />
-          <option name="linkRegexp" value="https://critique.corp.google.com/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\bomg/(\d+)\b" />
-          <option name="linkRegexp" value="https://omg.corp.google.com/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\b(?:go/|goto/)([^,.&lt;&gt;()&quot;\s]+(?:[.,][^,.&lt;&gt;()&quot;\s]+)*)" />
-          <option name="linkRegexp" value="https://goto.google.com/$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="\bcs/([^\s]+[\w$])" />
-          <option name="linkRegexp" value="https://cs.corp.google.com/search/?q=$1" />
-        </IssueNavigationLink>
-        <IssueNavigationLink>
-          <option name="issueRegexp" value="(LINT\.IfChange)|(LINT\.ThenChange)" />
-          <option name="linkRegexp" value="https://goto.google.com/ifthisthenthatlint" />
-        </IssueNavigationLink>
       </list>
     </option>
   </component>
diff --git a/Dockerfile b/Dockerfile
index ac011ef..aba9898 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,20 +1,15 @@
-FROM gcr.io/cloud-builders/javac:8
+FROM gcr.io/cloud-builders/gradle:5.6.2-jdk-8
 
-RUN apt-get update && apt-get install -y \
-    cmake \
-    curl \
-    ninja-build \
-    python3-pip
+RUN apt-get update
+RUN apt-get install -y curl
+RUN apt-get install -y ninja-build
+RUN apt-get install -y python3-pip
 RUN pip3 install meson
 RUN curl -o ndk.zip \
-    https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip
+    https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip
 RUN unzip ndk.zip
-RUN mv android-ndk-r21e /ndk
-RUN curl -L -o platform-tools.zip \
-    https://dl.google.com/android/repository/platform-tools-latest-linux.zip
-RUN unzip platform-tools.zip platform-tools/adb
-RUN mv platform-tools/adb /usr/bin/adb
+RUN mv android-ndk-r20b /ndk
 
 WORKDIR /src
 ENTRYPOINT ["./gradlew"]
-CMD ["--stacktrace", "-PndkPath=/ndk", "release"]
+CMD ["-PndkPath=/ndk", "release"]
diff --git a/build.gradle.kts b/build.gradle.kts
index f217598..a00d626 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,47 +1,113 @@
-buildscript {
-    val snapshotSuffix = if (hasProperty("release")) {
-        // We're still tagging releases as betas until we have more thorough
-        // test automation.
-        "-beta-1"
-    } else {
-        "-SNAPSHOT"
-    }
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
-    extra.apply {
-        set("snapshotSuffix", snapshotSuffix)
-    }
+plugins {
+    kotlin("jvm") version "1.3.72"
+    application
 }
 
 group = "com.android"
-version = "1.0.0${extra.get("snapshotSuffix")}"
-
-plugins {
-    distribution
-}
+version = "1.0.0-SNAPSHOT"
 
 repositories {
     mavenCentral()
     jcenter()
     google()
+    maven(url = "https://dl.bintray.com/s1m0nw1/KtsRunner")
+}
+
+dependencies {
+    implementation(kotlin("stdlib", "1.3.72"))
+    implementation(kotlin("reflect", "1.3.72"))
+
+    implementation("com.google.prefab:api:1.0.0")
+
+    implementation("com.github.ajalt:clikt:2.2.0")
+    implementation("de.swirtz:ktsRunner:0.0.7")
+    implementation("org.apache.maven:maven-core:3.6.2")
+    implementation("org.redundent:kotlin-xml-builder:1.5.3")
+
+    testImplementation("org.jetbrains.kotlin:kotlin-test")
+    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
+    testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0-M1")
+    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.0-M1")
+}
+
+application {
+    // Define the main class for the application.
+    mainClassName = "com.android.ndkports.CliKt"
+}
+
+tasks.withType<KotlinCompile> {
+    kotlinOptions.jvmTarget = "1.8"
+    kotlinOptions.freeCompilerArgs += listOf(
+        "-progressive",
+        "-Xuse-experimental=kotlinx.serialization.ImplicitReflectionSerializer"
+    )
+}
+
+val portsBuildDir = buildDir.resolve("ports")
+
+val allPorts = listOf("openssl", "curl", "jsoncpp")
+
+// Can be specified in ~/.gradle/gradle.properties:
+//
+//     ndkPath=/path/to/ndk
+//
+// Or on the command line:
+//
+//     ./gradlew -PndkPath=/path/to/ndk run
+val ndkPath: String by project
+tasks.named<JavaExec>("run") {
+    // Order matters since we don't do any dependency sorting, so we can't just
+    // use the directory list.
+    args = listOf("--ndk", ndkPath, "-o", portsBuildDir.toString()) + allPorts
+}
+
+for (port in allPorts) {
+    distributions {
+        create(port) {
+            contents {
+                includeEmptyDirs = false
+                from(portsBuildDir.resolve(port)) {
+                    include("**/*.aar")
+                    include("**/*.pom")
+                }
+            }
+        }
+    }
+
+    tasks.named("${port}DistTar") {
+        dependsOn(":run")
+    }
+
+    tasks.named("${port}DistZip") {
+        dependsOn(":run")
+    }
 }
 
 distributions {
-    main {
+    create("all") {
         contents {
-            from("${rootProject.buildDir}/repository")
-            include("**/*.aar")
-            include("**/*.pom")
+            includeEmptyDirs = false
+            from(portsBuildDir) {
+                include("**/*.aar")
+                include("**/*.pom")
+            }
         }
     }
 }
 
-tasks {
-    distZip {
-        dependsOn(project.getTasksByName("publish", true))
-    }
+tasks.named("allDistTar") {
+    dependsOn(":run")
+}
+
+tasks.named("allDistZip") {
+    dependsOn(":run")
 }
 
 tasks.register("release") {
-    dependsOn(project.getTasksByName("test", true))
-    dependsOn(":distZip")
+    dependsOn(":allDistZip")
+    for (port in allPorts) {
+        dependsOn(":${port}DistZip")
+    }
 }
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
deleted file mode 100644
index 4de21c7..0000000
--- a/buildSrc/build.gradle.kts
+++ /dev/null
@@ -1,62 +0,0 @@
-val kotlinVersion = "1.4.20"
-
-plugins {
-    id("org.jetbrains.kotlin.jvm") version "1.4.20"
-    id("java-gradle-plugin")
-    id("maven-publish")
-}
-
-group = "com.android.ndkports"
-version = "1.0.0-SNAPSHOT"
-
-repositories {
-    mavenCentral()
-    jcenter()
-    google()
-}
-
-dependencies {
-    implementation(kotlin("stdlib", kotlinVersion))
-    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1")
-
-    implementation("com.google.prefab:api:1.1.2")
-    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
-    implementation("org.redundent:kotlin-xml-builder:1.6.1")
-
-    testImplementation(kotlin("test", kotlinVersion))
-    testImplementation(kotlin("test-junit", kotlinVersion))
-    testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
-    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0")
-}
-
-tasks {
-    compileJava {
-        @Suppress("UnstableApiUsage")
-        options.release.set(8)
-    }
-
-    compileKotlin {
-        kotlinOptions.jvmTarget = "1.8"
-    }
-
-    compileTestKotlin {
-        kotlinOptions.jvmTarget = "1.8"
-    }
-}
-
-gradlePlugin {
-    plugins {
-        create("ndkports") {
-            id = "com.android.ndkports.NdkPorts"
-            implementationClass = "com.android.ndkports.NdkPortsPlugin"
-        }
-    }
-}
-
-publishing {
-    repositories {
-        maven {
-            url = uri("${rootProject.buildDir}/repository")
-        }
-    }
-}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
deleted file mode 100644
index e69de29..0000000
--- a/buildSrc/settings.gradle.kts
+++ /dev/null
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/Abi.kt b/buildSrc/src/main/kotlin/com/android/ndkports/Abi.kt
deleted file mode 100644
index 155c59c..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/Abi.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.ndkports
-
-import java.util.Collections.max
-
-enum class Abi(
-    val archName: String,
-    val abiName: String,
-    val triple: String,
-    val minSupportedVersion: Int
-) {
-    Arm("arm", "armeabi-v7a", "arm-linux-androideabi", 16),
-    Arm64("arm64", "arm64-v8a", "aarch64-linux-android", 21),
-    X86("x86", "x86", "i686-linux-android", 16),
-    X86_64("x86_64", "x86_64", "x86_64-linux-android", 21);
-
-    fun adjustMinSdkVersion(minSdkVersion: Int) =
-        max(listOf(minSdkVersion, minSupportedVersion))
-
-    companion object {
-        fun fromAbiName(name: String) = values().find { it.abiName == name }
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/AdHocPortTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/AdHocPortTask.kt
deleted file mode 100644
index 4c9fcbc..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/AdHocPortTask.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.ndkports
-
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import java.io.File
-
-open class RunBuilder {
-    val cmd = mutableListOf<String>()
-    fun arg(arg: String) = cmd.add(arg)
-    fun args(vararg args: String) = cmd.addAll(args)
-
-    val env = mutableMapOf<String, String>()
-    fun env(key: String, value: String) = env.set(key, value)
-}
-
-class AdHocBuilder(
-    val sourceDirectory: File,
-    val buildDirectory: File,
-    val installDirectory: File,
-    val toolchain: Toolchain,
-    val sysroot: File,
-    val ncpus: Int,
-) {
-    val runs = mutableListOf<RunBuilder>()
-    fun run(block: RunBuilder.() -> Unit) {
-        runs.add(RunBuilder().apply { block() })
-    }
-}
-
-abstract class AdHocPortTask : PortTask() {
-    @get:Input
-    abstract val builder: Property<AdHocBuilder.() -> Unit>
-
-    fun builder(block: AdHocBuilder.() -> Unit) = builder.set(block)
-
-    override fun buildForAbi(
-        toolchain: Toolchain,
-        workingDirectory: File,
-        buildDirectory: File,
-        installDirectory: File
-    ) {
-        buildDirectory.mkdirs()
-
-        val builderBlock = builder.get()
-        val builder = AdHocBuilder(
-            sourceDirectory.get().asFile,
-            buildDirectory,
-            installDirectory,
-            toolchain,
-            prefabGenerated.get().asFile,
-            ncpus,
-        )
-        builder.builderBlock()
-
-        for (run in builder.runs) {
-            executeSubprocess(
-                run.cmd, buildDirectory, additionalEnvironment = run.env
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/AutoconfPortTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/AutoconfPortTask.kt
deleted file mode 100644
index e288e33..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/AutoconfPortTask.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.ndkports
-
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import java.io.File
-
-class AutoconfBuilder(val toolchain: Toolchain, val sysroot: File) :
-    RunBuilder()
-
-abstract class AutoconfPortTask : PortTask() {
-    @get:Input
-    abstract val autoconf: Property<AutoconfBuilder.() -> Unit>
-
-    fun autoconf(block: AutoconfBuilder.() -> Unit) = autoconf.set(block)
-
-    override fun buildForAbi(
-        toolchain: Toolchain,
-        workingDirectory: File,
-        buildDirectory: File,
-        installDirectory: File
-    ) {
-        buildDirectory.mkdirs()
-
-        val autoconfBlock = autoconf.get()
-        val builder = AutoconfBuilder(
-            toolchain,
-            prefabGenerated.get().asFile.resolve(toolchain.abi.triple)
-        )
-        builder.autoconfBlock()
-
-        executeSubprocess(listOf(
-            "${sourceDirectory.get().asFile.absolutePath}/configure",
-            "--host=${toolchain.binutilsTriple}",
-            "--prefix=${installDirectory.absolutePath}"
-        ) + builder.cmd,
-            buildDirectory,
-            additionalEnvironment = mutableMapOf(
-                "AR" to toolchain.ar.absolutePath,
-                "CC" to toolchain.clang.absolutePath,
-                "CXX" to toolchain.clangxx.absolutePath,
-                "RANLIB" to toolchain.ranlib.absolutePath,
-                "STRIP" to toolchain.strip.absolutePath,
-                "PATH" to "${toolchain.binDir}:${System.getenv("PATH")}"
-            ).apply { putAll(builder.env) })
-
-        executeSubprocess(listOf("make", "-j$ncpus"), buildDirectory)
-
-        executeSubprocess(
-            listOf("make", "-j$ncpus", "install"), buildDirectory
-        )
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/CMakePortTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/CMakePortTask.kt
deleted file mode 100644
index dba2017..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/CMakePortTask.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.android.ndkports
-
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import java.io.File
-
-class CMakeBuilder(val toolchain: Toolchain, val sysroot: File) :
-    RunBuilder()
-
-abstract class CMakePortTask : PortTask() {
-    @get:Input
-    abstract val cmake: Property<CMakeBuilder.() -> Unit>
-
-    fun cmake(block: CMakeBuilder.() -> Unit) = cmake.set(block)
-
-    override fun buildForAbi(
-        toolchain: Toolchain,
-        workingDirectory: File,
-        buildDirectory: File,
-        installDirectory: File
-    ) {
-        configure(toolchain, buildDirectory, installDirectory)
-        build(buildDirectory)
-        install(buildDirectory)
-    }
-
-    private fun configure(
-        toolchain: Toolchain, buildDirectory: File, installDirectory: File
-    ) {
-        val cmakeBlock = cmake.get()
-        val builder = CMakeBuilder(
-            toolchain,
-            prefabGenerated.get().asFile.resolve(toolchain.abi.triple)
-        )
-        builder.cmakeBlock()
-
-        val toolchainFile =
-            toolchain.ndk.path.resolve("build/cmake/android.toolchain.cmake")
-
-        buildDirectory.mkdirs()
-        executeSubprocess(
-            listOf(
-                "cmake",
-                "-DCMAKE_TOOLCHAIN_FILE=${toolchainFile.absolutePath}",
-                "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
-                "-DCMAKE_INSTALL_PREFIX=${installDirectory.absolutePath}",
-                "-DANDROID_ABI=${toolchain.abi.abiName}",
-                "-DANDROID_API_LEVEL=${toolchain.api}",
-                "-GNinja",
-                sourceDirectory.get().asFile.absolutePath,
-            ) + builder.cmd, buildDirectory, builder.env
-        )
-    }
-
-    private fun build(buildDirectory: File) =
-        executeSubprocess(listOf("ninja", "-v"), buildDirectory)
-
-    private fun install(buildDirectory: File) =
-        executeSubprocess(listOf("ninja", "-v", "install"), buildDirectory)
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/Devices.kt b/buildSrc/src/main/kotlin/com/android/ndkports/Devices.kt
deleted file mode 100644
index 3ed1d9a..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/Devices.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.android.ndkports
-
-import java.io.File
-
-data class AdbException(val args: Iterable<String>, val output: String) :
-    RuntimeException("${formatCmd(args)}:\n$output") {
-    val cmd: String by lazy { formatCmd(args) }
-
-    companion object {
-        fun formatCmd(args: Iterable<String>) = args.joinToString(" ")
-    }
-}
-
-private fun adb(args: Iterable<String>, serial: String? = null): String {
-    val adbCmd = if (serial == null) {
-        listOf("adb")
-    } else {
-        listOf("adb", "-s", serial)
-    }
-    val result = ProcessBuilder(adbCmd + args).redirectErrorStream(true).start()
-    val output = result.inputStream.bufferedReader().use { it.readText() }
-    if (result.waitFor() != 0) {
-        throw AdbException(args, output)
-    }
-    return output
-}
-
-data class Device(val serial: String) {
-    private val abis: List<Abi> by lazy {
-        val abiProps = listOf(
-            "ro.product.cpu.abi",
-            "ro.product.cpu.abi2",
-            "ro.product.cpu.abilist",
-        )
-        val abiSet = mutableSetOf<Abi>()
-        for (abiProp in abiProps) {
-            for (abiName in getProp(abiProp).trim().split(",")) {
-                Abi.fromAbiName(abiName)?.let { abiSet.add(it) }
-            }
-        }
-        abiSet.toList().sortedBy { it.abiName }
-    }
-
-    private val version: Int by lazy {
-        getProp("ro.build.version.sdk").trim().toInt()
-    }
-
-    fun compatibleWith(abi: Abi, minSdkVersion: Int) =
-        abi in abis && minSdkVersion <= version
-
-    fun push(src: File, dest: File) =
-        run(listOf("push", src.toString(), dest.toString()))
-
-    fun shell(cmd: Iterable<String>) = run(listOf("shell") + cmd)
-
-    private fun getProp(name: String): String = shell(listOf("getprop", name))
-
-    private fun run(args: Iterable<String>): String = adb(args, serial)
-}
-
-class DeviceFleet {
-    private fun lineHasUsableDevice(line: String): Boolean {
-        if (line.isBlank()) {
-            return false
-        }
-        if (line == "List of devices attached") {
-            return false
-        }
-        if (line.contains("offline")) {
-            return false
-        }
-        if (line.contains("unauthorized")) {
-            return false
-        }
-        if (line.startsWith("* daemon")) {
-            return false
-        }
-        return true
-    }
-
-    private val devices: List<Device> by lazy {
-        adb(listOf("devices")).lines().filter { lineHasUsableDevice(it) }.map {
-            Device(it.split("\\s".toRegex()).first())
-        }
-    }
-
-    fun findDeviceFor(abi: Abi, minSdkVersion: Int): Device? =
-        devices.find { it.compatibleWith(abi, minSdkVersion) }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/NdkPortsPlugin.kt b/buildSrc/src/main/kotlin/com/android/ndkports/NdkPortsPlugin.kt
deleted file mode 100644
index ff629c0..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/NdkPortsPlugin.kt
+++ /dev/null
@@ -1,179 +0,0 @@
-package com.android.ndkports
-
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.attributes.Attribute
-import org.gradle.api.component.SoftwareComponentFactory
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.file.RegularFileProperty
-import org.gradle.api.model.ObjectFactory
-import org.gradle.api.plugins.BasePlugin
-import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.bundling.Zip
-import javax.inject.Inject
-
-abstract class NdkPortsExtension {
-    abstract val source: RegularFileProperty
-
-    abstract val ndkPath: DirectoryProperty
-
-    abstract val minSdkVersion: Property<Int>
-}
-
-class NdkPortsPluginImpl(
-    private val project: Project,
-    private val softwareComponentFactory: SoftwareComponentFactory,
-    objects: ObjectFactory,
-) {
-    private val topBuildDir = project.buildDir.resolve("port")
-
-    private val extension =
-        project.extensions.create("ndkPorts", NdkPortsExtension::class.java)
-
-    private var portTaskAdded: Boolean = false
-    private val portTask = objects.property(PortTask::class.java)
-    private lateinit var prefabTask: Provider<PrefabTask>
-    private lateinit var extractTask: Provider<SourceExtractTask>
-    private lateinit var packageTask: Provider<PackageBuilderTask>
-    private lateinit var aarTask: Provider<Zip>
-
-    private lateinit var implementation: Configuration
-    private lateinit var exportedAars: Configuration
-    private lateinit var consumedAars: Configuration
-
-    private val artifactType = Attribute.of("artifactType", String::class.java)
-
-    private fun createConfigurations() {
-        implementation = project.configurations.create("implementation") {
-            it.isCanBeResolved = false
-            it.isCanBeConsumed = false
-        }
-
-        exportedAars = project.configurations.create("exportedAars") {
-            it.isCanBeResolved = false
-            it.isCanBeConsumed = true
-            it.extendsFrom(implementation)
-            it.attributes { attributes ->
-                with(attributes) {
-                    attribute(artifactType, "aar")
-                }
-            }
-        }
-
-        consumedAars = project.configurations.create("consumedAars") {
-            it.isCanBeResolved = true
-            it.isCanBeConsumed = false
-            it.extendsFrom(implementation)
-            it.attributes { attributes ->
-                with(attributes) {
-                    attribute(artifactType, "aar")
-                }
-            }
-        }
-    }
-
-    private fun createTasks() {
-        prefabTask = project.tasks.register("prefab", PrefabTask::class.java) {
-            with(it) {
-                aars = consumedAars.incoming.artifacts.artifactFiles
-                outputDirectory.set(topBuildDir.resolve("dependencies"))
-                ndkPath.set(extension.ndkPath)
-                minSdkVersion.set(extension.minSdkVersion)
-            }
-        }
-
-        extractTask = project.tasks.register(
-            "extractSrc", SourceExtractTask::class.java
-        ) {
-            with(it) {
-                source.set(extension.source)
-                outDir.set(topBuildDir.resolve("src"))
-            }
-        }
-
-        packageTask = project.tasks.register(
-            "prefabPackage", PackageBuilderTask::class.java
-        ) {
-            if (!portTask.isPresent) {
-                throw InvalidUserDataException(
-                    "The ndkports plugin was applied but no port task was " +
-                            "registered. A task deriving from NdkPortsTask " +
-                            "must be registered."
-                )
-            }
-            with(it) {
-                sourceDirectory.set(extractTask.get().outDir)
-                outDir.set(topBuildDir)
-                ndkPath.set(extension.ndkPath)
-                installDirectory.set(portTask.get().installDir)
-                minSdkVersion.set(extension.minSdkVersion)
-            }
-        }
-
-        aarTask = project.tasks.register("packageAar", Zip::class.java) {
-            it.from(packageTask.get().intermediatesDirectory)
-            it.archiveExtension.set("aar")
-            it.dependsOn(packageTask)
-        }
-
-        project.artifacts.add(exportedAars.name, aarTask)
-
-        val portTasks = project.tasks.withType(PortTask::class.java)
-        portTasks.whenTaskAdded { portTask ->
-            if (portTaskAdded) {
-                throw InvalidUserDataException(
-                    "Cannot define multiple port tasks for a single module"
-                )
-            }
-            portTaskAdded = true
-            this.portTask.set(portTask)
-
-            with (portTask) {
-                sourceDirectory.set(extractTask.get().outDir)
-                ndkPath.set(extension.ndkPath)
-                buildDir.set(topBuildDir)
-                minSdkVersion.set(extension.minSdkVersion)
-                prefabGenerated.set(prefabTask.get().generatedDirectory)
-            }
-        }
-
-        val testTasks =
-            project.tasks.withType(AndroidExecutableTestTask::class.java)
-        testTasks.whenTaskAdded { testTask ->
-            with (testTask) {
-                dependsOn(aarTask)
-                minSdkVersion.set(extension.minSdkVersion)
-                ndkPath.set(extension.ndkPath)
-            }
-            project.tasks.getByName("check").dependsOn(testTask)
-        }
-    }
-
-    private fun createComponents() {
-        val adhocComponent = softwareComponentFactory.adhoc("prefab")
-        project.components.add(adhocComponent)
-        adhocComponent.addVariantsFromConfiguration(exportedAars) {
-            it.mapToMavenScope("runtime")
-        }
-    }
-
-    fun apply() {
-        project.pluginManager.apply(BasePlugin::class.java)
-        createConfigurations()
-        createTasks()
-        createComponents()
-    }
-}
-
-@Suppress("UnstableApiUsage", "Unused")
-class NdkPortsPlugin @Inject constructor(
-    private val objects: ObjectFactory,
-    private val softwareComponentFactory: SoftwareComponentFactory,
-) : Plugin<Project> {
-    override fun apply(project: Project) {
-        NdkPortsPluginImpl(project, softwareComponentFactory, objects).apply()
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/PackageBuilderTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/PackageBuilderTask.kt
deleted file mode 100644
index a7c408d..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/PackageBuilderTask.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-package com.android.ndkports
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.NamedDomainObjectContainer
-import org.gradle.api.Project
-import org.gradle.api.file.Directory
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.model.ObjectFactory
-import org.gradle.api.provider.ListProperty
-import org.gradle.api.provider.MapProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.Nested
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import javax.inject.Inject
-
-abstract class ModuleProperty @Inject constructor(
-    objectFactory: ObjectFactory,
-    @get:Input val name: String,
-) {
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    val static: Property<Boolean> =
-        objectFactory.property(Boolean::class.java).convention(false)
-
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    val headerOnly: Property<Boolean> =
-        objectFactory.property(Boolean::class.java).convention(false)
-
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    val includesPerAbi: Property<Boolean> =
-        objectFactory.property(Boolean::class.java).convention(false)
-
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    val dependencies: ListProperty<String> =
-        objectFactory.listProperty(String::class.java).convention(emptyList())
-}
-
-abstract class PackageBuilderTask @Inject constructor(
-    objectFactory: ObjectFactory,
-) : DefaultTask() {
-    /**
-     * The name of the port. Will be used as the package name in prefab.json.
-     */
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    val packageName: Property<String> =
-        objectFactory.property(String::class.java).convention(project.name)
-
-    /**
-     * The version to encode in the prefab.json.
-     *
-     * This version must be compatible with CMake's `find_package` for
-     * config-style packages. This means that it must be one to four decimal
-     * separated integers. No other format is allowed.
-     *
-     * If not set, the default is [Project.getVersion] as interpreted by
-     * [CMakeCompatibleVersion.parse].
-     *
-     * For example, OpenSSL 1.1.1g will set this value to
-     * `CMakeCompatibleVersion(1, 1, 1, 7)`.
-     */
-    @get:Input
-    abstract val version: Property<CMakeCompatibleVersion>
-
-    @get:Input
-    abstract val minSdkVersion: Property<Int>
-
-    @get:Nested
-    abstract val modules: NamedDomainObjectContainer<ModuleProperty>
-
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    val licensePath: Property<String> =
-        objectFactory.property(String::class.java).convention("LICENSE")
-
-    @Suppress("UnstableApiUsage")
-    @get:Input
-    abstract val dependencies: MapProperty<String, String>
-
-    @get:InputDirectory
-    abstract val sourceDirectory: DirectoryProperty
-
-    @get:InputDirectory
-    abstract val installDirectory: DirectoryProperty
-
-    @get:Internal
-    abstract val outDir: DirectoryProperty
-
-    @get:OutputDirectory
-    val intermediatesDirectory: Provider<Directory>
-        get() = outDir.dir("aar")
-
-    @get:InputDirectory
-    abstract val ndkPath: DirectoryProperty
-
-    private val ndk: Ndk
-        get() = Ndk(ndkPath.asFile.get())
-
-    @TaskAction
-    fun run() {
-        val modules = modules.asMap.values.map {
-            ModuleDescription(
-                it.name,
-                it.static.get(),
-                it.headerOnly.get(),
-                it.includesPerAbi.get(),
-                it.dependencies.get()
-            )
-        }
-        PrefabPackageBuilder(
-            PackageData(
-                packageName.get(),
-                project.version as String,
-                version.get(),
-                minSdkVersion.get(),
-                licensePath.get(),
-                modules,
-                dependencies.get(),
-            ),
-            intermediatesDirectory.get().asFile,
-            installDirectory.get().asFile,
-            sourceDirectory.get().asFile,
-            ndk,
-        ).build()
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/PortTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/PortTask.kt
deleted file mode 100644
index 223afb0..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/PortTask.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.android.ndkports
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.Directory
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.Internal
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-
-@Suppress("UnstableApiUsage")
-abstract class PortTask : DefaultTask() {
-
-    @get:InputDirectory
-    abstract val sourceDirectory: DirectoryProperty
-
-    @get:OutputDirectory
-    abstract val buildDir: DirectoryProperty
-
-    @get:OutputDirectory
-    val installDir: Provider<Directory>
-        get() = buildDir.dir("install")
-
-    @get:InputDirectory
-    abstract val prefabGenerated: DirectoryProperty
-
-    @get:Input
-    abstract val minSdkVersion: Property<Int>
-
-    @get:InputDirectory
-    abstract val ndkPath: DirectoryProperty
-
-    private val ndk: Ndk
-        get() = Ndk(ndkPath.asFile.get())
-
-    /**
-     * The number of CPUs available for building.
-     *
-     * May be passed to the build system if required.
-     */
-    @Internal
-    protected val ncpus = Runtime.getRuntime().availableProcessors()
-
-    protected fun executeSubprocess(
-        args: List<String>,
-        workingDirectory: File,
-        additionalEnvironment: Map<String, String>? = null
-    ) {
-        val pb = ProcessBuilder(args).redirectErrorStream(true)
-            .directory(workingDirectory)
-
-        if (additionalEnvironment != null) {
-            pb.environment().putAll(additionalEnvironment)
-        }
-
-        val result = pb.start()
-        val output = result.inputStream.bufferedReader().use { it.readText() }
-        if (result.waitFor() != 0) {
-            throw RuntimeException("Subprocess failed with:\n$output")
-        }
-    }
-
-    @Suppress("MemberVisibilityCanBePrivate")
-    fun buildDirectoryFor(abi: Abi): File =
-        buildDir.asFile.get().resolve("build/$abi")
-
-    @Suppress("MemberVisibilityCanBePrivate")
-    fun installDirectoryFor(abi: Abi): File =
-        installDir.get().asFile.resolve("$abi")
-
-    @TaskAction
-    fun run() {
-        for (abi in Abi.values()) {
-            val api = abi.adjustMinSdkVersion(minSdkVersion.get())
-            buildForAbi(
-                Toolchain(ndk, abi, api),
-                buildDir.asFile.get(),
-                buildDirectory = buildDirectoryFor(abi),
-                installDirectory = installDirectoryFor(abi),
-            )
-        }
-    }
-
-    abstract fun buildForAbi(
-        toolchain: Toolchain,
-        workingDirectory: File,
-        buildDirectory: File,
-        installDirectory: File
-    )
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/PrefabPackageBuilder.kt b/buildSrc/src/main/kotlin/com/android/ndkports/PrefabPackageBuilder.kt
deleted file mode 100644
index 27be792..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/PrefabPackageBuilder.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2019 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 com.android.ndkports
-
-import com.google.prefab.api.AndroidAbiMetadata
-import com.google.prefab.api.ModuleMetadataV1
-import com.google.prefab.api.PackageMetadataV1
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-import org.redundent.kotlin.xml.xml
-import java.io.File
-import java.io.Serializable
-
-data class PackageData(
-    val name: String,
-    val mavenVersion: String,
-    val prefabVersion: CMakeCompatibleVersion,
-    val minSdkVersion: Int,
-    val licensePath: String,
-    val modules: List<ModuleDescription>,
-    val dependencies: Map<String, String>,
-)
-
-/**
- * A module exported by the package.
- *
- * As currently implemented by ndkports, one module is exactly one library.
- * Prefab supports header-only libraries, but ndkports does not support these
- * yet.
- *
- * Static libraries are not currently supported by ndkports.
- *
- * @property[name] The name of the module. Note that currently the name of the
- * installed library file must be exactly `lib$name.so`.
- * @property[includesPerAbi] Set to true if a different set of headers should be
- * exposed per-ABI. Not currently implemented.
- * @property[dependencies] A list of other modules required by this module, in
- * the format described by https://google.github.io/prefab/.
- */
-data class ModuleDescription(
-    val name: String,
-    val static: Boolean,
-    val headerOnly: Boolean,
-    val includesPerAbi: Boolean,
-    val dependencies: List<String>,
-) : Serializable
-
-class PrefabPackageBuilder(
-    private val packageData: PackageData,
-    private val packageDirectory: File,
-    private val directory: File,
-    private val sourceDirectory: File,
-    private val ndk: Ndk,
-) {
-    private val prefabDirectory = packageDirectory.resolve("prefab")
-    private val modulesDirectory = prefabDirectory.resolve("modules")
-
-    // TODO: Get from gradle.
-    private val packageName = "com.android.ndk.thirdparty.${packageData.name}"
-
-    private fun preparePackageDirectory() {
-        if (packageDirectory.exists()) {
-            packageDirectory.deleteRecursively()
-        }
-        modulesDirectory.mkdirs()
-    }
-
-    private fun makePackageMetadata() {
-        prefabDirectory.resolve("prefab.json").writeText(
-            Json.encodeToString(
-                PackageMetadataV1(
-                    packageData.name,
-                    schemaVersion = 1,
-                    dependencies = packageData.dependencies.keys.toList(),
-                    version = packageData.prefabVersion.toString()
-                )
-            )
-        )
-    }
-
-    private fun makeModuleMetadata(module: ModuleDescription, moduleDirectory: File) {
-        moduleDirectory.resolve("module.json").writeText(
-            Json.encodeToString(
-                ModuleMetadataV1(
-                    exportLibraries = module.dependencies
-                )
-            )
-        )
-    }
-
-    private fun installLibForAbi(module: ModuleDescription, abi: Abi, libsDir: File) {
-        val extension = if (module.static) "a" else "so"
-        val libName = "lib${module.name}.${extension}"
-        val installDirectory = libsDir.resolve("android.${abi.abiName}").apply {
-            mkdirs()
-        }
-
-        directory.resolve("$abi/lib/$libName")
-            .copyTo(installDirectory.resolve(libName))
-
-        installDirectory.resolve("abi.json").writeText(
-            Json.encodeToString(
-                AndroidAbiMetadata(
-                    abi = abi.abiName,
-                    api = abi.adjustMinSdkVersion(packageData.minSdkVersion),
-                    ndk = ndk.version.major,
-                    stl = "c++_shared"
-                )
-            )
-        )
-    }
-
-    private fun installLicense() {
-        val src = sourceDirectory.resolve(packageData.licensePath)
-        val dest = packageDirectory.resolve("META-INF")
-            .resolve(File(packageData.licensePath).name)
-        src.copyTo(dest)
-    }
-
-    private fun createAndroidManifest() {
-        packageDirectory.resolve("AndroidManifest.xml")
-            .writeText(xml("manifest") {
-                attributes(
-                    "xmlns:android" to "http://schemas.android.com/apk/res/android",
-                    "package" to packageName,
-                    "android:versionCode" to 1,
-                    "android:versionName" to "1.0"
-                )
-
-                "uses-sdk" {
-                    attributes(
-                        "android:minSdkVersion" to packageData.minSdkVersion,
-                        "android:targetSdkVersion" to 29
-                    )
-                }
-            }.toString())
-    }
-
-    fun build() {
-        preparePackageDirectory()
-        makePackageMetadata()
-        for (module in packageData.modules) {
-            val moduleDirectory = modulesDirectory.resolve(module.name).apply {
-                mkdirs()
-            }
-
-            makeModuleMetadata(module, moduleDirectory)
-
-            if (module.includesPerAbi) {
-                TODO()
-            } else {
-                // TODO: Check that headers are actually identical across ABIs.
-                directory.resolve("${Abi.Arm}/include")
-                    .copyRecursively(moduleDirectory.resolve("include"))
-            }
-
-            if (!module.headerOnly) {
-                val libsDir = moduleDirectory.resolve("libs").apply { mkdirs() }
-                for (abi in Abi.values()) {
-                    installLibForAbi(module, abi, libsDir)
-                }
-            }
-        }
-
-        installLicense()
-
-        createAndroidManifest()
-    }
-}
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/PrefabSysrootPlugin.kt b/buildSrc/src/main/kotlin/com/android/ndkports/PrefabSysrootPlugin.kt
deleted file mode 100644
index 2828764..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/PrefabSysrootPlugin.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.android.ndkports
-
-import com.google.prefab.api.BuildSystemInterface
-import com.google.prefab.api.Module
-import com.google.prefab.api.Package
-import com.google.prefab.api.PlatformDataInterface
-import java.io.File
-
-class PrefabSysrootPlugin(
-    override val outputDirectory: File, override val packages: List<Package>
-) : BuildSystemInterface {
-
-    override fun generate(requirements: Collection<PlatformDataInterface>) {
-        prepareOutputDirectory(outputDirectory)
-
-        for (pkg in packages) {
-            for (module in pkg.modules) {
-                for (requirement in requirements) {
-                    installModule(module, requirement)
-                }
-            }
-        }
-    }
-
-    private fun installModule(
-        module: Module, requirement: PlatformDataInterface
-    ) {
-        val installDir = outputDirectory.resolve(requirement.targetTriple)
-        val includeDir = installDir.resolve("include")
-
-        if (module.isHeaderOnly) {
-            installHeaders(module.includePath.toFile(), includeDir)
-            return
-        }
-
-        val library = module.getLibraryFor(requirement)
-        installHeaders(module.includePath.toFile(), includeDir)
-        val libDir = installDir.resolve("lib").apply {
-            mkdirs()
-        }
-        library.path.toFile().apply { copyTo(libDir.resolve(name)) }
-    }
-
-    private fun installHeaders(src: File, dest: File) {
-        src.copyRecursively(dest) { file, exception ->
-            if (exception !is FileAlreadyExistsException) {
-                throw exception
-            }
-
-            if (!file.readBytes().contentEquals(exception.file.readBytes())) {
-                val path = file.relativeTo(dest)
-                throw RuntimeException(
-                    "Found duplicate headers with non-equal contents: $path"
-                )
-            }
-
-            OnErrorAction.SKIP
-        }
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/PrefabTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/PrefabTask.kt
deleted file mode 100644
index d58f1fa..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/PrefabTask.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.android.ndkports
-
-import com.google.prefab.api.Android
-import com.google.prefab.api.BuildSystemInterface
-import com.google.prefab.api.Package
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.Directory
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.file.FileCollection
-import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-import java.util.zip.ZipFile
-
-abstract class PrefabTask : DefaultTask() {
-    @InputFiles
-    lateinit var aars: FileCollection
-
-    @get:OutputDirectory
-    abstract val outputDirectory: DirectoryProperty
-
-    @get:OutputDirectory
-    val generatedDirectory: Provider<Directory>
-        get() = outputDirectory.dir("generated")
-
-    @get:Optional
-    @get:Input
-    abstract val generator: Property<Class<out BuildSystemInterface>>
-
-    @get:InputDirectory
-    abstract val ndkPath: DirectoryProperty
-
-    @get:Input
-    abstract val minSdkVersion: Property<Int>
-
-    private val ndk: Ndk
-        get() = Ndk(ndkPath.asFile.get())
-
-    @TaskAction
-    fun run() {
-        if (!generator.isPresent) {
-            // Creating the generated directory even if we have no generator
-            // makes it easier to write tasks that *might* consume prefab
-            // packages.
-            generatedDirectory.get().asFile.mkdirs()
-            return
-        }
-
-        val outDir = outputDirectory.get().asFile
-        val packages = mutableListOf<Package>()
-        for (aar in aars) {
-            val packagePath = outDir.resolve(aar.nameWithoutExtension)
-            extract(aar, packagePath)
-            packages.add(Package(packagePath.toPath().resolve("prefab")))
-        }
-        generateSysroot(packages, minSdkVersion.get(), ndk.version.major)
-    }
-
-    private fun extract(aar: File, extractDir: File) {
-        ZipFile(aar).use { zip ->
-            zip.entries().asSequence().forEach { entry ->
-                zip.getInputStream(entry).use { input ->
-                    val outFile = extractDir.resolve(entry.name)
-                    if (entry.isDirectory) {
-                        outFile.mkdirs()
-                    } else {
-                        outFile.outputStream().use { output ->
-                            input.copyTo(output)
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private fun generateSysroot(
-        packages: List<Package>, osVersion: Int, ndkVersion: Int
-    ) {
-        val generatorType = generator.get()
-        val constructor =
-            generatorType.getConstructor(File::class.java, List::class.java)
-        val buildSystemIntegration =
-            constructor.newInstance(generatedDirectory.get().asFile, packages)
-
-        buildSystemIntegration.generate(Android.Abi.values().map {
-            Android(it, osVersion, Android.Stl.CxxShared, ndkVersion)
-        })
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/SourceExtractTask.kt b/buildSrc/src/main/kotlin/com/android/ndkports/SourceExtractTask.kt
deleted file mode 100644
index 4f2660f..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/SourceExtractTask.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.android.ndkports
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.file.RegularFileProperty
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-
-abstract class SourceExtractTask : DefaultTask() {
-    @get:InputFile
-    abstract val source: RegularFileProperty
-
-    @get:OutputDirectory
-    abstract val outDir: DirectoryProperty
-
-    @TaskAction
-    fun run() {
-        val pb = ProcessBuilder(
-            listOf(
-                "tar",
-                "xf",
-                source.get().asFile.absolutePath,
-                "--strip-components=1"
-            )
-        ).redirectErrorStream(true).directory(outDir.get().asFile)
-
-        val result = pb.start()
-        val output = result.inputStream.bufferedReader().use { it.readText() }
-        if (result.waitFor() != 0) {
-            throw RuntimeException("Subprocess failed with:\n$output")
-        }
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/Testing.kt b/buildSrc/src/main/kotlin/com/android/ndkports/Testing.kt
deleted file mode 100644
index 203e04f..0000000
--- a/buildSrc/src/main/kotlin/com/android/ndkports/Testing.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.android.ndkports
-
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.SendChannel
-import kotlinx.coroutines.channels.toList
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-
-sealed class TestResult(val name: String, val abi: Abi) {
-    class Success(name: String, abi: Abi) : TestResult(name, abi) {
-        override fun toString(): String = "PASS $abi $name"
-    }
-
-    class Failure(name: String, abi: Abi, private val output: String) :
-        TestResult(name, abi) {
-        override fun toString(): String = "FAIL $abi $name: $output"
-    }
-}
-
-private val BASE_DEVICE_DIRECTORY = File("/data/local/tmp/ndkports")
-
-data class PushSpec(val src: File, val dest: File)
-
-class PushBuilder(val abi: Abi, val toolchain: Toolchain) {
-    val pushSpecs = mutableListOf<PushSpec>()
-
-    fun push(src: File, dest: File) = pushSpecs.add(PushSpec(src, dest))
-}
-
-data class ShellTestSpec(val name: String, val cmd: Iterable<String>)
-
-class ShellTestBuilder(val deviceDirectory: File, val abi: Abi) {
-    val runSpecs = mutableListOf<ShellTestSpec>()
-
-    fun shellTest(name: String, cmd: Iterable<String>) =
-        runSpecs.add(ShellTestSpec(name, cmd))
-}
-
-abstract class AndroidExecutableTestTask : DefaultTask() {
-    @get:InputDirectory
-    abstract val ndkPath: DirectoryProperty
-
-    private val ndk: Ndk
-        get() = Ndk(ndkPath.asFile.get())
-
-    @get:Input
-    abstract val minSdkVersion: Property<Int>
-
-    @get:Input
-    abstract val push: Property<PushBuilder.() -> Unit>
-
-    fun push(block: PushBuilder.() -> Unit) = push.set(block)
-
-    @get:Input
-    abstract val run: Property<ShellTestBuilder.() -> Unit>
-
-    fun run(block: ShellTestBuilder.() -> Unit) = run.set(block)
-
-    private fun deviceDirectoryForAbi(abi: Abi): File =
-        BASE_DEVICE_DIRECTORY.resolve(project.name).resolve(abi.toString())
-
-    private suspend fun runTests(
-        device: Device, abi: Abi, resultChannel: SendChannel<TestResult>
-    ) = coroutineScope {
-        val deviceDirectory = deviceDirectoryForAbi(abi)
-
-        val pushBlock = push.get()
-        val runBlock = run.get()
-
-        val pushBuilder =
-            PushBuilder(abi, Toolchain(ndk, abi, minSdkVersion.get()))
-        pushBuilder.pushBlock()
-        coroutineScope {
-            pushBuilder.pushSpecs.forEach {
-                launch(Dispatchers.IO) {
-                    device.push(
-                        it.src, deviceDirectory.resolve(it.dest)
-                    )
-                }
-            }
-        }
-
-        val runBuilder = ShellTestBuilder(deviceDirectory, abi)
-        runBuilder.runBlock()
-        runBuilder.runSpecs.forEach {
-            launch(Dispatchers.IO) {
-                val result = try {
-                    device.shell(it.cmd)
-                    TestResult.Success(it.name, abi)
-                } catch (ex: AdbException) {
-                    TestResult.Failure(it.name, abi, "${ex.cmd}\n${ex.output}")
-                }
-
-                resultChannel.send(result)
-            }
-        }
-    }
-
-    @Suppress("UnstableApiUsage")
-    @TaskAction
-    fun runTask() = runBlocking {
-        val fleet = DeviceFleet()
-        val warningChannel = Channel<String>(Channel.UNLIMITED)
-        val resultChannel = Channel<TestResult>(Channel.UNLIMITED)
-        coroutineScope {
-            for (abi in Abi.values()) {
-                launch {
-                    val device = fleet.findDeviceFor(
-                        abi, abi.adjustMinSdkVersion(minSdkVersion.get())
-                    )
-                    if (device == null) {
-                        warningChannel.send(
-                            "No device capable of running tests for $abi " +
-                                    "minSdkVersion 21"
-                        )
-                        return@launch
-                    }
-                    device.shell(
-                        listOf(
-                            "rm", "-rf", deviceDirectoryForAbi(abi).toString()
-                        )
-                    )
-                    runTests(device, abi, resultChannel)
-                }
-            }
-        }
-        warningChannel.close()
-        resultChannel.close()
-
-        for (warning in warningChannel) {
-            logger.warn(warning)
-        }
-
-        val failures =
-            resultChannel.toList().filterIsInstance<TestResult.Failure>()
-        if (failures.isNotEmpty()) {
-            throw RuntimeException(
-                "Tests failed:\n${failures.joinToString("\n")}"
-            )
-        }
-    }
-}
\ No newline at end of file
diff --git a/curl/build.gradle.kts b/curl/build.gradle.kts
deleted file mode 100644
index 089ef04..0000000
--- a/curl/build.gradle.kts
+++ /dev/null
@@ -1,106 +0,0 @@
-import com.android.ndkports.AutoconfPortTask
-import com.android.ndkports.CMakeCompatibleVersion
-import com.android.ndkports.PrefabSysrootPlugin
-
-val portVersion = "7.79.1"
-
-group = "com.android.ndk.thirdparty"
-version = "$portVersion${rootProject.extra.get("snapshotSuffix")}"
-
-plugins {
-    id("maven-publish")
-    id("com.android.ndkports.NdkPorts")
-}
-
-dependencies {
-    implementation(project(":openssl"))
-}
-
-ndkPorts {
-    ndkPath.set(File(project.findProperty("ndkPath") as String))
-    source.set(project.file("src.tar.gz"))
-    minSdkVersion.set(16)
-}
-
-tasks.prefab {
-    generator.set(PrefabSysrootPlugin::class.java)
-}
-
-tasks.register<AutoconfPortTask>("buildPort") {
-    autoconf {
-        args(
-            "--disable-ntlm-wb",
-            "--enable-ipv6",
-            "--with-zlib",
-            "--with-ca-path=/system/etc/security/cacerts",
-            "--with-ssl=$sysroot"
-        )
-
-        // aarch64 still defaults to bfd which transitively checks libraries.
-        // When curl is linking one of its own libraries which depends on
-        // openssl, it doesn't pass -rpath-link to be able to find the SSL
-        // libraries and fails to build because of it.
-        //
-        // TODO: Switch to lld once we're using r21.
-        env("LDFLAGS", "-fuse-ld=gold")
-    }
-}
-
-tasks.prefabPackage {
-    version.set(CMakeCompatibleVersion.parse(portVersion))
-
-    licensePath.set("COPYING")
-
-    @Suppress("UnstableApiUsage") dependencies.set(
-        mapOf(
-            "openssl" to "1.1.1k"
-        )
-    )
-
-    modules {
-        create("curl") {
-            dependencies.set(
-                listOf(
-                    "//openssl:crypto", "//openssl:ssl"
-                )
-            )
-        }
-    }
-}
-
-publishing {
-    publications {
-        create<MavenPublication>("maven") {
-            from(components["prefab"])
-            pom {
-                name.set("curl")
-                description.set("The ndkports AAR for curl.")
-                url.set(
-                    "https://android.googlesource.com/platform/tools/ndkports"
-                )
-                licenses {
-                    license {
-                        name.set("The curl License")
-                        url.set("https://curl.haxx.se/docs/copyright.html")
-                        distribution.set("repo")
-                    }
-                }
-                developers {
-                    developer {
-                        name.set("The Android Open Source Project")
-                    }
-                }
-                scm {
-                    url.set("https://android.googlesource.com/platform/tools/ndkports")
-                    connection.set("scm:git:https://android.googlesource.com/platform/tools/ndkports")
-                }
-            }
-        }
-    }
-
-    repositories {
-        maven {
-            url = uri("${rootProject.buildDir}/repository")
-        }
-    }
-}
diff --git a/curl/src.tar.gz b/curl/src.tar.gz
deleted file mode 100644
index b6d3b85..0000000
--- a/curl/src.tar.gz
+++ /dev/null
Binary files differ
diff --git a/googletest/build.gradle.kts b/googletest/build.gradle.kts
deleted file mode 100644
index c365519..0000000
--- a/googletest/build.gradle.kts
+++ /dev/null
@@ -1,124 +0,0 @@
-import com.android.ndkports.AndroidExecutableTestTask
-import com.android.ndkports.CMakeCompatibleVersion
-import com.android.ndkports.CMakePortTask
-
-val portVersion = "1.11.0"
-
-group = "com.android.ndk.thirdparty"
-version = "$portVersion${rootProject.extra.get("snapshotSuffix")}"
-
-plugins {
-    id("maven-publish")
-    id("com.android.ndkports.NdkPorts")
-}
-
-ndkPorts {
-    ndkPath.set(File(project.findProperty("ndkPath") as String))
-    source.set(project.file("src.tar.gz"))
-    minSdkVersion.set(16)
-}
-
-val buildTask = tasks.register<CMakePortTask>("buildPort") {
-    cmake {
-        arg("-Dgtest_build_tests=ON")
-        arg("-Dgmock_build_tests=ON")
-    }
-}
-
-tasks.prefabPackage {
-    version.set(CMakeCompatibleVersion.parse(portVersion))
-
-    modules {
-        create("gtest") {
-            static.set(true)
-        }
-        create("gtest_main") {
-            static.set(true)
-        }
-        create("gmock") {
-            static.set(true)
-        }
-        create("gmock_main") {
-            static.set(true)
-        }
-    }
-}
-
-fun findTests(directory: File) = directory.listFiles()!!.filter {
-    // There are also many tests that end with test_, but those require running
-    // Python on the device.
-    it.name.endsWith("test")
-}
-
-tasks.register<AndroidExecutableTestTask>("test") {
-    push {
-        val buildDir = buildTask.get().buildDirectoryFor(abi)
-        findTests(buildDir.resolve("googlemock")).forEach { test ->
-            push(test, File("googlemock").resolve(test.name))
-        }
-        findTests(buildDir.resolve("googletest")).forEach { test ->
-            push(test, File("googletest").resolve(test.name))
-        }
-    }
-
-    run {
-        val buildDir = buildTask.get().buildDirectoryFor(abi)
-        findTests(buildDir.resolve("googlemock")).forEach { test ->
-            shellTest(
-                test.name, listOf(
-                    "cd",
-                    deviceDirectory.resolve("googlemock").toString(),
-                    "&&",
-                    "./${test.name}"
-                )
-            )
-        }
-        findTests(buildDir.resolve("googletest")).forEach { test ->
-            shellTest(
-                test.name, listOf(
-                    "cd",
-                    deviceDirectory.resolve("googletest").toString(),
-                    "&&",
-                    "./${test.name}"
-                )
-            )
-        }
-    }
-}
-
-publishing {
-    publications {
-        create<MavenPublication>("maven") {
-            from(components["prefab"])
-            pom {
-                name.set("GoogleTest")
-                description.set("The ndkports AAR for GoogleTest.")
-                url.set(
-                    "https://android.googlesource.com/platform/tools/ndkports"
-                )
-                licenses {
-                    license {
-                        name.set("BSD-3-Clause License")
-                        url.set("https://github.com/google/googletest/blob/master/LICENSE")
-                        distribution.set("repo")
-                    }
-                }
-                developers {
-                    developer {
-                        name.set("The Android Open Source Project")
-                    }
-                }
-                scm {
-                    url.set("https://android.googlesource.com/platform/tools/ndkports")
-                    connection.set("scm:git:https://android.googlesource.com/platform/tools/ndkports")
-                }
-            }
-        }
-    }
-
-    repositories {
-        maven {
-            url = uri("${rootProject.buildDir}/repository")
-        }
-    }
-}
diff --git a/googletest/src.tar.gz b/googletest/src.tar.gz
deleted file mode 100644
index 8163d74..0000000
--- a/googletest/src.tar.gz
+++ /dev/null
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c..94336fc 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index da9702f..7c4388a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0..cccdd3d 100755
--- a/gradlew
+++ b/gradlew
@@ -1,21 +1,5 @@
 #!/usr/bin/env sh
 
-#
-# Copyright 2015 the original author or authors.
-#
-# 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.
-#
-
 ##############################################################################
 ##
 ##  Gradle start up script for UN*X
@@ -44,7 +28,7 @@
 APP_BASE_NAME=`basename "$0"`
 
 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+DEFAULT_JVM_OPTS=""
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD="maximum"
@@ -82,7 +66,6 @@
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
-
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -126,11 +109,10 @@
     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
 fi
 
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
     JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
@@ -156,19 +138,19 @@
         else
             eval `echo args$i`="\"$arg\""
         fi
-        i=`expr $i + 1`
+        i=$((i+1))
     done
     case $i in
-        0) set -- ;;
-        1) set -- "$args0" ;;
-        2) set -- "$args0" "$args1" ;;
-        3) set -- "$args0" "$args1" "$args2" ;;
-        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
     esac
 fi
 
@@ -177,9 +159,14 @@
     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
     echo " "
 }
-APP_ARGS=`save "$@"`
+APP_ARGS=$(save "$@")
 
 # Collect all arguments for the java command, following the shell quoting and substitution rules
 eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
 
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
 exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..f955316 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,19 +1,3 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem      https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
 @if "%DEBUG%" == "" @echo off
 @rem ##########################################################################
 @rem
@@ -29,18 +13,15 @@
 set APP_BASE_NAME=%~n0
 set APP_HOME=%DIRNAME%
 
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+set DEFAULT_JVM_OPTS=
 
 @rem Find java.exe
 if defined JAVA_HOME goto findJavaFromJavaHome
 
 set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if "%ERRORLEVEL%" == "0" goto init
 
 echo.
 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -54,7 +35,7 @@
 set JAVA_HOME=%JAVA_HOME:"=%
 set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
-if exist "%JAVA_EXE%" goto execute
+if exist "%JAVA_EXE%" goto init
 
 echo.
 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -64,14 +45,28 @@
 
 goto fail
 
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
 :execute
 @rem Setup the command line
 
 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 
-
 @rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
 
 :end
 @rem End local scope for the variables with windows NT shell
diff --git a/jsoncpp/build.gradle.kts b/jsoncpp/build.gradle.kts
deleted file mode 100644
index 29ffc66..0000000
--- a/jsoncpp/build.gradle.kts
+++ /dev/null
@@ -1,103 +0,0 @@
-import com.android.ndkports.AndroidExecutableTestTask
-import com.android.ndkports.CMakeCompatibleVersion
-import com.android.ndkports.MesonPortTask
-
-val portVersion = "1.9.5"
-
-group = "com.android.ndk.thirdparty"
-version = "$portVersion${rootProject.extra.get("snapshotSuffix")}"
-
-plugins {
-    id("maven-publish")
-    id("com.android.ndkports.NdkPorts")
-}
-
-ndkPorts {
-    ndkPath.set(File(project.findProperty("ndkPath") as String))
-    source.set(project.file("src.tar.gz"))
-    minSdkVersion.set(16)
-}
-
-tasks.extractSrc {
-    doLast {
-        // jsoncpp has a "version" file on the include path that conflicts with
-        // https://en.cppreference.com/w/cpp/header/version. Remove it so we can
-        // build.
-        outDir.get().asFile.resolve("version").delete()
-    }
-}
-
-val buildTask = tasks.register<MesonPortTask>("buildPort")
-
-tasks.prefabPackage {
-    version.set(CMakeCompatibleVersion.parse(portVersion))
-
-    modules {
-        create("jsoncpp")
-    }
-}
-
-tasks.register<AndroidExecutableTestTask>("test") {
-    push {
-        push(
-            buildTask.get().buildDirectoryFor(abi).resolve("jsoncpp_test"),
-            File("jsoncpp_test")
-        )
-        push(
-            buildTask.get().installDirectoryFor(abi)
-                .resolve("lib/libjsoncpp.so"), File("libjsoncpp.so")
-        )
-        push(
-            toolchain.sysrootLibs.resolve("libc++_shared.so"),
-            File("libc++_shared.so")
-        )
-    }
-
-    run {
-        // JsonCpp has other tests, but they require running Python on the
-        // device.
-        shellTest(
-            "jsoncpp_test", listOf(
-                "LD_LIBRARY_PATH=$deviceDirectory",
-                deviceDirectory.resolve("jsoncpp_test").toString()
-            )
-        )
-    }
-}
-
-publishing {
-    publications {
-        create<MavenPublication>("maven") {
-            from(components["prefab"])
-            pom {
-                name.set("JsonCpp")
-                description.set("The ndkports AAR for JsonCpp.")
-                url.set(
-                    "https://android.googlesource.com/platform/tools/ndkports"
-                )
-                licenses {
-                    license {
-                        name.set("The JsonCpp License")
-                        url.set("https://github.com/open-source-parsers/jsoncpp/blob/master/LICENSE")
-                        distribution.set("repo")
-                    }
-                }
-                developers {
-                    developer {
-                        name.set("The Android Open Source Project")
-                    }
-                }
-                scm {
-                    url.set("https://android.googlesource.com/platform/tools/ndkports")
-                    connection.set("scm:git:https://android.googlesource.com/platform/tools/ndkports")
-                }
-            }
-        }
-    }
-
-    repositories {
-        maven {
-            url = uri("${rootProject.buildDir}/repository")
-        }
-    }
-}
diff --git a/jsoncpp/src.tar.gz b/jsoncpp/src.tar.gz
deleted file mode 100644
index 35052af..0000000
--- a/jsoncpp/src.tar.gz
+++ /dev/null
Binary files differ
diff --git a/openssl/build.gradle.kts b/openssl/build.gradle.kts
deleted file mode 100644
index b7c7a48..0000000
--- a/openssl/build.gradle.kts
+++ /dev/null
@@ -1,180 +0,0 @@
-import com.android.ndkports.AdHocPortTask
-import com.android.ndkports.AndroidExecutableTestTask
-import com.android.ndkports.CMakeCompatibleVersion
-
-fun openSslVersionToCMakeVersion(openSslVersion: String): CMakeCompatibleVersion {
-    val (major, minor, microAndLetter) = openSslVersion.split(".")
-    val letter = microAndLetter.last()
-    val micro = microAndLetter.substringBefore(letter)
-    val tweak = if (letter.isDigit()) {
-        // 1.1.1 is 1.1.1.0.
-        0
-    } else {
-        // 1.1.1a is 1.1.1.1.
-        letter.toInt() - 'a'.toInt() + 1
-    }
-
-    return CMakeCompatibleVersion(
-        major.toInt(), minor.toInt(), micro.toInt(), tweak
-    )
-}
-
-val portVersion = "1.1.1l"
-val prefabVersion = openSslVersionToCMakeVersion(portVersion)
-
-group = "com.android.ndk.thirdparty"
-version = "$portVersion${rootProject.extra.get("snapshotSuffix")}"
-
-plugins {
-    id("maven-publish")
-    id("com.android.ndkports.NdkPorts")
-}
-
-ndkPorts {
-    ndkPath.set(File(project.findProperty("ndkPath") as String))
-    source.set(project.file("src.tar.gz"))
-    minSdkVersion.set(16)
-}
-
-val buildTask = tasks.register<AdHocPortTask>("buildPort") {
-    builder {
-        run {
-            args(
-                sourceDirectory.resolve("Configure").absolutePath,
-                "android-${toolchain.abi.archName}",
-                "-D__ANDROID_API__=${toolchain.api}",
-                "--prefix=${installDirectory.absolutePath}",
-                "--openssldir=${installDirectory.absolutePath}",
-                "no-sctp",
-                "shared"
-            )
-
-            env("ANDROID_NDK", toolchain.ndk.path.absolutePath)
-            env("PATH", "${toolchain.binDir}:${System.getenv("PATH")}")
-        }
-
-        run {
-            args("make", "-j$ncpus", "SHLIB_EXT=.so")
-
-            env("ANDROID_NDK", toolchain.ndk.path.absolutePath)
-            env("PATH", "${toolchain.binDir}:${System.getenv("PATH")}")
-        }
-
-        run {
-            args("make", "install_sw", "SHLIB_EXT=.so")
-
-            env("ANDROID_NDK", toolchain.ndk.path.absolutePath)
-            env("PATH", "${toolchain.binDir}:${System.getenv("PATH")}")
-        }
-    }
-}
-
-tasks.prefabPackage {
-    version.set(prefabVersion)
-
-    modules {
-        create("crypto")
-        create("ssl")
-    }
-}
-
-tasks.register<AndroidExecutableTestTask>("test") {
-    val srcDir = tasks.extractSrc.get().outDir.asFile.get()
-    val testSrc = srcDir.resolve("test/ssl-tests")
-    val deviceTestRelPath = File("testconf")
-
-    val unsupportedTests = listOf(
-        // This test is empty and appears to just be broken in 1.1.1k.
-        "16-certstatus.conf",
-        // zlib support is not enabled.
-        "22-compression.conf",
-        // Android does not support SCTP sockets and this test requires them.
-        "29-dtls-sctp-label-bug.conf"
-    )
-
-    push {
-        val ignoredExtensions = listOf("o", "d")
-        val buildDirectory = buildTask.get().buildDirectoryFor(abi)
-        push(
-            srcDir.resolve("test/ct/log_list.conf"), File("log_list.conf")
-        )
-        for (file in buildDirectory.walk()) {
-            if (!file.isFile) {
-                continue
-            }
-
-            if (file.extension in ignoredExtensions) {
-                continue
-            }
-
-            push(file, file.relativeTo(buildDirectory))
-        }
-        for (file in testSrc.walk()) {
-            if (file.extension == "conf") {
-                push(
-                    file, deviceTestRelPath.resolve(file.relativeTo(testSrc))
-                )
-            }
-        }
-        push(srcDir.resolve("test/certs"), File("certs"))
-    }
-
-    run {
-        // https://github.com/openssl/openssl/blob/master/test/README.ssltest.md
-        val sslTest = deviceDirectory.resolve("test/ssl_test")
-        val ctlogFile = deviceDirectory.resolve("log_list.conf")
-        val testCertDir = deviceDirectory.resolve("certs")
-        for (file in testSrc.walk()) {
-            val test = deviceDirectory.resolve(deviceTestRelPath)
-                .resolve(file.relativeTo(testSrc))
-            if (file.extension == "conf" && file.name !in unsupportedTests) {
-                shellTest(
-                    file.relativeTo(testSrc).toString(), listOf(
-                        "LD_LIBRARY_PATH=$deviceDirectory",
-                        "CTLOG_FILE=$ctlogFile",
-                        "TEST_CERTS_DIR=$testCertDir",
-                        sslTest.toString(),
-                        test.toString()
-                    )
-                )
-            }
-        }
-    }
-}
-
-publishing {
-    publications {
-        create<MavenPublication>("maven") {
-            from(components["prefab"])
-            pom {
-                name.set("OpenSSL")
-                description.set("The ndkports AAR for OpenSSL.")
-                url.set(
-                    "https://android.googlesource.com/platform/tools/ndkports"
-                )
-                licenses {
-                    license {
-                        name.set("Dual OpenSSL and SSLeay License")
-                        url.set("https://www.openssl.org/source/license-openssl-ssleay.txt")
-                        distribution.set("repo")
-                    }
-                }
-                developers {
-                    developer {
-                        name.set("The Android Open Source Project")
-                    }
-                }
-                scm {
-                    url.set("https://android.googlesource.com/platform/tools/ndkports")
-                    connection.set("scm:git:https://android.googlesource.com/platform/tools/ndkports")
-                }
-            }
-        }
-    }
-
-    repositories {
-        maven {
-            url = uri("${rootProject.buildDir}/repository")
-        }
-    }
-}
diff --git a/openssl/src.tar.gz b/openssl/src.tar.gz
deleted file mode 100644
index 81be7f9..0000000
--- a/openssl/src.tar.gz
+++ /dev/null
Binary files differ
diff --git a/ports/curl/port.kts b/ports/curl/port.kts
new file mode 100644
index 0000000..a870a86
--- /dev/null
+++ b/ports/curl/port.kts
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import java.io.File
+
+object : AutoconfPort() {
+    override val name = "curl"
+    override val version = "7.69.1"
+    override val mavenVersion = "$version-alpha-1"
+    override val licensePath = "COPYING"
+
+    override val license = License(
+        "The curl License", "https://curl.haxx.se/docs/copyright.html"
+    )
+
+    override val dependencies = listOf("openssl")
+
+    override val modules = listOf(
+        Module(
+            "curl",
+            dependencies = listOf("//openssl:crypto", "//openssl:ssl")
+        )
+    )
+
+    override fun configureArgs(
+        workingDirectory: File,
+        toolchain: Toolchain
+    ): List<String> {
+        val sslPrefix = installDirectoryForPort(
+            "openssl",
+            workingDirectory,
+            toolchain
+        ).absolutePath
+        return listOf(
+            "--disable-ntlm-wb",
+            "--enable-ipv6",
+            "--with-zlib",
+            "--with-ca-path=/system/etc/security/cacerts",
+            "--with-ssl=$sslPrefix"
+        )
+    }
+
+    override fun configureEnv(
+        workingDirectory: File,
+        toolchain: Toolchain
+    ): Map<String, String> = mapOf(
+        // aarch64 still defaults to bfd which transitively checks libraries.
+        // When curl is linking one of its own libraries which depends on
+        // openssl, it doesn't pass -rpath-link to be able to find the SSL
+        // libraries and fails to build because of it.
+        //
+        // TODO: Switch to lld once we're using r21.
+        "LDFLAGS" to "-fuse-ld=gold"
+    )
+}
diff --git a/ports/curl/src.tar.gz b/ports/curl/src.tar.gz
new file mode 100644
index 0000000..b0da690
--- /dev/null
+++ b/ports/curl/src.tar.gz
Binary files differ
diff --git a/ports/jsoncpp/port.kts b/ports/jsoncpp/port.kts
new file mode 100644
index 0000000..8a3e1ba
--- /dev/null
+++ b/ports/jsoncpp/port.kts
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import java.io.File
+
+object : MesonPort() {
+    override val name = "jsoncpp"
+    override val version = "1.8.4"
+    override val mavenVersion = "$version-alpha-1"
+
+    override val license = License(
+        "The JsonCpp License",
+        "https://github.com/open-source-parsers/jsoncpp/blob/master/LICENSE"
+    )
+
+    override val modules = listOf(
+        Module("jsoncpp")
+    )
+
+    override fun extractSource(
+        sourceTarball: File,
+        sourceDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> =
+        super.extractSource(sourceTarball, sourceDirectory, workingDirectory)
+            .onSuccess {
+                // jsoncpp has a "version" file on the include path that
+                // conflicts with
+                // https://en.cppreference.com/w/cpp/header/version. Remove it
+                // so we can build.
+                sourceDirectory.resolve("version").delete()
+            }
+}
diff --git a/ports/jsoncpp/src.tar.gz b/ports/jsoncpp/src.tar.gz
new file mode 100644
index 0000000..7f3cd4b
--- /dev/null
+++ b/ports/jsoncpp/src.tar.gz
Binary files differ
diff --git a/ports/openssl/port.kts b/ports/openssl/port.kts
new file mode 100644
index 0000000..45b5206
--- /dev/null
+++ b/ports/openssl/port.kts
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import java.io.File
+
+object : Port() {
+    override val name = "openssl"
+    override val version = "1.1.1g"
+    override val mavenVersion = "$version-alpha-1"
+    override val prefabVersion = CMakeCompatibleVersion(1, 1, 1, 7)
+
+    override val license = License(
+        "Dual OpenSSL and SSLeay License",
+        "https://www.openssl.org/source/license-openssl-ssleay.txt"
+    )
+
+    override val modules = listOf(
+        Module("crypto"),
+        Module("ssl")
+    )
+
+    override fun configure(
+        toolchain: Toolchain,
+        sourceDirectory: File,
+        buildDirectory: File,
+        installDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> {
+        buildDirectory.mkdirs()
+        return executeProcessStep(
+            listOf(
+                sourceDirectory.resolve("Configure").absolutePath,
+                "android-${toolchain.abi.archName}",
+                "-D__ANDROID_API__=${toolchain.api}",
+                "--prefix=${installDirectory.absolutePath}",
+                "--openssldir=${installDirectory.absolutePath}",
+                "shared"
+            ),
+            buildDirectory,
+            additionalEnvironment = mapOf(
+                "ANDROID_NDK" to toolchain.ndk.path.absolutePath,
+                "PATH" to "${toolchain.binDir}:${System.getenv("PATH")}"
+            )
+        )
+    }
+
+    override fun build(
+        toolchain: Toolchain,
+        buildDirectory: File
+    ): Result<Unit, String> =
+        executeProcessStep(
+            listOf(
+                "make",
+                "-j$ncpus",
+                "SHLIB_EXT=.so"
+            ), buildDirectory,
+            additionalEnvironment = mapOf(
+                "ANDROID_NDK" to toolchain.ndk.path.absolutePath,
+                "PATH" to "${toolchain.binDir}:${System.getenv("PATH")}"
+            )
+        )
+
+    override fun install(
+        toolchain: Toolchain,
+        buildDirectory: File,
+        installDirectory: File
+    ): Result<Unit, String> =
+        executeProcessStep(
+            listOf("make", "install_sw", "SHLIB_EXT=.so"), buildDirectory,
+            additionalEnvironment = mapOf(
+                "ANDROID_NDK" to toolchain.ndk.path.absolutePath,
+                "PATH" to "${toolchain.binDir}:${System.getenv("PATH")}"
+            )
+        )
+}
diff --git a/ports/openssl/src.tar.gz b/ports/openssl/src.tar.gz
new file mode 100644
index 0000000..e768f9e
--- /dev/null
+++ b/ports/openssl/src.tar.gz
Binary files differ
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..e7c16b6
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+docker build -t ndkports .
+docker run --rm -v $(pwd):/src ndkports
diff --git a/scripts/build_release.sh b/scripts/build_release.sh
deleted file mode 100755
index f99e223..0000000
--- a/scripts/build_release.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-set -e
-docker build -t ndkports .
-# We need to specify the full argument list for gradle explicitly because
-# there's no way to append to docker's CMD. This should be kept the same as the
-# default, but adding -Prelease.
-docker run --rm -v $(pwd):/src ndkports \
-  --stacktrace -PndkPath=/ndk -Prelease release
diff --git a/scripts/build_snapshot.sh b/scripts/build_snapshot.sh
deleted file mode 100755
index 4c9d767..0000000
--- a/scripts/build_snapshot.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-set -e
-docker build -t ndkports .
-# Default command for the docker image handles the NDK location, --stacktrace,
-# task list, etc.
-docker run --rm -v $(pwd):/src ndkports
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 726326c..4549d2e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -8,9 +8,4 @@
             }
         }
     }
-}
-
-include("curl")
-include("googletest")
-include("jsoncpp")
-include("openssl")
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/android/ndkports/Abi.kt b/src/main/kotlin/com/android/ndkports/Abi.kt
new file mode 100644
index 0000000..58981a2
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Abi.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+enum class Abi(val archName: String, val abiName: String) {
+    Arm("arm", "armeabi-v7a"),
+    Arm64("arm64", "arm64-v8a"),
+    X86("x86", "x86"),
+    X86_64("x86_64", "x86_64"),
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/android/ndkports/AutoconfPort.kt b/src/main/kotlin/com/android/ndkports/AutoconfPort.kt
new file mode 100644
index 0000000..1c009cb
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/AutoconfPort.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import java.io.File
+
+abstract class AutoconfPort : Port() {
+    open fun configureArgs(
+        workingDirectory: File,
+        toolchain: Toolchain
+    ): List<String> = emptyList()
+
+    open fun configureEnv(
+        workingDirectory: File,
+        toolchain: Toolchain
+    ): Map<String, String> = emptyMap()
+
+    override fun configure(
+        toolchain: Toolchain,
+        sourceDirectory: File,
+        buildDirectory: File,
+        installDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> {
+        buildDirectory.mkdirs()
+        return executeProcessStep(
+            listOf(
+                "${sourceDirectory.absolutePath}/configure",
+                "--host=${toolchain.binutilsTriple}",
+                "--prefix=${installDirectory.absolutePath}"
+            ) + configureArgs(workingDirectory, toolchain),
+            buildDirectory,
+            additionalEnvironment = mutableMapOf(
+                "AR" to toolchain.ar.absolutePath,
+                "CC" to toolchain.clang.absolutePath,
+                "CXX" to toolchain.clangxx.absolutePath,
+                "RANLIB" to toolchain.ranlib.absolutePath,
+                "STRIP" to toolchain.strip.absolutePath,
+                "PATH" to "${toolchain.binDir}:${System.getenv("PATH")}"
+            ).apply { putAll(configureEnv(workingDirectory, toolchain)) }
+        )
+    }
+
+    override fun build(
+        toolchain: Toolchain,
+        buildDirectory: File
+    ): Result<Unit, String> =
+        executeProcessStep(
+            listOf("make", "-j$ncpus"), buildDirectory
+        )
+
+    override fun install(
+        toolchain: Toolchain,
+        buildDirectory: File,
+        installDirectory: File
+    ): Result<Unit, String> =
+        executeProcessStep(
+            listOf("make", "-j$ncpus", "install"), buildDirectory
+        )
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt b/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
similarity index 97%
rename from buildSrc/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
rename to src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
index ba58ccb..6d3ee45 100644
--- a/buildSrc/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
+++ b/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
@@ -16,7 +16,7 @@
 
 package com.android.ndkports
 
-import java.io.Serializable
+import java.io.File
 
 /**
  * A version number that is compatible with CMake's package version format.
@@ -31,7 +31,7 @@
     val minor: Int?,
     val patch: Int?,
     val tweak: Int?
-) : Serializable {
+) {
     init {
         if (tweak != null) {
             require(patch != null)
diff --git a/src/main/kotlin/com/android/ndkports/Cli.kt b/src/main/kotlin/com/android/ndkports/Cli.kt
new file mode 100644
index 0000000..e2a5879
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Cli.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.arguments.multiple
+import com.github.ajalt.clikt.parameters.arguments.validate
+import com.github.ajalt.clikt.parameters.options.convert
+import com.github.ajalt.clikt.parameters.options.default
+import com.github.ajalt.clikt.parameters.options.flag
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.required
+import com.github.ajalt.clikt.parameters.types.file
+import de.swirtz.ktsrunner.objectloader.KtsObjectLoader
+import java.io.File
+import java.io.FileNotFoundException
+import java.lang.RuntimeException
+import kotlin.system.exitProcess
+
+class Cli : CliktCommand(help = "ndkports") {
+    private val outDir: File by option(
+        "-o",
+        "--out",
+        help = "Build directory."
+    ).file().default(File("out"))
+
+    private val publishToMavenLocal: Boolean by option(
+        help = "Publish AARs to the local Maven repository (~/.m2/repository)"
+    ).flag()
+
+    private val packages: List<String> by argument(
+        help = "Names of packages to build."
+    ).multiple().validate {
+        require(it.isNotEmpty()) { "must provide at least one package" }
+    }
+
+    private val ndk: Ndk by option().convert { Ndk(File(it)) }.required()
+
+    private fun portDirectoryFor(name: String): File =
+        File("ports").resolve(name)
+
+    private fun loadPort(name: String): Port {
+        val portDir = portDirectoryFor(name).also {
+            if (!it.exists()) {
+                throw FileNotFoundException("Could not find ${it.path}")
+            }
+        }
+
+        val portFile = portDir.resolve("port.kts").also {
+            if (!it.exists()) {
+                throw FileNotFoundException("Could not find ${it.path}")
+            }
+        }
+
+        return KtsObjectLoader().load(portFile.reader())
+    }
+
+    override fun run() {
+        println("Building packages: ${packages.joinToString(", ")}")
+        val portsByName = packages.map { loadPort(it) }.associateBy { it.name }
+        for (port in portsByName.values) {
+            val workingDirectory =
+                outDir.resolve(port.name).also { it.mkdirs() }
+
+            val sourceDirectory = workingDirectory.resolve("src")
+            val sourceTarball =
+                portDirectoryFor(port.name).resolve("src.tar.gz")
+
+            port.extractSource(sourceTarball, sourceDirectory, workingDirectory)
+
+            val apiForAbi = mapOf(
+                Abi.Arm to 16,
+                Abi.Arm64 to 21,
+                Abi.X86 to 16,
+                Abi.X86_64 to 21
+            )
+            for (abi in Abi.values()) {
+                val api = apiForAbi.getOrElse(abi) {
+                    throw RuntimeException(
+                        "No API level specified for ${abi.abiName}"
+                    )
+                }
+                val toolchain = Toolchain(ndk, abi, api)
+
+                val buildDirectory = workingDirectory.resolve("build/$abi")
+                val installDirectory = installDirectoryForPort(
+                    port.name, workingDirectory, toolchain
+                )
+
+                port.run(
+                    toolchain,
+                    sourceDirectory,
+                    buildDirectory,
+                    installDirectory,
+                    workingDirectory
+                ).onFailure {
+                    println(it)
+                    exitProcess(1)
+                }
+            }
+
+            PrefabPackageBuilder(
+                port,
+                workingDirectory,
+                sourceDirectory,
+                publishToMavenLocal,
+                ndk,
+                apiForAbi,
+                portsByName
+            ).build()
+        }
+    }
+}
+
+fun main(args: Array<String>) {
+    Cli().main(args)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/android/ndkports/License.kt b/src/main/kotlin/com/android/ndkports/License.kt
new file mode 100644
index 0000000..8ec639f
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/License.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+data class License(val name: String, val url: String)
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/MesonPortTask.kt b/src/main/kotlin/com/android/ndkports/MesonPort.kt
similarity index 61%
rename from buildSrc/src/main/kotlin/com/android/ndkports/MesonPortTask.kt
rename to src/main/kotlin/com/android/ndkports/MesonPort.kt
index 6be9b9e..8c1f706 100644
--- a/buildSrc/src/main/kotlin/com/android/ndkports/MesonPortTask.kt
+++ b/src/main/kotlin/com/android/ndkports/MesonPort.kt
@@ -16,41 +16,24 @@
 
 package com.android.ndkports
 
-import org.gradle.api.model.ObjectFactory
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
 import java.io.File
-import javax.inject.Inject
 
-@Suppress("UnstableApiUsage")
-abstract class MesonPortTask @Inject constructor(objects: ObjectFactory) :
-    PortTask() {
+abstract class MesonPort : Port() {
     enum class DefaultLibraryType(val argument: String) {
-        Both("both"), Shared("shared"), Static("static")
+        Both("both"),
+        Shared("shared"),
+        Static("static")
     }
 
-    @get:Input
-    val defaultLibraryType: Property<DefaultLibraryType> =
-        objects.property(DefaultLibraryType::class.java)
-            .convention(DefaultLibraryType.Shared)
+    open val defaultLibraryType: DefaultLibraryType = DefaultLibraryType.Shared
 
-    override fun buildForAbi(
+    override fun configure(
         toolchain: Toolchain,
-        workingDirectory: File,
+        sourceDirectory: File,
         buildDirectory: File,
-        installDirectory: File
-    ) {
-        configure(toolchain, workingDirectory, buildDirectory, installDirectory)
-        build(buildDirectory)
-        install(buildDirectory)
-    }
-
-    private fun configure(
-        toolchain: Toolchain,
-        workingDirectory: File,
-        buildDirectory: File,
-        installDirectory: File
-    ) {
+        installDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> {
         val cpuFamily = when (toolchain.abi) {
             Abi.Arm -> "arm"
             Abi.Arm64 -> "aarch64"
@@ -66,8 +49,7 @@
         }
 
         val crossFile = workingDirectory.resolve("cross_file.txt").apply {
-            writeText(
-                """
+            writeText("""
             [binaries]
             ar = '${toolchain.ar}'
             c = '${toolchain.clang}'
@@ -79,11 +61,10 @@
             cpu_family = '$cpuFamily'
             cpu = '$cpu'
             endian = 'little'
-            """.trimIndent()
-            )
+            """.trimIndent())
         }
 
-        executeSubprocess(
+        return executeProcessStep(
             listOf(
                 "meson",
                 "--cross-file",
@@ -93,16 +74,23 @@
                 "--prefix",
                 installDirectory.absolutePath,
                 "--default-library",
-                defaultLibraryType.get().argument,
-                sourceDirectory.get().asFile.absolutePath,
+                defaultLibraryType.argument,
+                sourceDirectory.absolutePath,
                 buildDirectory.absolutePath
             ), workingDirectory
         )
     }
 
-    private fun build(buildDirectory: File) =
-        executeSubprocess(listOf("ninja", "-v"), buildDirectory)
+    override fun build(
+        toolchain: Toolchain,
+        buildDirectory: File
+    ): Result<Unit, String> =
+        executeProcessStep(listOf("ninja", "-v"), buildDirectory)
 
-    private fun install(buildDirectory: File) =
-        executeSubprocess(listOf("ninja", "-v", "install"), buildDirectory)
+    override fun install(
+        toolchain: Toolchain,
+        buildDirectory: File,
+        installDirectory: File
+    ): Result<Unit, String> =
+        executeProcessStep(listOf("ninja", "-v", "install"), buildDirectory)
 }
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/Ndk.kt b/src/main/kotlin/com/android/ndkports/Ndk.kt
similarity index 94%
rename from buildSrc/src/main/kotlin/com/android/ndkports/Ndk.kt
rename to src/main/kotlin/com/android/ndkports/Ndk.kt
index 7ccd255..af66cb5 100644
--- a/buildSrc/src/main/kotlin/com/android/ndkports/Ndk.kt
+++ b/src/main/kotlin/com/android/ndkports/Ndk.kt
@@ -17,6 +17,7 @@
 package com.android.ndkports
 
 import java.io.File
+import java.lang.RuntimeException
 
 class Ndk(val path: File) {
     val version = NdkVersion.fromNdk(path)
@@ -35,5 +36,4 @@
 
     private val toolchainDirectory = llvmBaseDir.resolve(hostTag)
     val toolchainBinDirectory = toolchainDirectory.resolve("bin")
-    val sysrootDirectory = toolchainDirectory.resolve("sysroot")
 }
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/NdkVersion.kt b/src/main/kotlin/com/android/ndkports/NdkVersion.kt
similarity index 100%
rename from buildSrc/src/main/kotlin/com/android/ndkports/NdkVersion.kt
rename to src/main/kotlin/com/android/ndkports/NdkVersion.kt
diff --git a/src/main/kotlin/com/android/ndkports/Port.kt b/src/main/kotlin/com/android/ndkports/Port.kt
new file mode 100644
index 0000000..2e0433f
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Port.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import java.io.File
+
+@Suppress("unused")
+fun executeProcessStep(
+    args: List<String>,
+    workingDirectory: File,
+    additionalEnvironment: Map<String, String>? = null
+): Result<Unit, String> {
+    val pb = ProcessBuilder(args)
+        .redirectOutput(ProcessBuilder.Redirect.INHERIT)
+        .redirectError(ProcessBuilder.Redirect.INHERIT)
+        .directory(workingDirectory)
+
+    if (additionalEnvironment != null) {
+        pb.environment().putAll(additionalEnvironment)
+    }
+
+    return pb.start()
+        .waitFor().let {
+            if (it == 0) {
+                Result.Ok(Unit)
+            } else {
+                Result.Error("Process failed with exit code $it")
+            }
+        }
+}
+
+fun installDirectoryForPort(
+    name: String,
+    workingDirectory: File,
+    toolchain: Toolchain
+): File = workingDirectory.parentFile.resolve("$name/install/${toolchain.abi}")
+
+/**
+ * A module exported by the package.
+ *
+ * As currently implemented by ndkports, one module is exactly one library.
+ * Prefab supports header-only libraries, but ndkports does not support these
+ * yet.
+ *
+ * Static libraries are not currently supported by ndkports.
+ *
+ * @property[name] The name of the module. Note that currently the name of the
+ * installed library file must be exactly `lib$name.so`.
+ * @property[includesPerAbi] Set to true if a different set of headers should be
+ * exposed per-ABI. Not currently implemented.
+ * @property[dependencies] A list of other modules required by this module, in
+ * the format described by https://google.github.io/prefab/.
+ */
+data class Module(
+    val name: String,
+    val includesPerAbi: Boolean = false,
+    val dependencies: List<String> = emptyList()
+)
+
+/**
+ * The base class for all ports.
+ */
+abstract class Port {
+    /**
+     * The name of the port. Will be used as the package name in prefab.json.
+     */
+    abstract val name: String
+
+    /**
+     * The version of the package.
+     *
+     * Used as the default for [prefabVersion] and [mavenVersion] when
+     * appropriate.
+     */
+    abstract val version: String
+
+    /**
+     * The version to encode in the prefab.json.
+     *
+     * This version must be compatible with CMake's `find_package` for
+     * config-style packages. This means that it must be one to four decimal
+     * separated integers. No other format is allowed.
+     *
+     * If not set, the default is [version] as interpreted by
+     * [CMakeCompatibleVersion.parse].
+     *
+     * For example, OpenSSL 1.1.1g will set this value to
+     * `CMakeCompatibleVersion(1, 1, 1, 7)`.
+     */
+    open val prefabVersion: CMakeCompatibleVersion
+        get() = CMakeCompatibleVersion.parse(version)
+
+    /**
+     * The version to use for the maven package.
+     *
+     * This field allows versioning the maven package differently from the
+     * package itself, which is sometimes necessary given CMake's strict version
+     * format requirements.
+     *
+     * If not set, the default is [version].
+     *
+     * For example, this could be set to `"$name-$version-alpha-1"` to
+     * distribute an alpha of the package.
+     */
+    open val mavenVersion: String
+        get() = version
+
+    /**
+     * The relative path to the license file of this package.
+     *
+     * This file will be packaged in the AAR in the META-INF directory.
+     */
+    open val licensePath: String = "LICENSE"
+
+    /**
+     * A description of the license (name and URL) for use in the pom.xml.
+     */
+    abstract val license: License
+
+    /**
+     * A list of dependencies for this package.
+     *
+     * For example, curl depends on `listOf("openssl")`.
+     */
+    open val dependencies: List<String> = emptyList()
+
+    /**
+     * A list of modules exported by this package.
+     */
+    abstract val modules: List<Module>
+
+    /**
+     * The number of CPUs available for building.
+     *
+     * May be passed to the build system if required.
+     */
+    protected val ncpus = Runtime.getRuntime().availableProcessors()
+
+    fun run(
+        toolchain: Toolchain,
+        sourceDirectory: File,
+        buildDirectory: File,
+        installDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> {
+        configure(
+            toolchain,
+            sourceDirectory,
+            buildDirectory,
+            installDirectory,
+            workingDirectory
+        ).onFailure { return Result.Error(it) }
+
+        build(toolchain, buildDirectory).onFailure { return Result.Error(it) }
+
+        install(
+            toolchain,
+            buildDirectory,
+            installDirectory
+        ).onFailure { return Result.Error(it) }
+
+        return Result.Ok(Unit)
+    }
+
+    /**
+     * Overridable build step for extracting the source package.
+     *
+     * @param[sourceTarball] The path to the source tarball.
+     * @param[sourceDirectory] The destination directory for the extracted
+     * source.
+     * @param[workingDirectory] The working top-level directory for this
+     * package.
+     * @return A [Result<Unit, String>][Result] describing the result of the
+     * operation. On failure, [Result.Error<String>][Result.Error] containing an
+     * error message is returned.
+     */
+    open fun extractSource(
+        sourceTarball: File,
+        sourceDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> {
+        sourceDirectory.mkdirs()
+        return executeProcessStep(
+            listOf(
+                "tar",
+                "xf",
+                sourceTarball.absolutePath,
+                "--strip-components=1"
+            ), sourceDirectory
+        )
+    }
+
+    /**
+     * Overridable build step for configuring the build.
+     *
+     * Any pre-build steps should be run here.
+     *
+     * In an autoconf build, this runs `configure`.
+     *
+     * @param[toolchain] The toolchain used for this build.
+     * @param[sourceDirectory] The directory containing the extracted package
+     * source.
+     * @param[buildDirectory] The directory to use for building.
+     * @param[installDirectory] The destination directory for this package's
+     * installed headers and libraries.
+     * @param[workingDirectory] The top-level working directory for this
+     * package.
+     * @return A [Result<Unit, String>][Result] describing the result of the
+     * operation. On failure, [Result.Error<String>][Result.Error] containing an
+     * error message is returned.
+     */
+    open fun configure(
+        toolchain: Toolchain,
+        sourceDirectory: File,
+        buildDirectory: File,
+        installDirectory: File,
+        workingDirectory: File
+    ): Result<Unit, String> = Result.Ok(Unit)
+
+    /**
+     * Overridable build step for building the package.
+     *
+     * In an autoconf build, this runs `make`.
+     *
+     * @param[toolchain] The toolchain used for this build.
+     * @param[buildDirectory] The directory to use for building.
+     * @return A [Result<Unit, String>][Result] describing the result of the
+     * operation. On failure, [Result.Error<String>][Result.Error] containing an
+     * error message is returned.
+     */
+    open fun build(
+        toolchain: Toolchain,
+        buildDirectory: File
+    ): Result<Unit, String> = Result.Ok(Unit)
+
+    /**
+     * Overridable build step for installing built artifacts for packaging.
+     *
+     * The install step is expected to install headers and libraries to the
+     * [installDirectory] with the following layout:
+     *
+     * [installDirectory]
+     *     include/
+     *         <package headers>
+     *     lib/
+     *         <package libraries>
+     *
+     * A matching `lib${module.name}.so` must be present in the `lib` directory
+     * for every item in [modules].
+     *
+     * Note that it is expected for all modules to use the same headers. This is
+     * currently the case for all ports currently maintained, but could change
+     * in the future.
+     *
+     * In an autoconf build, this runs `make install`.
+     *
+     * @param[toolchain] The toolchain used for this build.
+     * @param[buildDirectory] The directory containing build artifacts.
+     * @param[installDirectory] The destination directory for this package's
+     * installed headers and libraries.
+     * @return A [Result<Unit, String>][Result] describing the result of the
+     * operation. On failure, [Result.Error<String>][Result.Error] containing an
+     * error message is returned.
+     */
+    open fun install(
+        toolchain: Toolchain,
+        buildDirectory: File,
+        installDirectory: File
+    ): Result<Unit, String> = Result.Ok(Unit)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/android/ndkports/PrefabPackageBuilder.kt b/src/main/kotlin/com/android/ndkports/PrefabPackageBuilder.kt
new file mode 100644
index 0000000..1a58f5a
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/PrefabPackageBuilder.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import com.google.prefab.api.AndroidAbiMetadata
+import com.google.prefab.api.ModuleMetadataV1
+import com.google.prefab.api.PackageMetadataV1
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.stringify
+import org.apache.maven.model.Dependency
+import org.apache.maven.model.Developer
+import org.apache.maven.model.License
+import org.apache.maven.model.Scm
+import org.apache.maven.model.io.DefaultModelWriter
+import org.apache.maven.project.MavenProject
+import org.redundent.kotlin.xml.xml
+import java.io.File
+
+class PrefabPackageBuilder(
+    private val port: Port,
+    private val directory: File,
+    private val sourceDirectory: File,
+    private val publishToMavenLocal: Boolean,
+    private val ndk: Ndk,
+    private val abiToApiMap: Map<Abi, Int>,
+    private val portsByName: Map<String, Port>
+) {
+    private val packageDirectory = directory.resolve("aar")
+    private val prefabDirectory = packageDirectory.resolve("prefab")
+    private val modulesDirectory = prefabDirectory.resolve("modules")
+
+    private val packageComponents = listOf(
+        "com",
+        "android",
+        "ndk",
+        "thirdparty",
+        port.name
+    )
+
+    private val packageName = packageComponents.joinToString(".")
+    private val groupComponents = packageComponents.dropLast(1)
+    private val groupId = groupComponents.joinToString(".")
+    private val artifactId = packageComponents.last()
+
+    private val mavenProject = MavenProject().also {
+        it.name = port.name
+        it.description = "The ndkports AAR for ${port.name}."
+        it.url = "https://android.googlesource.com/platform/tools/ndkports"
+        it.groupId = groupId
+        it.artifactId = artifactId
+        it.version = port.mavenVersion
+        it.packaging = "aar"
+        it.licenses = listOf(
+            License().also { license ->
+                license.name = port.license.name
+                license.url = port.license.url
+                license.distribution = "repo"
+            }
+        )
+        it.developers = listOf(
+            Developer().also { developer ->
+                developer.name = "The Android Open Source Project"
+            }
+        )
+        it.scm = Scm().also { scm ->
+            scm.url = "https://android.googlesource.com/platform/tools/ndkports"
+            scm.connection = "scm:git:https://android.googlesource.com/platform/tools/ndkports"
+        }
+        it.dependencies = port.dependencies.map { depName ->
+            val depPort = portsByName[depName] ?: throw RuntimeException(
+                "${port.name} depends on unknown port: $depName"
+            )
+            Dependency().also { dep ->
+                dep.artifactId = depPort.name
+                dep.groupId = groupId
+                dep.version = depPort.mavenVersion
+                dep.type = "aar"
+                // TODO: Make this an option in the Port.
+                // We currently only have one dependency from curl to OpenSSL,
+                // and that's (from the perspective of the AAR consumer), a
+                // runtime dependency. If we ever have compile dependencies,
+                // we'll want to make it possible for each port to customize its
+                // scope.
+                dep.scope = "runtime"
+            }
+        }
+        // TODO: Issue management?
+    }
+
+    private fun preparePackageDirectory() {
+        if (packageDirectory.exists()) {
+            packageDirectory.deleteRecursively()
+        }
+        modulesDirectory.mkdirs()
+    }
+
+    private fun makePackageMetadata() {
+        prefabDirectory.resolve("prefab.json").writeText(
+            Json.stringify(
+                PackageMetadataV1(
+                    port.name,
+                    schemaVersion = 1,
+                    dependencies = port.dependencies,
+                    version = port.prefabVersion.toString()
+                )
+            )
+        )
+    }
+
+    private fun makeModuleMetadata(module: Module, moduleDirectory: File) {
+        moduleDirectory.resolve("module.json").writeText(
+            Json.stringify(
+                ModuleMetadataV1(
+                    exportLibraries = module.dependencies
+                )
+            )
+        )
+    }
+
+    private fun installLibForAbi(module: Module, abi: Abi, libsDir: File) {
+        val libName = "lib${module.name}.so"
+        val installDirectory = libsDir.resolve("android.${abi.abiName}").apply {
+            mkdirs()
+        }
+
+        directory.resolve("install/$abi/lib/$libName")
+            .copyTo(installDirectory.resolve(libName))
+
+        val api = abiToApiMap.getOrElse(abi) {
+            throw RuntimeException(
+                "No API level specified for ${abi.abiName}"
+            )
+        }
+
+        installDirectory.resolve("abi.json").writeText(
+            Json.stringify(
+                AndroidAbiMetadata(
+                    abi = abi.abiName,
+                    api = api,
+                    ndk = ndk.version.major,
+                    stl = "c++_shared"
+                )
+            )
+        )
+    }
+
+    private fun installLicense() {
+        val src = sourceDirectory.resolve(port.licensePath)
+        val dest = packageDirectory.resolve("META-INF")
+            .resolve(File(port.licensePath).name)
+        src.copyTo(dest)
+    }
+
+    private fun createAndroidManifest() {
+        packageDirectory.resolve("AndroidManifest.xml")
+            .writeText(xml("manifest") {
+                attributes(
+                    "xmlns:android" to "http://schemas.android.com/apk/res/android",
+                    "package" to packageName,
+                    "android:versionCode" to 1,
+                    "android:versionName" to "1.0"
+                )
+
+                "uses-sdk" {
+                    attributes(
+                        "android:minSdkVersion" to 16,
+                        "android:targetSdkVersion" to 29
+                    )
+                }
+            }.toString())
+    }
+
+    private fun createPom(pomFile: File) {
+        DefaultModelWriter().write(pomFile, null, mavenProject.model)
+    }
+
+    private fun installToLocalMaven(archive: File, pomFile: File) {
+        val pb = ProcessBuilder(
+            listOf(
+                "mvn",
+                "install:install-file",
+                "-Dfile=$archive",
+                "-DpomFile=$pomFile"
+            )
+        )
+            .redirectOutput(ProcessBuilder.Redirect.INHERIT)
+            .redirectError(ProcessBuilder.Redirect.INHERIT)
+
+        return pb.start()
+            .waitFor().let {
+                if (it != 0) {
+                    throw RuntimeException(
+                        "Failed to install archive to local maven " +
+                                "repository: $archive $pomFile"
+                    )
+                }
+            }
+    }
+
+    private fun createArchive() {
+        val archive = directory.resolve("${port.name}-${port.mavenVersion}.aar")
+        val pomFile = directory.resolve("${port.name}-${port.mavenVersion}.pom")
+        createZipFromDirectory(archive, packageDirectory)
+        createPom(pomFile)
+        if (publishToMavenLocal) {
+            installToLocalMaven(archive, pomFile)
+        }
+    }
+
+    fun build() {
+        preparePackageDirectory()
+        makePackageMetadata()
+        for (module in port.modules) {
+            val moduleDirectory = modulesDirectory.resolve(module.name).apply {
+                mkdirs()
+            }
+
+            makeModuleMetadata(module, moduleDirectory)
+
+            if (module.includesPerAbi) {
+                TODO()
+            } else {
+                // TODO: Perform sanity check.
+                directory.resolve("install/${Abi.Arm}/include")
+                    .copyRecursively(moduleDirectory.resolve("include"))
+            }
+
+            val libsDir = moduleDirectory.resolve("libs").apply { mkdirs() }
+            for (abi in Abi.values()) {
+                installLibForAbi(module, abi, libsDir)
+            }
+        }
+
+        installLicense()
+
+        createAndroidManifest()
+        createArchive()
+    }
+}
diff --git a/src/main/kotlin/com/android/ndkports/Result.kt b/src/main/kotlin/com/android/ndkports/Result.kt
new file mode 100644
index 0000000..21c203e
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Result.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+sealed class Result<out T, out E> {
+    data class Ok<T>(val value: T) : Result<T, Nothing>()
+    data class Error<E>(val error: E) : Result<Nothing, E>()
+
+    inline fun onSuccess(block: (T) -> Unit): Result<T, E> {
+        if (this is Ok<T>) {
+            block(value)
+        }
+        return this
+    }
+
+    inline fun onFailure(block: (E) -> Unit): Result<T, E> {
+        if (this is Error<E>) {
+            block(error)
+        }
+        return this
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/android/ndkports/Toolchain.kt b/src/main/kotlin/com/android/ndkports/Toolchain.kt
similarity index 94%
rename from buildSrc/src/main/kotlin/com/android/ndkports/Toolchain.kt
rename to src/main/kotlin/com/android/ndkports/Toolchain.kt
index 1293ecb..d42e8d1 100644
--- a/buildSrc/src/main/kotlin/com/android/ndkports/Toolchain.kt
+++ b/src/main/kotlin/com/android/ndkports/Toolchain.kt
@@ -29,8 +29,6 @@
         else -> "$binutilsTriple$api"
     }
 
-    val sysrootLibs = ndk.sysrootDirectory.resolve("usr/lib/$binutilsTriple")
-
     val binDir = ndk.toolchainBinDirectory
     val ar = binDir.resolve("$binutilsTriple-ar")
     val clang = binDir.resolve("$clangTriple-clang")
diff --git a/src/main/kotlin/com/android/ndkports/Zip.kt b/src/main/kotlin/com/android/ndkports/Zip.kt
new file mode 100644
index 0000000..a23ff90
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Zip.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+private fun zipDirectory(name: String, zipOut: ZipOutputStream) {
+    zipOut.putNextEntry(ZipEntry("$name/"))
+    zipOut.closeEntry()
+}
+
+private fun zipFile(file: File, name: String, zipOut: ZipOutputStream) {
+    zipOut.putNextEntry(ZipEntry(name))
+    FileInputStream(file).use {
+        it.copyTo(zipOut)
+    }
+}
+
+private fun zip(file: File, name: String, zipOut: ZipOutputStream) {
+    if (file.isDirectory) {
+        zipDirectory(name, zipOut)
+    } else {
+        zipFile(file, name, zipOut)
+    }
+}
+
+fun createZipFromDirectory(output: File, input: File) {
+    FileOutputStream(output).use { fos ->
+        ZipOutputStream(fos).use { zos ->
+            input.walk().filter { it != input }.forEach {
+                zip(it, it.relativeTo(input).path, zos)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt b/src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt
rename to src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt
diff --git a/buildSrc/src/test/kotlin/com/android/ndkports/NdkVersionTest.kt b/src/test/kotlin/com/android/ndkports/NdkVersionTest.kt
similarity index 100%
rename from buildSrc/src/test/kotlin/com/android/ndkports/NdkVersionTest.kt
rename to src/test/kotlin/com/android/ndkports/NdkVersionTest.kt
diff --git a/src/test/kotlin/com/android/ndkports/ResultTest.kt b/src/test/kotlin/com/android/ndkports/ResultTest.kt
new file mode 100644
index 0000000..0abef18
--- /dev/null
+++ b/src/test/kotlin/com/android/ndkports/ResultTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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 com.android.ndkports
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+class ResultTest {
+    @Test
+    fun `onFailure executes block on failure`() {
+        val result = Result.Error("foo")
+        result.onFailure {
+            assertEquals("foo", it)
+            return
+        }
+        fail()
+    }
+
+    @Test
+    fun `onFailure does not execute block on success`() {
+        val result = Result.Ok(Unit)
+        result.onFailure { fail() }
+    }
+
+    @Test
+    fun `onFailure returns same result object`() {
+        val result = Result.Error(Unit)
+        assertEquals(result, result.onFailure {})
+    }
+
+    @Test
+    fun `onSuccess executes block on success`() {
+        val result = Result.Ok("foo")
+        result.onSuccess {
+            assertEquals("foo", it)
+            return
+        }
+        fail()
+    }
+
+    @Test
+    fun `onSuccess does not execute block on failure`() {
+        val result = Result.Error(Unit)
+        result.onSuccess { fail() }
+    }
+
+    @Test
+    fun `onSuccess returns same result object`() {
+        val result = Result.Ok(Unit)
+        assertEquals(result, result.onSuccess {})
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties
similarity index 100%
rename from buildSrc/src/test/resources/junit-platform.properties
rename to src/test/resources/junit-platform.properties