Initial ndkports implementation. am: dca0d72437 am: 662da5656b am: 4f6ecd7a8a
Change-Id: I36603bcb8b004218fe311894a22842a46606b2f5
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..14e1cbc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,83 @@
+.gradle
+/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# 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
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..e3439a9
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,16 @@
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <option name="RIGHT_MARGIN" value="80" />
+ <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
+ <option name="SOFT_MARGINS" value="80" />
+ <JetCodeStyleSettings>
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+ </JetCodeStyleSettings>
+ <codeStyleSettings language="kotlin">
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+ <option name="WRAP_ON_TYPING" value="0" />
+ </codeStyleSettings>
+ </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </state>
+</component>
\ No newline at end of file
diff --git a/.idea/kotlinCodeInsightSettings.xml b/.idea/kotlinCodeInsightSettings.xml
new file mode 100644
index 0000000..71e404d
--- /dev/null
+++ b/.idea/kotlinCodeInsightSettings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="KotlinCodeInsightWorkspaceSettings">
+ <option name="addUnambiguousImportsOnTheFly" value="true" />
+ <option name="optimizeImportsOnTheFly" value="true" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml
new file mode 100644
index 0000000..a6fe551
--- /dev/null
+++ b/.idea/kotlinScripting.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="KotlinScriptingSettings">
+ <option name="isAutoReloadEnabled" value="true" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..bc8d0a3
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +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_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
new file mode 100644
index 0000000..73193ea
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="IssueNavigationConfiguration">
+ <option name="links">
+ <list>
+ <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/)([^,.<>()"\s]+(?:[.,][^,.<>()"\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>
+ </list>
+ </option>
+ </component>
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a40c3df
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+# ndkports
+
+A collection of Android build scripts for various third-party libraries and the
+tooling to build them.
+
+If you're an Android app developer looking to *consume* these libraries, this is
+probably not what you want. This project builds AARs to be published to Maven.
+You most likely want to use the AAR, not build it yourself.
+
+Note: Gradle support for consuming these artifacts from an AAR is a work in
+progress.
+
+## Ports
+
+Each third-party project is called a "port". Ports consist of a description of
+where to fetch the source, apply any patches needed, build, install, and package
+the library into an AAR.
+
+A port is a subclass of the abstract Kotlin class `com.android.ndkports.Port`.
+Projects define the name and version of the port, the URL to fetch source from,
+a list of modules (libraries) to build, and the build steps.
+
+```kotlin
+abstract class Port {
+ abstract val name: String
+ abstract val version: String
+ abstract val url: String
+
+ open val dependencies: List<String> = emptyList()
+ abstract val modules: List<Module>
+
+ open fun fetchSource(
+ sourceDirectory: File,
+ workingDirectory: File
+ ): Result<Unit, String>
+
+ open fun configure(
+ toolchain: Toolchain,
+ sourceDirectory: File,
+ buildDirectory: File,
+ installDirectory: File,
+ workingDirectory: File
+ ): Result<Unit, String>
+
+ open fun build(
+ toolchain: Toolchain,
+ buildDirectory: File
+ ): Result<Unit, String>
+
+ open fun install(
+ toolchain: Toolchain,
+ buildDirectory: File,
+ installDirectory: File
+ ): Result<Unit, String>
+}
+```
+
+Individual port files are kept in `ports/$name/port.kts`. For example, the cURL
+port is [ports/curl/port.kts](ports/curl/port.kts).
+
+## Building a Port
+
+ndkports requires an NDK to be used for building to be specified on the command
+line as well as a list of packages to build. For example, to build cURL:
+
+```bash
+$ ./gradlew run --args='--ndk /path/to/android-ndk-r20 openssl curl'
+Build output...
+$ find -name '*.aar'
+./out/curl/curl.aar
+./out/openssl/openssl.aar
+```
+
+Note that dependencies currently need to be already built or ordered explicitly.
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..b9a4d11
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,60 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ kotlin("jvm") version "1.3.50"
+ application
+}
+
+group = "com.android"
+version = "1.0.0-SNAPSHOT"
+
+repositories {
+ mavenCentral()
+ jcenter()
+ google()
+ maven(url = "https://dl.bintray.com/s1m0nw1/KtsRunner")
+}
+
+dependencies {
+ implementation(kotlin("stdlib", "1.3.50"))
+ implementation(kotlin("reflect", "1.3.50"))
+
+ implementation("com.google.prefab:api:1.0.0-alpha2")
+
+ implementation("com.github.ajalt:clikt:2.2.0")
+ implementation("com.squareup.okhttp3:okhttp:4.2.2")
+ 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"
+ )
+}
+
+// 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") {
+ val allPorts = File("ports").listFiles()!!.map { it.name }
+ args = listOf("--ndk", ndkPath, "-o", "out") + allPorts
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..29e08e8
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..94336fc
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7c4388a
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+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
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+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=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+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
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# 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
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ 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" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+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
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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=
+
+@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 init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+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 %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/ports/curl/port.kts b/ports/curl/port.kts
new file mode 100644
index 0000000..4a47446
--- /dev/null
+++ b/ports/curl/port.kts
@@ -0,0 +1,57 @@
+/*
+ * 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.66.0"
+ override val url = "https://curl.haxx.se/download/curl-$version.tar.gz"
+ 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"
+ )
+ }
+}
\ No newline at end of file
diff --git a/ports/jsoncpp/port.kts b/ports/jsoncpp/port.kts
new file mode 100644
index 0000000..f951e23
--- /dev/null
+++ b/ports/jsoncpp/port.kts
@@ -0,0 +1,46 @@
+/*
+ * 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.9.1"
+ override val url =
+ "https://github.com/open-source-parsers/jsoncpp/archive/$version.tar.gz"
+
+ 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 fetchSource(
+ sourceDirectory: File,
+ workingDirectory: File
+ ): Result<Unit, String> =
+ super.fetchSource(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()
+ }
+}
\ No newline at end of file
diff --git a/ports/openssl/port.kts b/ports/openssl/port.kts
new file mode 100644
index 0000000..00345bc
--- /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.1d"
+ override val prefabVersion = CMakeCompatibleVersion(1, 1, 1, 4)
+ override val url = "https://www.openssl.org/source/openssl-$version.tar.gz"
+
+ 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")}"
+ )
+ )
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..4549d2e
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,11 @@
+rootProject.name = "ndkports"
+
+pluginManagement {
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "kotlinx-serialization") {
+ useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
+ }
+ }
+ }
+}
\ 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..053fe8d
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/AutoconfPort.kt
@@ -0,0 +1,69 @@
+/*
+ * 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()
+
+ 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 = mapOf(
+ "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")}"
+ )
+ )
+ }
+
+ 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/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt b/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
new file mode 100644
index 0000000..6d3ee45
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/CMakeCompatibleVersion.kt
@@ -0,0 +1,66 @@
+/*
+ * 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
+
+/**
+ * A version number that is compatible with CMake's package version format.
+ *
+ * https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-version-file
+ *
+ * CMake package versions *must* be numeric with a maximum of four dot separated
+ * components.
+ */
+data class CMakeCompatibleVersion(
+ val major: Int,
+ val minor: Int?,
+ val patch: Int?,
+ val tweak: Int?
+) {
+ init {
+ if (tweak != null) {
+ require(patch != null)
+ }
+
+ if (patch != null) {
+ require(minor != null)
+ }
+ }
+
+ override fun toString(): String =
+ listOfNotNull(major, minor, patch, tweak).joinToString(".")
+
+ companion object {
+ private val versionRegex = Regex(
+ """^(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+))?)?)?$"""
+ )
+
+ fun parse(versionString: String): CMakeCompatibleVersion {
+ val match = versionRegex.find(versionString)
+ require(match != null) {
+ "$versionString is not in major[.minor[.patch[.tweak]]] format"
+ }
+ return CMakeCompatibleVersion(
+ match.groups[1]!!.value.toInt(),
+ match.groups[2]?.value?.toInt(),
+ match.groups[3]?.value?.toInt(),
+ match.groups[4]?.value?.toInt()
+ )
+ }
+ }
+}
\ No newline at end of file
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..25d48ed
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Cli.kt
@@ -0,0 +1,129 @@
+/*
+ * 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 loadPort(name: String): Port {
+ val portDir = File("ports").resolve(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")
+
+ port.fetchSource(sourceDirectory, workingDirectory).onFailure {
+ println(it)
+ exitProcess(1)
+ }
+
+ 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/src/main/kotlin/com/android/ndkports/MesonPort.kt b/src/main/kotlin/com/android/ndkports/MesonPort.kt
new file mode 100644
index 0000000..8c1f706
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/MesonPort.kt
@@ -0,0 +1,96 @@
+/*
+ * 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 MesonPort : Port() {
+ enum class DefaultLibraryType(val argument: String) {
+ Both("both"),
+ Shared("shared"),
+ Static("static")
+ }
+
+ open val defaultLibraryType: DefaultLibraryType = DefaultLibraryType.Shared
+
+ override fun configure(
+ toolchain: Toolchain,
+ sourceDirectory: File,
+ buildDirectory: File,
+ installDirectory: File,
+ workingDirectory: File
+ ): Result<Unit, String> {
+ val cpuFamily = when (toolchain.abi) {
+ Abi.Arm -> "arm"
+ Abi.Arm64 -> "aarch64"
+ Abi.X86 -> "x86"
+ Abi.X86_64 -> "x86_64"
+ }
+
+ val cpu = when (toolchain.abi) {
+ Abi.Arm -> "armv7a"
+ Abi.Arm64 -> "armv8a"
+ Abi.X86 -> "i686"
+ Abi.X86_64 -> "x86_64"
+ }
+
+ val crossFile = workingDirectory.resolve("cross_file.txt").apply {
+ writeText("""
+ [binaries]
+ ar = '${toolchain.ar}'
+ c = '${toolchain.clang}'
+ cpp = '${toolchain.clangxx}'
+ strip = '${toolchain.strip}'
+
+ [host_machine]
+ system = 'android'
+ cpu_family = '$cpuFamily'
+ cpu = '$cpu'
+ endian = 'little'
+ """.trimIndent())
+ }
+
+ return executeProcessStep(
+ listOf(
+ "meson",
+ "--cross-file",
+ crossFile.absolutePath,
+ "--buildtype",
+ "release",
+ "--prefix",
+ installDirectory.absolutePath,
+ "--default-library",
+ defaultLibraryType.argument,
+ sourceDirectory.absolutePath,
+ buildDirectory.absolutePath
+ ), workingDirectory
+ )
+ }
+
+ override fun build(
+ toolchain: Toolchain,
+ buildDirectory: File
+ ): Result<Unit, String> =
+ executeProcessStep(listOf("ninja", "-v"), 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/src/main/kotlin/com/android/ndkports/Ndk.kt b/src/main/kotlin/com/android/ndkports/Ndk.kt
new file mode 100644
index 0000000..af66cb5
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Ndk.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.lang.RuntimeException
+
+class Ndk(val path: File) {
+ val version = NdkVersion.fromNdk(path)
+
+ private val llvmBaseDir = path.resolve("toolchains/llvm/prebuilt")
+ val hostTag: String = llvmBaseDir.let {
+ val files = it.list()
+ ?: throw RuntimeException("Unable to get file list for $it")
+
+ if (files.size != 1) {
+ throw RuntimeException("Expected exactly one directory in $it")
+ }
+
+ files.first()
+ }
+
+ private val toolchainDirectory = llvmBaseDir.resolve(hostTag)
+ val toolchainBinDirectory = toolchainDirectory.resolve("bin")
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/android/ndkports/NdkVersion.kt b/src/main/kotlin/com/android/ndkports/NdkVersion.kt
new file mode 100644
index 0000000..fce1304
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/NdkVersion.kt
@@ -0,0 +1,58 @@
+/*
+ * 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
+
+data class NdkVersion(
+ val major: Int,
+ val minor: Int,
+ val build: Int,
+ val qualifier: String?
+) {
+ companion object {
+ private val pkgRevisionRegex = Regex("""^Pkg.Revision\s*=\s*(\S+)$""")
+ private val versionRegex = Regex("""^(\d+).(\d+).(\d+)(?:-(\S+))?$""")
+
+ private fun fromString(versionString: String): NdkVersion {
+ val match = versionRegex.find(versionString)
+ require(match != null) { "Invalid version string" }
+ val (major, minor, build, qualifier) = match.destructured
+ return NdkVersion(
+ major.toInt(),
+ minor.toInt(),
+ build.toInt(),
+ qualifier.takeIf { match.groups[4] != null }
+ )
+ }
+
+ fun fromSourcePropertiesText(text: String): NdkVersion {
+ for (line in text.lines().map { it.trim() }) {
+ pkgRevisionRegex.find(line)?.let {
+ return fromString(it.groups.last()!!.value)
+ }
+ }
+ throw RuntimeException(
+ "Did not find Pkg.Revision in source.properties"
+ )
+ }
+
+ fun fromNdk(ndk: File): NdkVersion = fromSourcePropertiesText(
+ ndk.resolve("source.properties").readText()
+ )
+ }
+}
\ No newline at end of file
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..08c8fbf
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Port.kt
@@ -0,0 +1,158 @@
+/*
+ * 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 okhttp3.OkHttpClient
+import okhttp3.Request
+import java.io.File
+import java.io.FileOutputStream
+
+@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}")
+
+data class Module(
+ val name: String,
+ val includesPerAbi: Boolean = false,
+ val dependencies: List<String> = emptyList()
+)
+
+abstract class Port {
+ abstract val name: String
+ abstract val version: String
+ open val prefabVersion: CMakeCompatibleVersion
+ get() = CMakeCompatibleVersion.parse(version)
+ open val mavenVersion: String
+ get() = version
+
+ abstract val url: String
+
+ open val licensePath: String = "LICENSE"
+
+ abstract val license: License
+
+ open val dependencies: List<String> = emptyList()
+ abstract val modules: List<Module>
+
+ 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)
+ }
+
+ open fun fetchSource(
+ sourceDirectory: File,
+ workingDirectory: File
+ ): Result<Unit, String> {
+ val file = workingDirectory.resolve(File(url).name)
+
+ val client = OkHttpClient()
+ val request = Request.Builder().url(url).build()
+ client.newCall(request).execute().use { response ->
+ if (!response.isSuccessful) {
+ return Result.Error("Failed to download $url")
+ }
+
+ val body = response.body ?: throw RuntimeException(
+ "Expected non-null response body for $url"
+ )
+ FileOutputStream(file).use { output ->
+ body.byteStream().use { input ->
+ input.copyTo(output)
+ }
+ }
+ }
+
+ sourceDirectory.mkdirs()
+ return executeProcessStep(
+ listOf(
+ "tar",
+ "xf",
+ file.absolutePath,
+ "--strip-components=1"
+ ), sourceDirectory
+ )
+ }
+
+ open fun configure(
+ toolchain: Toolchain,
+ sourceDirectory: File,
+ buildDirectory: File,
+ installDirectory: File,
+ workingDirectory: File
+ ): Result<Unit, String> = Result.Ok(Unit)
+
+ open fun build(
+ toolchain: Toolchain,
+ buildDirectory: File
+ ): Result<Unit, String> = Result.Ok(Unit)
+
+ 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..cac9acd
--- /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}.aar")
+ val pomFile = directory.resolve("${port.name}.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/src/main/kotlin/com/android/ndkports/Toolchain.kt b/src/main/kotlin/com/android/ndkports/Toolchain.kt
new file mode 100644
index 0000000..d42e8d1
--- /dev/null
+++ b/src/main/kotlin/com/android/ndkports/Toolchain.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+class Toolchain(val ndk: Ndk, val abi: Abi, val api: Int) {
+ val binutilsTriple = when (abi) {
+ Abi.Arm -> "arm-linux-androideabi"
+ Abi.Arm64 -> "aarch64-linux-android"
+ Abi.X86 -> "i686-linux-android"
+ Abi.X86_64 -> "x86_64-linux-android"
+ }
+
+ private val clangTriple = when (abi) {
+ Abi.Arm -> "armv7a-linux-androideabi$api"
+ else -> "$binutilsTriple$api"
+ }
+
+ val binDir = ndk.toolchainBinDirectory
+ val ar = binDir.resolve("$binutilsTriple-ar")
+ val clang = binDir.resolve("$clangTriple-clang")
+ val clangxx = binDir.resolve("$clangTriple-clang++")
+ val ranlib = binDir.resolve("$binutilsTriple-ranlib")
+ val strip = binDir.resolve("$binutilsTriple-strip")
+}
\ No newline at end of file
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/src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt b/src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt
new file mode 100644
index 0000000..7724a8e
--- /dev/null
+++ b/src/test/kotlin/com/android/ndkports/CMakeCompatibleVersionTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.junit.jupiter.api.assertThrows
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class CMakeCompatibleVersionTest {
+ @Test
+ fun `can parse version number`() {
+ assertEquals(
+ CMakeCompatibleVersion(1, null, null, null),
+ CMakeCompatibleVersion.parse("1")
+ )
+ assertEquals(
+ CMakeCompatibleVersion(2, 1, null, null),
+ CMakeCompatibleVersion.parse("2.1")
+ )
+ assertEquals(
+ CMakeCompatibleVersion(3, 2, 1, null),
+ CMakeCompatibleVersion.parse("3.2.1")
+ )
+ assertEquals(
+ CMakeCompatibleVersion(4, 3, 2, 1),
+ CMakeCompatibleVersion.parse("4.3.2.1")
+ )
+ }
+
+ @Test
+ fun `reject invalid versions`() {
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(" ")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("1.")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(".1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(".1.")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(" 1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("1 ")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(" 1 ")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("2.1.")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(".2.1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(".2.1.")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("1a")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("2b.1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("5.4.3.2.1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("4.3.2.1a")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("2.a.1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("3. .1")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("1..2")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse(".")
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion.parse("...")
+ }
+ }
+
+ @Test
+ fun `constructor requires that nulls come last`() {
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion(1, 2, null, 3)
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion(1, null, 2, 3)
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion(1, null, 2, null)
+ }
+ assertThrows<IllegalArgumentException> {
+ CMakeCompatibleVersion(1, null, null, 2)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/android/ndkports/NdkVersionTest.kt b/src/test/kotlin/com/android/ndkports/NdkVersionTest.kt
new file mode 100644
index 0000000..750cc52
--- /dev/null
+++ b/src/test/kotlin/com/android/ndkports/NdkVersionTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.junit.jupiter.api.assertThrows
+import java.lang.RuntimeException
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class NdkVersionTest {
+ @Test
+ fun `can parse source properties`() {
+ assertEquals(
+ NdkVersion(20, 0, 5594570, null),
+ NdkVersion.fromSourcePropertiesText(
+ """
+ Pkg.Desc = Android NDK
+ Pkg.Revision = 20.0.5594570
+ """.trimIndent()
+ )
+ )
+
+ assertEquals(
+ NdkVersion(20, 0, 5594570, "canary"),
+ NdkVersion.fromSourcePropertiesText(
+ """
+ Pkg.Revision = 20.0.5594570-canary
+ Pkg.Desc = Android NDK
+ """.trimIndent()
+ )
+ )
+
+ assertEquals(
+ NdkVersion(20, 0, 5594570, "beta2"),
+ NdkVersion.fromSourcePropertiesText(
+ """
+
+ Pkg.Revision = 20.0.5594570-beta2
+ Pkg.Desc = Android NDK
+
+ """.trimIndent()
+ )
+ )
+ assertEquals(
+ NdkVersion(20, 0, 5594570, "rc1"),
+ NdkVersion.fromSourcePropertiesText(
+ """
+ Pkg.Desc = Android NDK
+
+
+
+ Pkg.Revision = 20.0.5594570-rc1
+ """.trimIndent()
+ )
+ )
+ }
+
+ @Test
+ fun `fails if not found`() {
+ assertThrows<RuntimeException> {
+ NdkVersion.fromSourcePropertiesText("Pkg.Desc = Android NDK")
+ }
+ }
+}
\ No newline at end of file
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/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000..d265fd8
--- /dev/null
+++ b/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.testinstance.lifecycle.default = per_class