diff --git a/.github/workflows/publishRelease.yml b/.github/workflows/publishRelease.yml index d0ab840..ef24e83 100644 --- a/.github/workflows/publishRelease.yml +++ b/.github/workflows/publishRelease.yml @@ -19,20 +19,18 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - distribution: adopt - java-version: 11 + distribution: temurin + java-version: '21' # Runs upload, and then closes & releases the repository - name: Publish to MavenCentral - run: ./gradlew publish --max-workers 1 + run: ./gradlew publishToMavenCentral env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} SNAPSHOT: false \ No newline at end of file diff --git a/.github/workflows/publishSnapshot.yml b/.github/workflows/publishSnapshot.yml deleted file mode 100644 index cc36031..0000000 --- a/.github/workflows/publishSnapshot.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Publish Snapshot - -on: - push: - branches: [master] - -jobs: - test: - uses: ./.github/workflows/test.yml - secrets: inherit - - publish: - needs: test - name: Publish Snapshot - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - with: - distribution: adopt - java-version: 11 - - # Runs upload, and then closes & releases the repository - - name: Publish to snapshot to Sonatype - run: ./gradlew publish --max-workers 1 - env: - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} - SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} - SNAPSHOT: true \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61a0a7a..e9a79b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,12 +12,12 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v4 with: - distribution: adopt - java-version: 11 + distribution: temurin + java-version: '21' # Builds the release artifacts of the library - name: Test - run: ./gradlew test \ No newline at end of file + run: ./gradlew cleanJvmTest jvmTest \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 1bec35e..eb0a492 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,40 @@ + + + diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/README.md b/README.md index ad4ae80..38c2dfa 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,40 @@ # Tree (Data Structure) [![maven](https://img.shields.io/maven-central/v/com.github.adriankuta/tree-structure?style=plastic)](https://mvnrepository.com/artifact/com.github.adriankuta/tree-structure) [![License: MIT](https://img.shields.io/github/license/AdrianKuta/Tree-Data-Structure?style=plastic)](https://github.com/AdrianKuta/Tree-Data-Structure/blob/master/LICENSE) -[![Publish](https://github.com/AdrianKuta/Tree-Data-Structure/actions/workflows/publishRelease.yml/badge.svg)](https://github.com/AdrianKuta/Tree-Data-Structure/actions/workflows/publish.yml) +[![Publish](https://github.com/AdrianKuta/Tree-Data-Structure/actions/workflows/publishRelease.yml/badge.svg)](https://github.com/AdrianKuta/Tree-Data-Structure/actions/workflows/publishRelease.yml) -Simple implementation to store object in tree structure. +Lightweight Kotlin Multiplatform tree data structure for Kotlin and Java. Includes a small DSL, multiple traversal iterators, and pretty-print support. + +- Kotlin Multiplatform (JVM, JS, iOS, and Native host) +- Pre-order, Post-order, and Level-order iteration +- Simple DSL: tree { child(...) } +- Utilities: nodeCount(), height(), depth(), path(), prettyString(), clear(), removeChild() + +## Installation + +Gradle (Kotlin DSL): +```kotlin +// commonMain for KMP projects, or any sourceSet/module where you need it +dependencies { + implementation("com.github.adriankuta:tree-structure:3.1.1") // see badge above for the latest version +} +``` + +Gradle (Groovy): +```groovy +dependencies { + implementation "com.github.adriankuta:tree-structure:3.1.1" // see badge above for the latest +} +``` + +Maven: +```xml + + com.github.adriankuta + tree-structure + 3.1.1 + +``` ## Usage @@ -25,19 +56,15 @@ europe.addChild(france) println(root.prettyString()) ``` -**Pretty Kotlin** - +**Pretty Kotlin (DSL)** ```kotlin -val root = - tree("World") { - child("North America") { - child("USA") - } - child("Europe") { - child("Poland") - child("Germany") - } +val root = tree("World") { + child("North America") { child("USA") } + child("Europe") { + child("Poland") + child("Germany") } +} ``` **Java** @@ -58,8 +85,7 @@ europe.addChild(france); System.out.println(root.prettyString()); ``` -*Output:* - +Output: ``` World ├── North America @@ -69,11 +95,59 @@ World └── France ``` +### Traversal and utilities +```kotlin +val root = TreeNode("root") +// ... build your tree -## Download +// Choose iteration order (default is PreOrder) +root.treeIterator = TreeNodeIterators.PostOrder +for (node in root) println(node.value) + +// Utilities +root.nodeCount() // number of descendants +root.height() // longest path to a leaf (in edges) +root.depth() // distance from current node to the root +val path = root.path(root.children.first()) // nodes from descendant up to root + +// Mutations +val child = root.children.first() +root.removeChild(child) +root.clear() // remove entire subtree +``` + +## Publishing to Maven Central (central.sonatype.com) + +This project is configured to publish artifacts to Maven Central via the Sonatype Central Portal. + +There are two supported ways to publish: + +1) Via GitHub Actions (recommended) +- Create a GitHub Release (tag) in this repository. When a release is published, the workflow .github/workflows/publishRelease.yml runs automatically. +- The workflow uses the Gradle task publishToMavenCentral to upload artifacts through the Central Portal. +- Make sure these repository secrets are configured in GitHub: + - MAVEN_CENTRAL_USERNAME — Your Sonatype Central username (not email). + - MAVEN_CENTRAL_PASSWORD — Your Sonatype Central password or a token from central.sonatype.com. + - SIGNING_KEY — ASCII‑armored GPG private key (exported, single line; for in‑memory signing). + - SIGNING_PASSWORD — Passphrase for the key above. +- The workflow uses JDK 21 and publishes the version defined in build.gradle.kts. + +2) Locally via Gradle +- Ensure you have a Sonatype Central account and that the groupId com.github.adriankuta is verified in central.sonatype.com (Namespace Rules → Verify). +- Export the same credentials/signing values as environment variables or pass them as Gradle properties: + - ORG_GRADLE_PROJECT_mavenCentralUsername + - ORG_GRADLE_PROJECT_mavenCentralPassword + - ORG_GRADLE_PROJECT_signingInMemoryKey + - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword +- Then run: + - ./gradlew publishToMavenCentral +- For snapshot publishing, set -Psnapshot=true (the version is derived from PUBLISH_VERSION with -SNAPSHOT). + +Notes +- Publishing is powered by the com.vanniktech.maven.publish Gradle plugin and Sonatype Central Portal (no legacy Nexus staging URLs needed). +- The plugin is configured to sign all publications. Coordinates and POM metadata are defined in build.gradle.kts. +- If using the combined task is preferred, you can also run publishAndReleaseToMavenCentral when automatic release is enabled; this repository currently uploads with publishToMavenCentral from CI. - implementation "com.github.adriankuta:tree-structure:$latest_versions" - ## License MIT License @@ -99,22 +173,3 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- - -## Publishing (Maven Central migration) - -This project is configured to publish via Sonatype's s01.oss.sonatype.org (Nexus) which is compatible with the new Central (central.sonatype.com). The old oss.sonatype.org host is no longer used. - -Environment variables supported by the build: -- CENTRAL_USERNAME / CENTRAL_PASSWORD — Central Portal user/token (preferred) -- OSSRH_USERNAME / OSSRH_PASSWORD — legacy credentials (fallback) -- SIGNING_KEY_ID / SIGNING_KEY / SIGNING_PASSWORD — PGP signing (ASCII-armored key) -- SNAPSHOT — set to true to append -SNAPSHOT to version - -Gradle tasks: -- Publish all publications to Sonatype: `./gradlew publishAllPublicationsToSonatypeS01Repository` -- Or, standard publish (selects snapshots vs releases by version): `./gradlew publish` - -Notes: -- Releases are uploaded to: https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ -- Snapshots are uploaded to: https://s01.oss.sonatype.org/content/repositories/snapshots/ -- Staging/release close and promote are handled by Sonatype. If you use CI, set the env vars in your secrets. diff --git a/build.gradle.kts b/build.gradle.kts index de3ae20..21a2cd5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,147 +1,88 @@ import org.jetbrains.kotlin.konan.properties.Properties plugins { - kotlin("multiplatform") version "1.7.20" - id("org.jetbrains.dokka") version "1.7.20" - id("maven-publish") + kotlin("multiplatform") version "1.9.20" + id("org.jetbrains.dokka") version "1.9.20" + id("com.vanniktech.maven.publish") version "0.34.0" signing } val PUBLISH_GROUP_ID = "com.github.adriankuta" -val PUBLISH_ARTIFACT_ID = "tree-structure" -val PUBLISH_VERSION = "3.1.0" +val PUBLISH_ARTIFACT_ID = "tree-structure" // base artifact; KMP will add -jvm, -ios*, etc. +val PUBLISH_VERSION = "3.1.1" -val secretFile = File(rootProject.rootDir, "local.properties") -if (secretFile.exists()) { - secretFile.reader().use { - Properties().apply { - load(it) - } - }.onEach { (name, value) -> - project.ext[name.toString()] = value - } -} else { - // Prefer Central Portal credentials via environment variables - project.ext["centralUsername"] = System.getenv("CENTRAL_USERNAME") - project.ext["centralPassword"] = System.getenv("CENTRAL_PASSWORD") - // Fallback legacy OSSRH credentials (still supported on s01) - project.ext["ossrhUsername"] = System.getenv("OSSRH_USERNAME") - project.ext["ossrhPassword"] = System.getenv("OSSRH_PASSWORD") - project.ext["sonatypeStagingProfileId"] = System.getenv("SONATYPE_STAGING_PROFILE_ID") - project.ext["signingKeyId"] = System.getenv("SIGNING_KEY_ID") - project.ext["signingPassword"] = System.getenv("SIGNING_PASSWORD") - project.ext["signingKey"] = System.getenv("SIGNING_KEY") - project.ext["snapshot"] = System.getenv("SNAPSHOT") -} val snapshot: String? by project group = PUBLISH_GROUP_ID version = if (snapshot.toBoolean()) "$PUBLISH_VERSION-SNAPSHOT" else PUBLISH_VERSION -val dokkaHtml by tasks.getting(org.jetbrains.dokka.gradle.DokkaTask::class) +mavenPublishing { + // Central Portal + auto release when we call publishAndReleaseToMavenCentral + publishToMavenCentral(automaticRelease = false) + signAllPublications() -val javadocJar: TaskProvider by tasks.registering(Jar::class) { - dependsOn(dokkaHtml) - archiveClassifier.set("javadoc") - from(dokkaHtml.outputDirectory) -} + coordinates(PUBLISH_GROUP_ID, PUBLISH_ARTIFACT_ID, version.toString()) -publishing { - publications { + pom { + name.set("Tree Data Structure") + description.set("Simple implementation to store object in tree structure.") + url.set("https://github.com/AdrianKuta/Tree-Data-Structure") - withType { - artifact(javadocJar) - pom { - name.set("Tree Data Structure") - description.set("Simple implementation to store object in tree structure.") - url.set("https://github.com/AdrianKuta/Tree-Data-Structure") - licenses { - license { - name.set("MIT License") - url.set("https://www.mit.edu/~amini/LICENSE.md") - } - } - developers { - developer { - name.set("Adrian Kuta") - email.set("adrian.kuta93@gmail.com") - } - } - scm { - connection.set("scm:git:github.com/AdrianKuta/Tree-Data-Structure.git") - developerConnection.set("scm:git:ssh://github.com/AdrianKuta/Tree-Data-Structure.git") - url.set("https://github.com/AdrianKuta/Tree-Data-Structure/tree/master") - } + licenses { + license { + name.set("MIT License") + url.set("https://opensource.org/licenses/MIT") + distribution.set("repo") } } - } - - repositories { - maven { - name = "SonatypeS01" - // s01 is the supported Nexus host for Central publishing via Maven-compatible uploads - val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) - credentials { - // Prefer Central Portal credentials when provided, fallback to legacy OSSRH credentials - val centralUsername: String? by project - val centralPassword: String? by project - val CENTRAL_USERNAME: String? by project - val CENTRAL_PASSWORD: String? by project - val ossrhUsername: String? by project - val ossrhPassword: String? by project - username = centralUsername - ?: CENTRAL_USERNAME - ?: ossrhUsername - ?: System.getenv("CENTRAL_USERNAME") - ?: System.getenv("OSSRH_USERNAME") - password = centralPassword - ?: CENTRAL_PASSWORD - ?: ossrhPassword - ?: System.getenv("CENTRAL_PASSWORD") - ?: System.getenv("OSSRH_PASSWORD") + developers { + developer { + id.set("AdrianKuta") + name.set("Adrian Kuta") + email.set("adrian.kuta93@gmail.com") } } + scm { + url.set("https://github.com/AdrianKuta/Tree-Data-Structure") + connection.set("scm:git:https://github.com/AdrianKuta/Tree-Data-Structure.git") + developerConnection.set("scm:git:ssh://git@github.com/AdrianKuta/Tree-Data-Structure.git") + } } } -signing { - val signingKeyId: String? by project - val signingPassword: String? by project - val signingKey: String? by project - useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) - - sign(publishing.publications) -} +// No legacy publishing {} block or s01 repos — Central Portal handles it. repositories { mavenCentral() } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} kotlin { + jvmToolchain(21); jvm { compilations.all { - kotlinOptions.jvmTarget = "1.8" - } - withJava() - testRuns["test"].executionTask.configure { - useJUnitPlatform() - } - } - js(BOTH) { - browser { - commonWebpackConfig { - cssSupport.enabled = true + kotlinOptions { + jvmTarget = "21" // <- was "1.8" } } } - // Add iOS targets + // JS targets (IR) for publishing + js(IR) { + browser() + nodejs() + } + + // iOS targets iosX64() iosArm64() iosSimulatorArm64() + // Native host target val hostOs = System.getProperty("os.name") val isMingwX64 = hostOs.startsWith("Windows") val nativeTarget = when { @@ -153,16 +94,8 @@ kotlin { sourceSets { val commonMain by getting - val commonTest by getting { - dependencies { - implementation(kotlin("test")) - } - } - val jvmMain by getting { - dependencies { - implementation(kotlin("script-runtime")) - } - } + val commonTest by getting { dependencies { implementation(kotlin("test")) } } + val jvmMain by getting { dependencies { implementation(kotlin("script-runtime")) } } val jvmTest by getting val jsMain by getting val jsTest by getting @@ -170,12 +103,8 @@ kotlin { val nativeTest by getting // Shared iOS source sets - val iosMain by creating { - dependsOn(commonMain) - } - val iosTest by creating { - dependsOn(commonTest) - } + val iosMain by creating { dependsOn(commonMain) } + val iosTest by creating { dependsOn(commonTest) } val iosX64Main by getting { dependsOn(iosMain) } val iosArm64Main by getting { dependsOn(iosMain) } val iosSimulatorArm64Main by getting { dependsOn(iosMain) }