Skip to content
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
80 changes: 80 additions & 0 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,86 @@ testing {
}
}

// Register the custom linter task that checks for LoadLibrary.initialize() in static blocks
val checkLoadLibraryInitializer = tasks.register("checkLoadLibraryInitializer") {
description = "Checks that all classes calling AutomergeSys have LoadLibrary.initialize() in static block"
group = "verification"

// Make the task depend on Java source files
inputs.files(fileTree("src/main/java") {
include("**/*.java")
})

doLast {
val violations = mutableListOf<String>()
val exemptClasses = listOf("AutomergeSys.java", "LoadLibrary.java")

fileTree("src/main/java") {
include("**/*.java")
}.forEach { file ->
val fileName = file.name

// Skip exempt classes
if (fileName in exemptClasses) {
return@forEach
}

val content = file.readText()

// Check if file calls AutomergeSys
// Use simple contains() since we're just looking for the class name followed by a dot
val callsAutomergeSys = content.contains("AutomergeSys.")

if (callsAutomergeSys) {
// Check if it has the static initializer with LoadLibrary.initialize()
// Look for the pattern: static { ... LoadLibrary.initialize() ... }
val hasStaticInit = Regex("""static\s*\{[^}]*LoadLibrary\.initialize\(\)""", RegexOption.DOT_MATCHES_ALL)
.containsMatchIn(content)

if (!hasStaticInit) {
val classMatch = Regex("""(?:public\s+)?(?:abstract\s+)?class\s+(\w+)""").find(content)
val className = classMatch?.groupValues?.get(1) ?: fileName.removeSuffix(".java")
val calledClass = "AutomergeSys"
val relativePath = file.relativeTo(projectDir).path
violations.add("$relativePath: $className calls $calledClass but missing static initializer")
}
}
}

if (violations.isNotEmpty()) {
val message = buildString {
appendLine()
appendLine("=" .repeat(80))
appendLine("LoadLibrary.initialize() Static Initializer Check FAILED")
appendLine("=".repeat(80))
appendLine()
appendLine("The following classes call AutomergeSys but are missing")
appendLine("a static initializer that calls LoadLibrary.initialize():")
appendLine()
violations.forEach { violation ->
appendLine(" ❌ $violation")
}
appendLine()
appendLine("To fix this, add the following static block to each class:")
appendLine()
appendLine(" static {")
appendLine(" LoadLibrary.initialize();")
appendLine(" }")
appendLine()
appendLine("=".repeat(80))
}
throw GradleException(message)
}

logger.lifecycle("✓ All classes that call AutomergeSys have LoadLibrary.initialize() in static block")
}
}

// Make 'check' depend on our linter
tasks.named("check") {
dependsOn(checkLoadLibraryInitializer)
}

// Create a separate test task that runs the same tests with Java 8 runtime
tasks.register<Test>("testJava8") {
description = "Runs tests with Java 8 runtime to verify backward compatibility"
Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/CommitResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class CommitResult {
private Optional<ChangeHash> hash;
private AutomergeSys.PatchLogPointer patchLog;

static {
LoadLibrary.initialize();
}

protected CommitResult(Optional<ChangeHash> hash, AutomergeSys.PatchLogPointer patchLog) {
this.hash = hash;
this.patchLog = patchLog;
Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/Cursor.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
public class Cursor {
private byte[] raw;

static {
LoadLibrary.initialize();
}

/**
* Parse the output of {@link toBytes()}
*
Expand Down
7 changes: 4 additions & 3 deletions lib/src/main/java/org/automerge/Document.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
* many times it may be worth reusing actor IDs.
*/
public class Document implements Read {
static {
LoadLibrary.initialize();
}

private Optional<DocPointer> pointer;
// Keep actor ID here so we a) don't have to keep passing it across the JNI
// boundary and b) can access it when a transaction is in progress
Expand All @@ -80,7 +84,6 @@ public class Document implements Read {

/** Create a new document with a random actor ID */
public Document() {
LoadLibrary.initialize();
this.pointer = Optional.of(AutomergeSys.createDoc());
this.actorId = AutomergeSys.getActorId(this.pointer.get());
this.transactionPtr = Optional.empty();
Expand All @@ -93,14 +96,12 @@ public Document() {
* the actor ID to use for this document
*/
public Document(byte[] actorId) {
LoadLibrary.initialize();
this.actorId = actorId;
this.pointer = Optional.of(AutomergeSys.createDocWithActor(actorId));
this.transactionPtr = Optional.empty();
}

private Document(DocPointer pointer) {
LoadLibrary.initialize();
this.pointer = Optional.of(pointer);
this.actorId = AutomergeSys.getActorId(this.pointer.get());
this.transactionPtr = Optional.empty();
Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/ObjectId.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
public class ObjectId {
private byte[] raw;

static {
LoadLibrary.initialize();
}

public static ObjectId ROOT;

static {
Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/PatchLog.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
public class PatchLog {
private Optional<PatchLogPointer> pointer;

static {
LoadLibrary.initialize();
}

public PatchLog() {
pointer = Optional.of(AutomergeSys.createPatchLog());
}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/SyncState.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
public class SyncState {
private Optional<AutomergeSys.SyncStatePointer> pointer;

static {
LoadLibrary.initialize();
}

private SyncState(AutomergeSys.SyncStatePointer pointer) {
this.pointer = Optional.of(pointer);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/main/java/org/automerge/TransactionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public class TransactionImpl implements Transaction {
private Document doc;
private Optional<Consumer<AutomergeSys.PatchLogPointer>> finish;

static {
LoadLibrary.initialize();
}

protected TransactionImpl(Document doc, AutomergeSys.TransactionPointer pointer) {
this.pointer = Optional.of(pointer);
this.doc = doc;
Expand Down
Loading