Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Native for CLIs

Kotlin Native for CLIs

When making Command Line Interfaces (CLIs) it sure would be nice to use everyone's favorite language, but we also have to think about things like distribution and portability. While we can zip up our jars and assets, ask the user to extract them somewhere, and write a shell script to configure the JVM, classpath, and launch our Jar, there are also other options that allow us to write in Kotlin, while also building tools that look and feel just like any other on a user's system.

When I first wrote Differ, a Kotlin Multiplatform library for image comparison, the sample app was a Kotlin JVM app like any other, which exercised the library by letting you provide two images for comparison and printing the results to STDOUT. It soon became apparent that the sample app would also make a useful CLI, but JVM based CLIs involve quite a bit of overhead to distribute and run. Since Differ is a multiplatform library, Kotlin Native was a straightforward solution to make a CLI written in Kotlin which looks and feels just like any other native tool on a developer machine.

In this talk we'll discuss situations in which Kotlin Native can be a great tool for building CLIs, some of the downsides of using Kotlin Native over other tools, and some of the tools and libraries that can help. Using real world examples, we'll explore how you can build, package and run CLIs just like other tools installed on your system. After this talk, you'll be prepared to use Kotlin Native to create your own useful, portable, and testable CLIs that work just like any other utility.

Ryan Harter

April 14, 2023
Tweet

More Decks by Ryan Harter

Other Decks in Technology

