Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add samples and validation to prepare for releases #15

Merged
merged 1 commit into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*.{kt,kts}]
ktlint_code_style = ktlint_official
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_chain-method-continuation = disabled
ktlint_standard_function-signature = disabled
ktlint_standard_function-expression-body = disabled
max_line_length = 120
9 changes: 8 additions & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,11 @@ jobs:
gradle-version: 8.9

- name: Run checks
run: ./gradlew build check shadowJar
run: ./gradlew build check shadowJar

# todo: disabled whilst investigating libvips
# - name: Run samples
# run: |
# sudo apt install imagemagick
# magick --version
# ./run_samples.sh
13 changes: 4 additions & 9 deletions .run/ImFfm.run.xml
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImFfm" type="Application" factoryName="Application" nameIsGenerated="true">
<configuration default="false" name="ImFfm" type="JetRunConfigurationType" nameIsGenerated="true">
<envs>
<env name="DYLD_LIBRARY_PATH" value="native_libs" />
</envs>
<option name="MAIN_CLASS_NAME" value="app.photofox.imffm.ImFfm" />
<module name="im-ffm.core" />
<option name="MAIN_CLASS_NAME" value="imffm.ImFfm" />
<module name="im-ffm.sample.main" />
<shortenClasspath name="NONE" />
<option name="VM_PARAMETERS" value="--enable-native-access=ALL-UNNAMED" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="app.photofox.imffm.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
Expand Down
52 changes: 0 additions & 52 deletions core/src/main/java/app/photofox/imffm/ImFfm.java

This file was deleted.

12 changes: 12 additions & 0 deletions run_samples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -eou pipefail

echo "building samples..."
./gradlew sample:clean sample:shadowJar

if [[ "$OSTYPE" == "darwin"* ]]; then
export DYLD_LIBRARY_PATH=native_libs
fi

echo "running samples..."
java -jar sample/build/libs/sample-all.jar
62 changes: 62 additions & 0 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@file:Suppress("UnstableApiUsage")

import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent

plugins {
kotlin("jvm") version "2.0.10"
id("com.github.johnrengelman.shadow") version "8.1.1"
application
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":core"))
implementation(platform("org.slf4j:slf4j-bom:2.0.16"))
implementation("org.slf4j:slf4j-api")
implementation("org.slf4j:slf4j-simple")
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(22))
}
}

testing {
suites {
val test by getting(JvmTestSuite::class) {
useKotlinTest("2.0.0")
testType = TestSuiteType.UNIT_TEST
}
}
}

tasks.withType<Test> {
testLogging {
events = TestLogEvent.values().toSet() - TestLogEvent.STARTED
exceptionFormat = TestExceptionFormat.FULL
}
environment(mapOf("DYLD_LIBRARY_PATH" to "native_libs"))
outputs.upToDateWhen { false }
}

tasks.withType<JavaExec>().configureEach {
environment(mapOf("DYLD_LIBRARY_PATH" to "native_libs"))
javaLauncher.set(project.javaToolchains.launcherFor(java.toolchain))
}

tasks.withType<Jar>().configureEach {
manifest {
attributes("Enable-Native-Access" to "ALL-UNNAMED")
}
}

application {
mainClass = "imffm.ImFfm"
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
// todo: figure out how to set DYLD_LIBRARY_PATH here
}
52 changes: 52 additions & 0 deletions sample/src/main/kotlin/imffm/ImFfm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package imffm

import org.slf4j.LoggerFactory
import java.lang.foreign.Arena
import java.nio.file.Files
import java.nio.file.Paths
import java.util.Locale
import kotlin.system.exitProcess

object ImFfm {

val logger = LoggerFactory.getLogger(ImFfm::class.java)

@JvmStatic
fun main(args: Array<String>) {
val samples = listOf(
ResizeImageSample
)
val sampleParentRunPath = Paths.get("sample_run")
if (Files.exists(sampleParentRunPath)) {
logger.info("clearing sample run directory at path \"$sampleParentRunPath\"")
sampleParentRunPath.toFile().deleteRecursively()
}
Files.deleteIfExists(sampleParentRunPath)
Files.createDirectory(sampleParentRunPath)

Arena.ofConfined().use { arena ->
samples.forEach { sample ->
logger.info("running sample \"${sample.name}\"...")
val sampleDirectoryName = makeSampleDirectoryName(sample.name)
val sampleRunPath = sampleParentRunPath.resolve(sampleDirectoryName)
Files.createDirectory(sampleRunPath)

sample.run(arena, sampleRunPath)
val validationResult = sample.validate(sampleRunPath)
if (validationResult.isFailure) {
logger.error("validation failed ❌", validationResult.exceptionOrNull())
exitProcess(1)
}
logger.info("validation succeeded ✅")
}
}
logger.info("all samples ran successfully 🎉")
exitProcess(0)
}

private fun makeSampleDirectoryName(original: String): String {
return original
.replace(oldValue = " ", "_")
.lowercase(Locale.ENGLISH)
}
}
70 changes: 70 additions & 0 deletions sample/src/main/kotlin/imffm/ResizeImageSample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package imffm

import app.photofox.imffm.generated.MagickWand_h.*
import java.io.File
import java.lang.foreign.Arena
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.absolutePathString

// https://imagemagick.org/MagickWand/resize.htm
object ResizeImageSample : RunnableSample {

override val name = "Image resizing"

override fun run(arena: Arena, workingDirectory: Path) {
MagickWandGenesis()

val wand = NewMagickWand()
// Read the image - all you need to do is change "logo:" to some other
// filename to have this resize and, if necessary, convert a different file
MagickReadImage(wand, arena.allocateFrom("logo:"))

// Get the image's width and height
var width = MagickGetImageWidth(wand)
var height = MagickGetImageHeight(wand)

// Cut them in half but make sure they don't underflow
width = (width / 2).coerceAtLeast(1)
height = (height / 2).coerceAtLeast(1)

// Resize the image using the Lanczos filter
// The blur factor is a "double", where > 1 is blurry, < 1 is sharp
// I haven't figured out how you would change the blur parameter of MagickResizeImage
// on the command line so I have set it to its default of one.
MagickResizeImage(wand, width, height, LanczosFilter())

// Set the compression quality to 95 (high quality = low compression)
MagickSetImageCompressionQuality(wand, 95)

/* Write the new image */
val resizedImagePath = workingDirectory.resolve("logo_resize.jpg")
MagickWriteImage(wand, arena.allocateFrom(resizedImagePath.absolutePathString()))

/* Clean up */
DestroyMagickWand(wand)

MagickWandTerminus()
}

override fun validate(workingDirectory: Path): Result<Unit> {
val resizedImagePath = workingDirectory.resolve("logo_resize.jpg")
if (!Files.exists(resizedImagePath)) {
return Result.failure(
RuntimeException("expected file does not exist")
)
}
val minSizeKb = 20
val maxSizeKb = 40
return runCatching {
Files.size(resizedImagePath) / 1000.0
}.map { size ->
if (size < minSizeKb || size > maxSizeKb) {
return Result.failure(
RuntimeException("image out of bounds (min $minSizeKb, max $maxSizeKb, actual $size")
)
}
}
}
}
11 changes: 11 additions & 0 deletions sample/src/main/kotlin/imffm/RunnableSample.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package imffm

import java.lang.foreign.Arena
import java.nio.file.Path

interface RunnableSample {

val name: String
fun run(arena: Arena, workingDirectory: Path)
fun validate(workingDirectory: Path): Result<Unit>
}
Empty file.
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ plugins {
}

rootProject.name = "im-ffm"
include("core")
include("core", "sample")
include("sample")