Transcript

  1. • Kotlin Multiplatform image dif f ing library • Like

    diff, but for images • Comparator backing Dropshots Differ
  2. Differ interface ImageComparator { data class ComparisonResult( val pixelDifferences: Int,

    val pixelCount: Int, val width: Int, val height: Int, ) fun compare(left: Image, right: Image, diff: Mask? = null): ComparisonResult }
  3. / / Common interface Image { val width: Int val

    height: Int fun getPixel(x: Int, y: Int): Color } Differ
  4. / / JVM private class BufferedImageWrapper( private val delegate: BufferedImage

    ) : Image { override val width: Int = delegate.width override val height: Int = delegate.height override fun getPixel(x: Int, y: Int): Color = delegate.getRGB(x, y).let( :: Color) } Differ
  5. private class BufferedImageWrapper( / / Android internal class BitmapImage( private

    val src: Bitmap ) : Image { override val width: Int get() = src.width override val height: Int get() = src.height override fun getPixel(x: Int, y: Int): Color = Color(src.getPixel(x, y)) }
  6. Differ ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Images are

    different 222407 pixels are different Time: 338ms Differences: 222407 (8.044234%) FAIL ⬢[rharter@toolbox differ]$
  7. Differ ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Images are

    different 222407 pixels are different Time: 338ms Differences: 222407 (8.044234%) FAIL ⬢[rharter@toolbox differ]$
  8. Differ ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Images are

    different 222407 pixels are different Time: 338ms Differences: 222407 (8.044234%) FAIL ⬢[rharter@toolbox differ]$ Time: 338ms
  9. • Can we make it feel native? • How do

    we handle dependencies? Single binary? • Can we make distribution easier? • Can we get better performance? Kotlin/Native for CLIs?
  10. kotlin { jvm { withJava() } macosX64() macosArm64() linuxX64() sourceSets

    { val commonMain by getting { dependencies { implementation(libs.kotlinx.cli) implementation(libs.kotlinx.io) implementation(project(":differ")) } application { } } } }
  11. dependencies { implementation(libs.kotlinx.cli) implementation(libs.kotlinx.io) implementation(project(":differ")) } } application { mainClass.set("com.dropbox.differ.cli.MainKt")

    } } } targets.withType<KotlinNativeTarget> { binaries { executable { baseName = "differ" entryPoint = "com.dropbox.differ.cli.main" } } }
  12. single- f ile public domain (or MIT licensed) libraries for

    C/C++ stb_image https://github.com/nothings/stb
  13. # stbimage.def headers = stb_image.h headerFilter = stb_image.h package =

    stb.image - -- #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" stb_image https://github.com/nothings/stb
  14. # stbimage.def headers = stb_image.h headerFilter = stb_image.h package =

    stb.image - -- #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" headers = stb_image.h stb_image https://github.com/nothings/stb
  15. # stbimage.def headers = stb_image.h headerFilter = stb_image.h package =

    stb.image - -- #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" headerFilter = stb_image.h stb_image https://github.com/nothings/stb
  16. # stbimage.def headers = stb_image.h headerFilter = stb_image.h package =

    stb.image - -- #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" package = stb.image stb_image https://github.com/nothings/stb
  17. # stbimage.def headers = stb_image.h headerFilter = stb_image.h package =

    stb.image - -- #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" - -- stb_image https://github.com/nothings/stb
  18. # stbimage.def headers = stb_image.h headerFilter = stb_image.h package =

    stb.image - -- #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" stb_image https://github.com/nothings/stb
  19. / * stb_image - v2.27 - public domain image loader

    - http: // nothings.org/stb no warranty implied; use at your own risk Do this: #define STB_IMAGE_IMPLEMENTATION before you include this file in *one* C or C ++ file to create the implementation. / / i.e. it should look like this: #include .. . #include .. . #include .. . #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free stb_image https://github.com/nothings/stb
  20. / / Basic usage (see HDR discussion below for HDR

    usage): / / int x,y,n; / / unsigned char *data = stbi_load(filename, &x, &y, &n, 0); / / // ... process data if not NULL ... / / // ... x = width, y = height, n = # 8-bit components per pixel ... / / // ... replace '0' with '1' .. '4' to force that many components per pixel / / // ... but 'n' will always be the number that it would have been if you said 0 / / stbi_image_free(data) stb_image https://github.com/nothings/stb
  21. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int stb_image https://github.com/nothings/stb
  22. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * stb_image https://github.com/nothings/stb
  23. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * init { } stb_image https://github.com/nothings/stb
  24. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * init { } val data = stbi_load(filePath, ? ? ? , , , 0) stb_image https://github.com/nothings/stb
  25. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * init { } pointer = data ?. reinterpret() ?: throw Exception("Failed to read image from file: $filePath") val data = stbi_load(filePath, ? ? ? , , , 0) stb_image https://github.com/nothings/stb
  26. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * init { } pointer = data ?. reinterpret() ?: throw Exception("Failed to read image from file: $filePath") val data = stbi_load(filePath, ? ? ? , , , 0) stb_image https://github.com/nothings/stb
  27. class StbImage internal constructor(filePath: String) : Image { } override

    val width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * init { } pointer = data ?. reinterpret() ?: throw Exception("Failed to read image from file: $filePath") val arena = Arena() try { } finally { arena.clear() } val data = stbi_load(filePath, ? ? ? , , , 0) stb_image https://github.com/nothings/stb
  28. } private val pointer: CPointer<UByteVar> / / unsigned char *

    init { } pointer = data ?. reinterpret() ?: throw Exception("Failed to read image from file: $filePath") val arena = Arena() try { } finally { arena.clear() } val x = arena.alloc<IntVar>() val y = arena.alloc<IntVar>() val n = arena.alloc<IntVar>() val data = stbi_load(filePath, ? ? ? , , , 0) stb_image https://github.com/nothings/stb
  29. } private val pointer: CPointer<UByteVar> / / unsigned char *

    init { } val data = stbi_load(filePath, pointer = data ?. reinterpret() ?: throw Exception("Failed to read image from file: $filePath") val arena = Arena() try { } finally { arena.clear() } val x = arena.alloc<IntVar>() val y = arena.alloc<IntVar>() val n = arena.alloc<IntVar>() , , , 0) x.ptr y.ptr n.ptr stb_image https://github.com/nothings/stb
  30. } } val data = stbi_load(filePath, pointer = data ?.

    reinterpret() ?: throw Exception("Failed to read image from file: $filePath") } finally { arena.clear() } val x = arena.alloc<IntVar>() val y = arena.alloc<IntVar>() val n = arena.alloc<IntVar>() , , , 0) x.ptr y.ptr n.ptr width = x.value height = y.value channels = n.value stb_image https://github.com/nothings/stb
  31. } } ?: throw Exception("Failed to read image from file:

    $filePath") } finally { arena.clear() } width = x.value height = y.value channels = n.value override fun getPixel(x: Int, y: Int): Color { } stb_image https://github.com/nothings/stb
  32. } } ?: throw Exception("Failed to read image from file:

    $filePath") } finally { arena.clear() } width = x.value height = y.value channels = n.value override fun getPixel(x: Int, y: Int): Color { } val pixelOffset = (x + y * width) * channels stb_image https://github.com/nothings/stb
  33. } } } finally { arena.clear() } height = y.value

    channels = n.value override fun getPixel(x: Int, y: Int): Color { } val pixelOffset = (x + y * width) * channels return Color( r = pointer[pixelOffset + 0], g = pointer[pixelOffset + 1], b = pointer[pixelOffset + 2], a = pointer[pixelOffset + 3], ) stb_image https://github.com/nothings/stb
  34. } } val pixelOffset = (x + y * width)

    * channels return Color( r = pointer[pixelOffset + 0], g = pointer[pixelOffset + 1], b = pointer[pixelOffset + 2], a = pointer[pixelOffset + 3], ) stb_image https://github.com/nothings/stb fun close() { stbi_image_free(pointer) }
  35. class StbImage internal constructor(filePath: String) : Image { override val

    width: Int override val height: Int private val pointer: CPointer<UByteVar> / / unsigned char * init { val data = stbi_load(filePath, pointer = data ?. reinterpret() ?: throw Exception("Failed to read image from file: $filePath") val arena = Arena() try { val x = arena.alloc<IntVar>() val y = arena.alloc<IntVar>() val n = arena.alloc<IntVar>() , , , 0) x.ptr y.ptr n.ptr width = x.value height = y.value stb_image https://github.com/nothings/stb
  36. libpng http://www.libpng.org/ # libpng.def headers = png.h headerFilter = png.h

    package = libpng compilerOpts.osx = -I/usr/local/include -I/opt/homebrew/include/libpng16 compilerOpts.linux = -I/usr/include/libpng -I/usr/include/libpng16 linkerOpts.osx = -L/usr/local/lib -L/opt/homebrew/lib -lpng linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lpng
  37. libpng http://www.libpng.org/ # libpng.def headers = png.h headerFilter = png.h

    package = libpng compilerOpts.osx = -I/usr/local/include -I/opt/homebrew/include/libpng16 compilerOpts.linux = -I/usr/include/libpng -I/usr/include/libpng16 linkerOpts.osx = -L/usr/local/lib -L/opt/homebrew/lib -lpng linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lpng headers = png.h headerFilter = png.h package = libpng
  38. libpng http://www.libpng.org/ # libpng.def headers = png.h headerFilter = png.h

    package = libpng compilerOpts.osx = -I/usr/local/include -I/opt/homebrew/include/libpng16 compilerOpts.linux = -I/usr/include/libpng -I/usr/include/libpng16 linkerOpts.osx = -L/usr/local/lib -L/opt/homebrew/lib -lpng linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lpng compilerOpts.osx = -I/usr/local/include -I/opt/homebrew/include/libpng16 compilerOpts.linux = -I/usr/include/libpng -I/usr/include/libpng16
  39. libpng http://www.libpng.org/ # libpng.def headers = png.h headerFilter = png.h

    package = libpng compilerOpts.osx = -I/usr/local/include -I/opt/homebrew/include/libpng16 compilerOpts.linux = -I/usr/include/libpng -I/usr/include/libpng16 linkerOpts.osx = -L/usr/local/lib -L/opt/homebrew/lib -lpng linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lpng linkerOpts.osx = -L/usr/local/lib -L/opt/homebrew/lib -lpng linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lpng
  40. class PNGImage(val filePath: String) : Image { private val arena

    = Arena() private val data: CPointer<UByteVar> override val width: Int override val height: Int init { val loadArena = Arena() try { val realPath = memScoped { val tmp = allocArray<ByteVar>(PATH_MAX) realpath(filePath, tmp) tmp.toKString() } val fp = fopen(realPath, "rb") ?: error("Failed to open file pointer at $realPath") libpng http://www.libpng.org/
  41. private fun getChannel(x: Int, y: Int, channel: Int): UByte {

    val pixelOffset = (x + y * width) * 4 return data[pixelOffset + channel] } override fun getPixel(x: Int, y: Int): Color = Color( r = getChannel(x, y, 0), g = getChannel(x, y, 0), b = getChannel(x, y, 0), a = getChannel(x, y, 0), ) fun close() { arena.clear() } } libpng http://www.libpng.org/
  42. class PNGImage(val filePath: String) : Image { private val arena

    = Arena() private val data: CPointer<UByteVar> override val width: Int override val height: Int init { val loadArena = Arena() try { val realPath = memScoped { val tmp = allocArray<ByteVar>(PATH_MAX) realpath(filePath, tmp) tmp.toKString() } val fp = fopen(realPath, "rb") ?: error("Failed to open file pointer at $realPath") libpng http://www.libpng.org/
  43. ImageIO javax.image private class BufferedImageWrapper( override fun getPixel(x: Int, y:

    Int): Color = delegate.getRGB(x, y).let( :: Color) ) : Image { private val delegate: BufferedImage / / JVM override val width: Int = delegate.width override val height: Int = delegate.height }
  44. ImageIO javax.image private class BufferedImageWrapper( override fun getPixel(x: Int, y:

    Int): Color = delegate.getRGB(x, y).let( :: Color) ) : Image { private val delegate: BufferedImage / / JVM override val width: Int = delegate.width override val height: Int = delegate.height } BufferedImage
  45. ImageIO javax.image private class BufferedImageWrapper( override fun getPixel(x: Int, y:

    Int): Color = delegate.getRGB(x, y).let( :: Color) ) : Image { private val delegate: BufferedImage / / JVM override val width: Int = delegate.width override val height: Int = delegate.height }
  46. ImageIO javax.image private class BufferedImageWrapper( override fun getPixel(x: Int, y:

    Int): Color = delegate.getRGB(x, y).let( :: Color) ) : Image { private val delegate: BufferedImage / / JVM override val width: Int = delegate.width override val height: Int = delegate.height } fun Image(filePath: String): Image = BufferedImageWrapper(ImageIO.read(File(filePath)))
  47. ImageIO javax.image private class BufferedImageWrapper( override fun getPixel(x: Int, y:

    Int): Color = delegate.getRGB(x, y).let( :: Color) ) : Image { private val delegate: BufferedImage / / JVM override val width: Int = delegate.width override val height: Int = delegate.height } fun Image(filePath: String): Image = BufferedImageWrapper(ImageIO.read(File(filePath))) ImageIO.read(File(filePath))
  48. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting

    to load first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 96ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 95ms Starting comparison Finished comparison in 1609ms Images are different 222407 pixels are different Time: 1811ms Differences: 222407 (8.044234%) FAIL
  49. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting

    to load first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 96ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 95ms Starting comparison Finished comparison in 1609ms Images are different 222407 pixels are different Time: 1811ms Differences: 222407 (8.044234%) FAIL Loaded ./differ/src/commonTest/resources/cat.png in 96ms Loaded ./differ/src/commonTest/resources/cat-bad.png in 95ms
  50. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting

    to load first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 96ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 95ms Starting comparison Finished comparison in 1609ms Images are different 222407 pixels are different Time: 1811ms Differences: 222407 (8.044234%) FAIL Loaded ./differ/src/commonTest/resources/cat.png in 96ms Loaded ./differ/src/commonTest/resources/cat-bad.png in 95ms Finished comparison in 1609ms
  51. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load

    first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 43ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 43ms Starting comparison Finished comparison in 1602ms Images are different 149973 pixels are different Time: 1712ms Differences: 149973 (5.424371%) FAIL libpng http://www.libpng.org/
  52. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load

    first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 43ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 43ms Starting comparison Finished comparison in 1602ms Images are different 149973 pixels are different Time: 1712ms Differences: 149973 (5.424371%) FAIL libpng http://www.libpng.org/ Loaded ./differ/src/commonTest/resources/cat.png in 43ms Loaded ./differ/src/commonTest/resources/cat-bad.png in 43ms
  53. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load

    first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 43ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 43ms Starting comparison Finished comparison in 1602ms Images are different 149973 pixels are different Time: 1712ms Differences: 149973 (5.424371%) FAIL libpng http://www.libpng.org/ Loaded ./differ/src/commonTest/resources/cat.png in 43ms Loaded ./differ/src/commonTest/resources/cat-bad.png in 43ms Finished comparison in 1602ms
  54. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load

    first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 136ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 77ms Starting comparison Finished comparison in 188ms Images are different 222407 pixels are different Time: 407ms Differences: 222407 (8.044234%) FAIL ImageIO javax.image
  55. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load

    first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 136ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 77ms Starting comparison Finished comparison in 188ms Images are different 222407 pixels are different Time: 407ms Differences: 222407 (8.044234%) FAIL ImageIO javax.image Loaded ./differ/src/commonTest/resources/cat.png in 136ms Loaded ./differ/src/commonTest/resources/cat-bad.png in 77ms
  56. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load

    first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 136ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 77ms Starting comparison Finished comparison in 188ms Images are different 222407 pixels are different Time: 407ms Differences: 222407 (8.044234%) FAIL ImageIO javax.image Loaded ./differ/src/commonTest/resources/cat.png in 136ms Loaded ./differ/src/commonTest/resources/cat-bad.png in 77ms Finished comparison in 188ms
  57. Performance Static Dynamic JVM ImageIO stb_image libpng Load: 95ms Comparison:

    1609ms Load: 43ms Comparison: 1602ms Load: ~110ms Comparison: 188ms
  58. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ

    |====================================================>| 100% ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png
  59. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ

    |====================================================>| 100% ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 96ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 95ms Starting comparison Finished comparison in 1609ms Images are different 222407 pixels are different Time: 1811ms Differences: 222407 (8.044234%)
  60. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-macosArm64

    ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-macosX86 or
  61. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-macosArm64

    ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-macosX86 or ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-linuxX64 or
  62. stb_image https://github.com/nothings/stb ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-macosArm64

    ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-macosX86 or ⬢[rharter@toolbox differ]$ curl -o ./differ https: // example.com/differ-linuxX64 or or
  63. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png ./differ: error while

    loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory ⬢[rharter@toolbox differ]$ libpng http://www.libpng.org/
  64. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png ./differ: error while

    loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory ⬢[rharter@toolbox differ]$ libpng http://www.libpng.org/ sudo dnf install -y libpng
  65. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png ./differ: error while

    loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory ⬢[rharter@toolbox differ]$ libpng http://www.libpng.org/ Doing things and working hard ... sudo dnf install -y libpng
  66. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png ./differ: error while

    loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory ⬢[rharter@toolbox differ]$ libpng http://www.libpng.org/ sudo dnf install -y libpng Doing things and working hard ... ⬢[rharter@toolbox differ]$
  67. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png ./differ: error while

    loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory ⬢[rharter@toolbox differ]$ libpng http://www.libpng.org/ sudo dnf install -y libpng Doing things and working hard ... ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png
  68. ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png ./differ: error while

    loading shared libraries: libpng16.so.16: cannot open shared object file: No such file or directory ⬢[rharter@toolbox differ]$ libpng http://www.libpng.org/ sudo dnf install -y libpng Doing things and working hard ... ⬢[rharter@toolbox differ]$ ./differ -- verbose cat.png cat-bad.png Starting to load first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 43ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 43ms Starting comparison Finished comparison in 1602ms Images are different
  69. ImageIO javax.image ⬢[rharter@toolbox differ]$ java \ -classpath $APP_HOME/lib/cli-jvm-0.0.2-SNAPSHOT.jar:$APP_HOME/lib/ kotlinx-io-0.1.16.jar:$APP_HOME/lib/differ-jvm-0.0.2-SNAPSHOT.jar: $APP_HOME/lib/kotlinx-cli-jvm-0.3.4.jar:$APP_HOME/lib/kotlin-stdlib-

    jdk8-1.8.0.jar:$APP_HOME/lib/kotlin-stdlib-jdk7-1.8.0.jar:$APP_HOME/lib/ kotlin-stdlib-1.8.0.jar:$APP_HOME/lib/kotlin-stdlib-common-1.8.0.jar: $APP_HOME/lib/atomicfu-common-0.14.1.jar:$APP_HOME/lib/ annotations-13.0.jar \
  70. ImageIO javax.image ⬢[rharter@toolbox differ]$ java \ -classpath $APP_HOME/lib/cli-jvm-0.0.2-SNAPSHOT.jar:$APP_HOME/lib/ kotlinx-io-0.1.16.jar:$APP_HOME/lib/differ-jvm-0.0.2-SNAPSHOT.jar: $APP_HOME/lib/kotlinx-cli-jvm-0.3.4.jar:$APP_HOME/lib/kotlin-stdlib-

    jdk8-1.8.0.jar:$APP_HOME/lib/kotlin-stdlib-jdk7-1.8.0.jar:$APP_HOME/lib/ kotlin-stdlib-1.8.0.jar:$APP_HOME/lib/kotlin-stdlib-common-1.8.0.jar: $APP_HOME/lib/atomicfu-common-0.14.1.jar:$APP_HOME/lib/ annotations-13.0.jar \ com.dropbox.differ.cli.jvm.JvmMainKt \
  71. ImageIO javax.image ⬢[rharter@toolbox differ]$ java \ -classpath $APP_HOME/lib/cli-jvm-0.0.2-SNAPSHOT.jar:$APP_HOME/lib/ kotlinx-io-0.1.16.jar:$APP_HOME/lib/differ-jvm-0.0.2-SNAPSHOT.jar: $APP_HOME/lib/kotlinx-cli-jvm-0.3.4.jar:$APP_HOME/lib/kotlin-stdlib-

    jdk8-1.8.0.jar:$APP_HOME/lib/kotlin-stdlib-jdk7-1.8.0.jar:$APP_HOME/lib/ kotlin-stdlib-1.8.0.jar:$APP_HOME/lib/kotlin-stdlib-common-1.8.0.jar: $APP_HOME/lib/atomicfu-common-0.14.1.jar:$APP_HOME/lib/ annotations-13.0.jar \ com.dropbox.differ.cli.jvm.JvmMainKt \ - - verbose cat.png cat-bad.png
  72. ImageIO javax.image ⬢[rharter@toolbox differ]$ ./gradlew tasks . . . Distribution

    tasks ------------------ assembleDist - Assembles the main distributions distTar - Bundles the project as a distribution. distZip - Bundles the project as a distribution. installDist - Installs the project as a distribution as-is. . . .
  73. ImageIO javax.image ⬢[rharter@toolbox differ]$ ./gradlew tasks . . . Distribution

    tasks ------------------ assembleDist - Assembles the main distributions distTar - Bundles the project as a distribution. distZip - Bundles the project as a distribution. installDist - Installs the project as a distribution as-is. . . . installDist
  74. - - verbose cat.png cat-bad.png ⬢[rharter@toolbox differ]$ ./bin/cli Starting to

    load first image: ./differ/src/commonTest/resources/cat.png Loaded ./differ/src/commonTest/resources/cat.png in 136ms Starting to load second image: ./differ/src/commonTest/resources/cat-bad.png Loaded ./differ/src/commonTest/resources/cat-bad.png in 77ms Starting comparison Finished comparison in 188ms Images are different 222407 pixels are different Time: 407ms Differences: 222407 (8.044234%) FAIL ImageIO javax.image
  75. Conclusion • Native dependencies are easier • Native performance is

    always better • Native distribution is easier
  76. Conclusion • Native dependencies are easier • Native performance is

    always better • Native distribution is easier
  77. Conclusion • Native dependencies are easier • Native performance is

    always better • Native distribution is easier • You should test your assumptions to see what works for you