Compare commits

..
Author SHA1 Message Date
InsanusMokrassar 6b287bb318 ci: use the Android API 37.0 SDK package layout 2026-11-02 03:12:32 +06:00
InsanusMokrassar 9fc0fed48e ci: avoid the removed Android SDK tools package 2026-11-02 03:04:56 +06:00
InsanusMokrassar 4d0f039787 build: add Kotlin Toolchain migration with Gradle fallback 2026-11-02 03:01:39 +06:00
InsanusMokrassar 0b9fb30862 Merge pull request #134 from InsanusMokrassar/2.1.0
2.1.0
2026-10-09 18:42:37 +06:00
InsanusMokrassar 9edc998ccc update dependencies 2026-09-18 15:26:26 +06:00
InsanusMokrassar e1b2aea2ea start 2.1.0 2026-09-18 15:13:04 +06:00
InsanusMokrassar f5e26c0308 Merge pull request #129 from InsanusMokrassar/2.0.0
2.0.0
2026-08-31 13:03:24 +06:00
InsanusMokrassar 3fb8731f0a update changelog 2026-08-31 12:39:55 +06:00
InsanusMokrassar bd34d84095 migrate build toolchain 2026-08-31 11:50:38 +06:00
InsanusMokrassar 42b56fdf99 update dependencies 2026-08-24 00:31:38 +06:00
InsanusMokrassar cd63889a34 start 2.0.0 2026-08-24 00:22:53 +06:00
InsanusMokrassar 892ccd442b Merge pull request #128 from InsanusMokrassar/1.7.0
1.7.0
2026-08-12 10:35:21 +06:00
InsanusMokrassar dac23a366b Update CHANGELOG.md 2026-08-12 10:35:01 +06:00
InsanusMokrassar f8d6591c86 update dependencies 2026-08-11 19:58:26 +06:00
InsanusMokrassar ddb0202069 start 1.7.0 2026-08-11 19:47:37 +06:00
InsanusMokrassar fa16f7f5d3 Merge pull request #123 from InsanusMokrassar/1.6.1
1.6.1
2026-04-18 19:59:37 +06:00
InsanusMokrassar d37630c1d4 update dependencies
org.jetbrains.kotlin:kotlin-stdlib 2.3.0 -> 2.3.20
org.jetbrains.dokka:dokka-gradle-plugin 2.1.0 -> 2.2.0
com.gradleup.nmcp 1.2.1 -> 1.4.4
junit:junit 4.12 -> 4.13.2
2026-04-18 19:58:37 +06:00
InsanusMokrassar e0e97292aa start 1.6.1 2026-04-18 19:49:38 +06:00
InsanusMokrassar 22b22c48f4 small fix in github_publishing 2026-01-24 19:11:41 +06:00
InsanusMokrassar 52f18b234d revert nmcp version 2026-01-24 18:07:22 +06:00
InsanusMokrassar 4d9708b53f Add allowEmptyAggregation to build.gradle 2026-01-24 16:29:07 +06:00
InsanusMokrassar 91f3516c8f Merge pull request #118 from InsanusMokrassar/1.6.0
1.6.0
2026-01-24 16:09:54 +06:00
InsanusMokrassar 7fbea3c8b6 update kotlin, gradle and changelog :) 2026-01-24 16:09:07 +06:00
InsanusMokrassar 0417cd117a start 1.6.0 2026-01-24 15:55:45 +06:00
InsanusMokrassar 2015dc5083 Merge pull request #112 from InsanusMokrassar/1.5.2
1.5.2
2025-11-12 17:08:27 +06:00
InsanusMokrassar 7f92a3e614 generate kdocs 2025-11-12 13:45:57 +06:00
InsanusMokrassar 3c4e59ddc2 update dependencies 2025-11-12 13:25:11 +06:00
InsanusMokrassar 461bc92966 start 1.5.2 2025-11-12 13:06:43 +06:00
InsanusMokrassar 8c0968ba86 Merge pull request #108 from InsanusMokrassar/1.5.1
1.5.1
2025-09-30 23:19:40 +06:00
InsanusMokrassar 526de46a98 update dependencies 2025-09-30 23:19:00 +06:00
InsanusMokrassar 0af8c78e37 start 1.5.1 2025-09-30 23:12:06 +06:00
InsanusMokrassar 6840598e04 fix publishing? 2025-06-30 20:28:52 +06:00
InsanusMokrassar 7a2d070ee3 fix of publishing scripts 2025-06-30 18:02:47 +06:00
InsanusMokrassar 92651d3ba6 Merge pull request #101 from InsanusMokrassar/1.5.0
1.5.0
2025-06-30 14:43:28 +06:00
InsanusMokrassar b8c72f1131 update dependencies 2025-06-29 18:46:36 +06:00
InsanusMokrassar 9b92fa29f4 start 1.5.0 2025-06-29 18:36:26 +06:00
InsanusMokrassar 42ced6edd2 Update github_release.gradle 2025-05-16 12:38:00 +06:00
InsanusMokrassar dd3e4c4c2a Update github_release.gradle 2025-05-16 12:20:39 +06:00
InsanusMokrassar f528909915 Merge pull request #98 from InsanusMokrassar/1.4.2
1.4.2
2025-05-16 09:05:00 +06:00
InsanusMokrassar b3919c8439 update dependencies 2025-05-16 08:58:52 +06:00
InsanusMokrassar d1dd159e36 update publication script 2025-05-16 08:55:04 +06:00
InsanusMokrassar 3db55982e9 start 1.4.2 2025-05-16 08:48:08 +06:00
InsanusMokrassar 8e477b1056 Merge pull request #95 from InsanusMokrassar/1.4.1
1.4.1
2025-01-29 12:36:18 +06:00
InsanusMokrassar 480d2c8132 update dependencies 2025-01-29 12:06:52 +06:00
InsanusMokrassar a2a73dfe83 start 1.4.1 2025-01-29 12:06:20 +06:00
InsanusMokrassar 4528918347 Merge pull request #92 from InsanusMokrassar/renovate/major-dokka
Update dokka to v2 (major)
2025-01-10 08:21:30 +06:00
renovate[bot] 9ef93724ad Update dokka to v2 2024-12-21 02:49:05 +00:00
InsanusMokrassar dccbd13788 Merge pull request #90 from InsanusMokrassar/1.4.0
1.4.0
2024-12-09 10:10:09 +06:00
InsanusMokrassar 8bb34d6842 update dependencies 2024-12-09 10:04:09 +06:00
InsanusMokrassar cff3f71de7 start 1.4.0 2024-12-09 10:03:14 +06:00
InsanusMokrassar aab0ac064f Merge pull request #87 from InsanusMokrassar/1.3.6
1.3.6
2024-08-27 19:13:32 +06:00
InsanusMokrassar d2909ed5f0 update dependencies 2024-08-27 19:12:58 +06:00
InsanusMokrassar 81eba88ce9 start 1.3.6 2024-08-27 19:01:11 +06:00
InsanusMokrassar a73f0d132c remove kotlin-js-store 2024-08-27 19:00:22 +06:00
InsanusMokrassar bd87184f81 Merge pull request #84 from InsanusMokrassar/1.3.5
1.3.5
2024-08-09 23:07:10 +06:00
InsanusMokrassar 7228e81344 update android sdk version 2024-08-09 22:53:36 +06:00
InsanusMokrassar ac828b1af7 update test dependencies and wrapper versions 2024-08-09 22:43:06 +06:00
InsanusMokrassar a8c85b9614 fix of build 2024-08-09 22:34:05 +06:00
InsanusMokrassar 98fbf43d4d update kotlin 2024-08-09 22:28:55 +06:00
InsanusMokrassar ef8a3e1500 start 1.3.5 2024-08-09 22:28:08 +06:00
InsanusMokrassar 3edbb78d56 Merge pull request #77 from InsanusMokrassar/1.3.4
1.3.4
2024-05-12 16:49:34 +06:00
InsanusMokrassar 663395ccbe update dependencies, update changelog, replace KSLoggerDefaultPlatformLoggerLambda in nativeMain 2024-05-12 16:48:47 +06:00
InsanusMokrassar ecd2b08c69 start 1.3.4 2024-05-12 16:42:35 +06:00
InsanusMokrassar f5e50d00a2 Merge pull request #73 from InsanusMokrassar/1.3.3
1.3.3
2024-03-15 13:39:16 +06:00
InsanusMokrassar a1784dc6af Update CHANGELOG.md 2024-03-15 13:21:16 +06:00
InsanusMokrassar 4d6410c870 Update gradle-wrapper.properties 2024-03-15 13:19:40 +06:00
InsanusMokrassar 3a4035f1a6 Update libs.versions.toml 2024-03-15 13:16:49 +06:00
InsanusMokrassar 86bf9468ba Update gradle.properties 2024-03-15 13:14:00 +06:00
InsanusMokrassar 24adeef67b Merge pull request #67 from InsanusMokrassar/1.3.2
1.3.2
2024-01-17 23:33:45 +06:00
InsanusMokrassar 4823df5c6f update gradle wrapper 2024-01-16 12:48:29 +06:00
InsanusMokrassar 2d397e9b12 update dependencies 2024-01-16 12:47:07 +06:00
InsanusMokrassar 37a0fceb56 start 1.3.2 2024-01-16 12:46:07 +06:00
InsanusMokrassar 0860d6cacc update artifacts to be published in nexus 2023-12-05 19:41:15 +06:00
InsanusMokrassar d9863ded5a Merge pull request #62 from InsanusMokrassar/1.3.1
1.3.1
2023-11-24 16:44:52 +06:00
InsanusMokrassar 48a265e3d5 update dependencies 2023-11-24 16:38:30 +06:00
InsanusMokrassar 30cdbea2c9 start 1.3.1 2023-11-24 16:37:15 +06:00
InsanusMokrassar b19639e8a1 Merge branch '1.3.0' (fix of js assert accordance) 2023-11-18 18:08:38 +06:00
InsanusMokrassar 447e4fe5ac hotfix of js assert accordance 2023-11-18 18:08:19 +06:00
InsanusMokrassar 30d82ebda6 Merge pull request #59 from InsanusMokrassar/1.3.0
1.3.0
2023-11-18 18:04:06 +06:00
InsanusMokrassar 0b7b408b06 update changelog 2023-11-18 17:59:45 +06:00
InsanusMokrassar ea43501a03 migration onto 1.3.0 2023-11-18 17:55:50 +06:00
InsanusMokrassar 0348756729 a lot of changes and improvements 2023-11-18 17:55:19 +06:00
InsanusMokrassar 81b32b0119 add a lot of kdocs 2023-11-18 17:15:23 +06:00
InsanusMokrassar 7a8bd035f5 add trace level 2023-11-18 16:18:45 +06:00
InsanusMokrassar 34a7c9624d start 1.2.5 2023-11-18 16:17:42 +06:00
InsanusMokrassar fb0f61bb15 Merge pull request #55 from InsanusMokrassar/1.2.4
1.2.4
2023-11-02 18:16:47 +06:00
InsanusMokrassar 48eeb6660e fill changelog 2023-11-02 18:14:14 +06:00
InsanusMokrassar ff14fd5e7c fixes of publication? 2023-11-02 18:06:55 +06:00
InsanusMokrassar aa7bd6179a update gradle versions and publication scripts 2023-11-02 17:59:22 +06:00
InsanusMokrassar ef72365505 start 1.2.4 2023-11-02 17:29:46 +06:00
InsanusMokrassar c0a10436be Merge pull request #54 from InsanusMokrassar/1.2.3
1.2.3
2023-11-01 23:43:57 +06:00
InsanusMokrassar a3170786a7 update dependencies 2023-11-01 23:40:32 +06:00
InsanusMokrassar d60e35f1e9 start 1.2.3 2023-11-01 23:39:39 +06:00
InsanusMokrassar bed00407fb downgrade version of gradle 2023-10-23 13:39:10 +06:00
InsanusMokrassar f326d9070a second try to fix publish issues 2023-10-23 02:12:38 +06:00
InsanusMokrassar b4c454141e add crutch for publishing of artifacts 2023-10-23 02:05:05 +06:00
InsanusMokrassar 1a589c675e Merge pull request #47 from InsanusMokrassar/1.2.2
1.2.2
2023-10-23 01:38:51 +06:00
InsanusMokrassar d25a73df3d remove unnecessary targets 2023-10-23 01:37:41 +06:00
InsanusMokrassar 333cb690a1 get back version of kslog 2023-09-22 01:52:15 +06:00
InsanusMokrassar a3e6ccf973 get back build.gradle 2023-09-22 01:46:05 +06:00
InsanusMokrassar 9ce62e13f5 migrate onto build.gradle.kts 2023-09-20 22:27:06 +06:00
InsanusMokrassar e88df8db99 update github workflows java toolchain versions 2023-09-20 21:45:39 +06:00
InsanusMokrassar 8613f350e3 update of gradle wrapper and publishing scripts 2023-09-20 21:39:01 +06:00
InsanusMokrassar 7fbf80a52a start 1.2.2 2023-09-20 20:04:27 +06:00
InsanusMokrassar 00da37e060 Merge pull request #45 from InsanusMokrassar/1.2.1
1.2.1
2023-09-06 19:05:58 +06:00
InsanusMokrassar a779791a00 update dependencies 2023-09-06 19:05:12 +06:00
InsanusMokrassar 03cc000306 start 1.2.1 2023-09-06 19:02:59 +06:00
InsanusMokrassar 875d2d723e Merge pull request #41 from InsanusMokrassar/1.2.0
1.2.0
2023-07-28 12:21:55 +06:00
InsanusMokrassar 01722311e6 update kotlin and add versions plugin 2023-07-28 12:15:01 +06:00
InsanusMokrassar 9b994de857 start 1.2.0 2023-07-28 12:15:01 +06:00
InsanusMokrassar a170bbaa5c Merge pull request #39 from InsanusMokrassar/1.1.2
1.1.2
2023-07-28 12:14:28 +06:00
InsanusMokrassar 7b8d72e9c0 update test dependencies 2023-06-19 22:47:41 +06:00
InsanusMokrassar 0237ec56c5 update dependencies 2023-06-19 22:46:45 +06:00
InsanusMokrassar bef9e0c550 start 1.1.2 2023-06-19 22:40:16 +06:00
InsanusMokrassar d7eb0093d8 Update README.md 2023-06-10 18:16:35 +06:00
InsanusMokrassar deba12ed5d update dokka and gradle wrapper version 2023-06-09 12:08:57 +06:00
InsanusMokrassar f608cf4bef Merge pull request #25 from InsanusMokrassar/renovate/configure
Configure Renovate
2023-04-25 12:27:29 +06:00
renovate[bot] 4a656d2d90 Add renovate.json 2023-04-18 08:23:00 +00:00
InsanusMokrassar c730f49fbd Merge pull request #24 from InsanusMokrassar/1.1.1
1.1.1
2023-04-13 11:09:15 +06:00
InsanusMokrassar 2a36f64b51 add actual defaultLogging in wasm 2023-04-13 10:58:07 +06:00
InsanusMokrassar 604ef1b347 add wasm support 2023-04-13 10:52:05 +06:00
InsanusMokrassar dc50977c9a KSLog factory fix 2023-04-13 10:33:13 +06:00
InsanusMokrassar 7c0650e2df start 1.1.1 2023-04-13 10:31:50 +06:00
InsanusMokrassar 387ca9097c Merge pull request #23 from InsanusMokrassar/1.1.0
1.1.0
2023-04-12 23:21:54 +06:00
InsanusMokrassar a71405aff5 small improvements in KSLog 2023-04-12 22:42:17 +06:00
InsanusMokrassar a2b497d0f9 update dependencies 2023-04-12 22:35:34 +06:00
InsanusMokrassar c5974fb51c start 1.1.0 2023-04-12 22:33:22 +06:00
InsanusMokrassar 3240b63f21 Enlarge Java Heap space 2023-03-12 19:38:22 +06:00
InsanusMokrassar fffafc4da7 Update libs.versions.toml 2023-03-12 19:34:08 +06:00
InsanusMokrassar b1d5ce2135 Merge pull request #22 from InsanusMokrassar/1.0.0
1.0.0
2023-01-04 20:26:35 +06:00
InsanusMokrassar 0b8db9be35 Update CHANGELOG.md 2023-01-04 08:59:46 +06:00
InsanusMokrassar 5030ecc255 Update libs.versions.toml 2023-01-03 08:27:35 +06:00
InsanusMokrassar b5669d2187 start 1.0.0 2023-01-03 08:23:30 +06:00
InsanusMokrassar ed7ad35605 update publishing workflows 2022-12-05 15:53:37 +06:00
InsanusMokrassar 6d5ddafefc update publishing scripts 2022-12-05 15:51:11 +06:00
InsanusMokrassar ee31c61677 Merge pull request #21 from InsanusMokrassar/0.5.4
0.5.4
2022-12-05 15:32:45 +06:00
InsanusMokrassar 1be14aa28e Update CHANGELOG.md 2022-12-05 15:32:27 +06:00
InsanusMokrassar 97b432c0b0 Update CHANGELOG.md 2022-12-05 08:06:39 +06:00
InsanusMokrassar 74a0ad8fd1 Update libs.versions.toml 2022-12-05 07:53:25 +06:00
InsanusMokrassar 502dd4d607 Update libs.versions.toml 2022-12-05 07:51:29 +06:00
InsanusMokrassar 079fbaa7f4 Update libs.versions.toml 2022-11-28 16:40:04 +06:00
InsanusMokrassar 99dac12a27 Update gradle.properties 2022-11-28 16:38:43 +06:00
InsanusMokrassar b0cc6c11ea add platforms support badges 2022-11-28 13:28:13 +06:00
InsanusMokrassar 58161132dc Merge pull request #20 from InsanusMokrassar/0.5.3
0.5.3
2022-11-03 13:27:17 +06:00
InsanusMokrassar 7e45e5d421 Update CHANGELOG.md 2022-11-03 13:27:02 +06:00
InsanusMokrassar e7956f6d31 Update README.md 2022-11-03 13:22:23 +06:00
InsanusMokrassar 9993fd5fdd Update libs.versions.toml 2022-11-03 13:11:51 +06:00
InsanusMokrassar 9e8665cdc9 Update gradle.properties 2022-11-03 13:09:32 +06:00
InsanusMokrassar 63d3cabc9c Merge pull request #19 from InsanusMokrassar/0.5.2
0.5.2
2022-09-12 15:46:27 +06:00
InsanusMokrassar 0b142c7ff9 add native platforms support 2022-09-12 15:44:59 +06:00
InsanusMokrassar 7a1d934dab start 0.5.2 2022-09-12 15:07:22 +06:00
InsanusMokrassar b3720de8c7 Merge pull request #18 from InsanusMokrassar/0.5.1
0.5.1
2022-08-18 12:44:17 +06:00
InsanusMokrassar 669afe481d add defaultMessageFormatterWithErrorPrint 2022-08-13 20:19:21 +06:00
InsanusMokrassar 42db9da058 update gradle wrapper 2022-08-13 20:12:44 +06:00
InsanusMokrassar 72de26adac start 0.5.1 2022-08-13 20:12:19 +06:00
InsanusMokrassar 4b47a4e817 Merge pull request #17 from InsanusMokrassar/0.5.0
0.5.0
2022-08-04 17:19:33 +06:00
InsanusMokrassar 785ab59519 Update CHANGELOG.md 2022-08-04 16:57:38 +06:00
InsanusMokrassar a4f2562690 Update libs.versions.toml 2022-08-04 16:56:30 +06:00
InsanusMokrassar 777ae44da5 Update gradle.properties 2022-08-04 16:54:14 +06:00
InsanusMokrassar 28d61a7b7c Merge pull request #16 from InsanusMokrassar/0.4.2
0.4.2
2022-07-31 19:26:54 +06:00
InsanusMokrassar 66c5451293 now tag in MessageFormatter is a nullable 2022-07-31 19:22:08 +06:00
InsanusMokrassar 77ca9ce25b MessageFormatter factory 2022-07-31 19:19:17 +06:00
InsanusMokrassar 194e52f521 addDefaultKSLog 2022-07-31 19:15:53 +06:00
InsanusMokrassar 34ea0bd91a isolate logging of KSLog'gers to avoid problems with parallel logs perform 2022-07-31 19:13:02 +06:00
InsanusMokrassar 3ff5d989b7 KSLog(SimpleKSLogCallback) 2022-07-31 19:09:01 +06:00
InsanusMokrassar 0e01534562 setDefaultKSLog and KSLog#plus(KSLog) 2022-07-31 19:06:57 +06:00
InsanusMokrassar 35e3d02837 start 0.4.2 2022-07-31 19:01:25 +06:00
InsanusMokrassar b255406dba Update CHANGELOG.md 2022-07-30 17:13:55 +06:00
InsanusMokrassar 88fa4e3c09 Merge pull request #15 from InsanusMokrassar/0.4.1
0.4.1
2022-07-30 17:12:14 +06:00
InsanusMokrassar 4d9481f07b overloads resolution ambiguities issues fixes 2022-07-30 16:44:04 +06:00
InsanusMokrassar 54e448f5a0 start 0.4.1 2022-07-30 15:44:02 +06:00
InsanusMokrassar 8729acfff3 Update github_release.gradle 2022-07-30 15:20:00 +06:00
InsanusMokrassar 923add4550 Merge pull request #14 from InsanusMokrassar/0.4.0
0.4.0
2022-07-30 15:16:21 +06:00
InsanusMokrassar 8dc96f708e fixes 2022-07-30 15:12:46 +06:00
InsanusMokrassar 4d98b7df18 rewrite way of filters work 2022-07-30 14:56:26 +06:00
InsanusMokrassar be48c2559e taggedLogger 2022-07-30 14:15:55 +06:00
InsanusMokrassar 437b1675e2 accept string messages -> accept any object as message 2022-07-30 14:12:45 +06:00
InsanusMokrassar 7fa929fb7a change version to 0.4.0 2022-07-30 14:03:11 +06:00
InsanusMokrassar 9c8d8899b1 update gradle wrapper 2022-07-30 13:51:22 +06:00
InsanusMokrassar 1d16902e69 start 0.3.3 2022-07-30 13:51:06 +06:00
InsanusMokrassar a0982dbd83 Merge pull request #13 from InsanusMokrassar/0.3.2
0.3.2
2022-06-24 00:21:32 +06:00
InsanusMokrassar 6fa68b1e94 update github library dependency 2022-06-23 19:11:09 +06:00
InsanusMokrassar 191433cc75 extend extensions of logging 2022-06-23 19:10:35 +06:00
InsanusMokrassar 5434971487 start 0.3.2 2022-06-23 16:26:46 +06:00
InsanusMokrassar b819a68cb5 Merge pull request #12 from InsanusMokrassar/0.3.1
0.3.1
2022-06-15 16:43:21 +06:00
InsanusMokrassar acb9b350ba Update CHANGELOG.md 2022-06-15 16:16:06 +06:00
InsanusMokrassar 65834ffcb4 Update gradle.properties 2022-06-15 16:15:37 +06:00
InsanusMokrassar 7f63d30274 Update build.gradle 2022-06-15 16:14:59 +06:00
InsanusMokrassar 81af45ba5d Update README.md 2022-06-12 02:44:29 +06:00
InsanusMokrassar caf6a2d05c Merge pull request #11 from InsanusMokrassar/0.3.0
0.3.0
2022-06-09 16:37:19 +06:00
InsanusMokrassar effeaa7ef7 decrease android_code_version 2022-06-09 16:35:35 +06:00
InsanusMokrassar 602ba0e1c8 fill changelog 2022-06-09 16:34:43 +06:00
InsanusMokrassar de7f735ad0 fixes 2022-06-09 16:29:56 +06:00
InsanusMokrassar 10576d32f6 start implement lazy messages api 2022-06-09 09:56:27 +06:00
InsanusMokrassar 88859ffab8 start 0.2.1 2022-06-09 09:41:41 +06:00
InsanusMokrassar 240dec3935 Update README.md 2022-06-09 02:41:13 +06:00
InsanusMokrassar 444b91676d Update README.md 2022-06-09 02:06:05 +06:00
InsanusMokrassar 33a0d9032f Merge pull request #8 from InsanusMokrassar/0.2.0
0.2.0
2022-06-08 22:53:52 +06:00
76 changed files with 4771 additions and 461 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
*.kt text eol=lf
kotlin text eol=lf
kotlin.bat text eol=crlf
+60
View File
@@ -0,0 +1,60 @@
name: Build and test
on:
push:
pull_request:
permissions:
contents: read
concurrency:
group: build-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
KOTLIN_CLI_NO_WELCOME_BANNER: "1"
jobs:
validation:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
with:
distribution: temurin
java-version: "17"
cache: gradle
- uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
with:
packages: platform-tools
- name: Install the declared Android SDK
run: sdkmanager "platforms;android-37.0" "build-tools;37.0.0"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.cache/JetBrains/Kotlin
key: kotlin-toolchain-${{ runner.os }}-${{ hashFiles('kotlin', 'kslog/module.yaml', 'project.yaml', 'gradle/libs.versions.toml', 'plugins/**/*.yaml', 'plugins/**/*.kt') }}
restore-keys: |
kotlin-toolchain-${{ runner.os }}-
- name: Check release/version consistency
run: |
python3 verification/check_release_consistency.py
bash verification/test_changelog_parser.sh
python3 verification/check_wrapper_line_endings.py
- name: Load Kotlin Toolchain model
run: ./kotlin show modules
- name: Validate Gradle Dokka output overrides
run: |
./gradlew --no-daemon help --task dokkaHtml -PDOKKA_PATH="$RUNNER_TEMP/kslog-dokka-property"
DOKKA_PATH="$RUNNER_TEMP/kslog-dokka-environment" ./gradlew --no-daemon help --task dokkaHtml
- name: Build Kotlin Toolchain targets
run: ./kotlin build
- name: Run Kotlin Toolchain checks
run: ./kotlin check
- name: Build the complete retained Gradle fallback
run: ./gradlew --no-daemon clean build
- name: Verify isolated Gradle publication and consumers
run: bash verification/verify_gradle_fallback.sh
+60 -8
View File
@@ -1,21 +1,73 @@
name: Publish KDocs
on:
push:
branches:
- master
permissions:
contents: read
concurrency:
group: kdocs-${{ github.ref }}
cancel-in-progress: false
env:
KOTLIN_CLI_NO_WELCOME_BANNER: "1"
jobs:
publishing:
generate:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
java-version: 11
- name: Build
run: ./gradlew dokkaHtml
persist-credentials: false
- uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
with:
distribution: temurin
java-version: "17"
- uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
with:
packages: platform-tools
- name: Install the declared Android SDK
run: sdkmanager "platforms;android-37.0" "build-tools;37.0.0"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.cache/JetBrains/Kotlin
key: kotlin-toolchain-${{ runner.os }}-${{ hashFiles('kotlin', 'kslog/module.yaml', 'project.yaml', 'gradle/libs.versions.toml', 'plugins/dokka/**/*.yaml', 'plugins/dokka/**/*.kt') }}
restore-keys: |
kotlin-toolchain-${{ runner.os }}-
- name: Materialize platform analysis classpaths
run: ./kotlin build -m kslog -p js -p wasmJs
- name: Generate KDocs
run: ./kotlin do dokkaHtml
- name: Upload generated KDocs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: kdocs-html
path: build/dokka/html
if-no-files-found: error
retention-days: 1
deploy:
needs: generate
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Download generated KDocs
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: kdocs-html
path: kdocs-html
- name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build/dokka/html
publish_dir: ./kdocs-html
publish_branch: kdocs
-27
View File
@@ -1,27 +0,0 @@
name: Publish package to GitHub Packages
on: [push]
jobs:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
rm gradle.properties
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
- name: Publish
continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
env:
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
+3 -1
View File
@@ -1,4 +1,6 @@
.idea
.kotlin
kotlin-js-store/
out/*
*.iml
target
@@ -12,6 +14,6 @@ out/
secret.gradle
local.*
local/
kotlin-js-store/
**/*.local.*
publishing.sh
+186
View File
@@ -1,5 +1,191 @@
# Changelog
## 2.1.0
* `Versions`:
* `Kotlin`: `2.4.10` -> `2.4.20`
* `com.gradleup.nmcp.aggregation`: `1.6.1` -> `1.6.2`
* `io.github.ben-manes.versions`: `0.61.0` -> `0.64.0`
## 2.0.0
* `Versions`:
* `Kotlin`: `2.3.21` -> `2.4.10`
## 1.7.0
* `Kotlin`: `2.3.21`
## 1.6.1
* `Kotlin`: `2.3.20`
## 1.6.0
* `Kotlin`: `2.3.0`
## 1.5.2
* `Kotlin`: `2.2.21`
## 1.5.1
* `Kotlin`: `2.2.20`
## 1.5.0
* `Kotlin`: `2.2.0`
## 1.4.2
* `Kotlin`: `2.1.21`
* Android SDK Level: `36`
## 1.4.1
* `Kotlin`: `2.1.10`
## 1.4.0
* `Kotlin`: `2.1.0`
## 1.3.6
* `Kotlin`: `2.0.20`
## 1.3.5
* `Kotlin`: `2.0.10`
* Android SDK Level: `35`
## 1.3.4
* `Kotlin`: `1.9.24`
* Native realization of `KSLoggerDefaultPlatformLoggerLambda` now is common for all native targets
## 1.3.3
* `Kotlin`: `1.9.23`
## 1.3.2
* `Kotlin`: `1.9.22`
## 1.3.1
* `Kotlin`: `1.9.21`
## 1.3.0
**THIS UPDATE CONTAINS CHANGES OF LOG LEVELS ACCORDANCES. LOOK AT THE LEVELS ACCORDANCE IN THE [DOCUMENTATION](https://docs.inmo.dev/kslog/logging.html#logging)
FOR MORE INFO**
* Add `trace` logging level
* Change overall table of log level accordance
* Now you may change default platform logger
## 1.2.4
**This update contains migration onto gradle 8+ (for both wrapper and android plugin). Use it with caution**
## 1.2.3
* `Kotlin`: `1.9.20`
## 1.2.2
**wasm32 target has been removed**
**mingwX86 target has been removed**
* `Kotlin`: `1.9.20-RC`
## 1.2.1
* `Kotlin`: `1.9.10`
* Latest supported android sdk now is `34`
## 1.2.0
* `Kotlin`: `1.9.0`
## 1.1.2
* `Kotlin`: `1.8.22`
## 1.1.1
* Rollback `KSLog` with `String` arg factory to use `DefaultKSLog`
## 1.1.0
* `Kotlin`: `1.8.20`
* Standard factory `KSLog` with `String` arg now use `TagLogger`
## 1.0.0
* `Kotlin`: `1.8.0`
## 0.5.4
* `Kotlin`: `1.7.22`
## 0.5.3
* `Kotlin`: `1.7.20`
## 0.5.2
* Add support of next platform:
* Linux ARM x64
* Linux ARM x32 HFP
* Linux x64
* WASM x32
* MinGW x64
* MinGW x86
## 0.5.1
* New property `defaultMessageFormatterWithErrorPrint` for messages format with errors
## 0.5.0
* `Kotlin`: `1.7.10`
## 0.4.2
* Add `setDefaultKSLog` and `addDefaultKSLog`
* Add `plus` operation for two `KSLog` instances to call them both on each log performing
* Add `KSLog` factory for simple creation of `CallbackKSLog`
* Add `MessageFormatter` factory
## 0.4.1
* Fixes in resolution ambiguity for functions with messages
## 0.4.0
**THIS UPDATE CONTAINS BREAKING CHANGES**
* **BREAKING CHANGE** Any `KSLog` now accept any object. It was made to allow to work with structured logging
* New factory for `TagLogger`: fun `taggedLogger`
* New logger has been added: `TypedLogger`
* New logger has been added: `FilterLogger`
* **BREAKING CHANGE** Logger `DefaultKSLog` lost its constructor with `filter`. Currently, it is still available as factory function
## 0.3.2
* Add duplication functions for all extensions which allow to send log without tag but with exception and message builder
## 0.3.1
* Technical fixes for compatibility with Java 8
## 0.3.0
* **BREAKING CHANGE**: Filters will not accept message since this update
* New `KSLog` realization `DefaultKSLog`
* Now platforms realize their default loggers callbacks instead of `KSLog` factories
## 0.2.0
* Full rewrite of API
+148
View File
@@ -0,0 +1,148 @@
# Developing KSLog
## Prerequisites
- JDK 17
- Android SDK platform 37 and build tools 37.0.0
(`sdkmanager` package IDs: `platforms;android-37.0` and `build-tools;37.0.0`;
the library's `compileSdk` remains 37)
- Git
- Bash and Python 3 for the committed compatibility verifiers
The checked-in `kotlin` and `kotlin.bat` wrappers download the pinned Kotlin
Toolchain distribution into the normal user cache. No global Toolchain install
is required.
## Build and test
```shell
./kotlin show modules
./kotlin clean
./kotlin build
./kotlin check
```
Build one supported platform with `./kotlin build -p <platform>`, where the
declared platforms are `android`, `js`, `jvm`, `linuxArm64`, `linuxX64`,
`mingwX64`, and `wasmJs`. Run the real JVM tests directly with:
```shell
./kotlin test -p jvm
```
Kotlin Toolchain 0.13.0 does not expose `linuxArm32Hfp`. Until that target and
the existing `kslog-wasm-js` artifact name can be represented, the Gradle build
remains the full-contract fallback:
```shell
./gradlew --no-daemon build
./gradlew --no-daemon compileKotlinLinuxArm32Hfp
```
Do not remove the Gradle path merely because the supported Toolchain targets
compile. See `MIGRATION_REPORT.md` for the exact capability gaps and validation
matrix.
Run the release/version preflight independently with:
```shell
python3 verification/check_release_consistency.py
bash verification/test_changelog_parser.sh
```
CI and the explicit GitHub release command run the same consistency check.
## Documentation
```shell
./kotlin build -m kslog -p js -p wasmJs
./kotlin do dokkaHtml
./kotlin do dokkaGfm
```
The outputs are `build/dokka/html` and `build/dokka/gfm`. CI only deploys HTML
documentation from `master`; pull requests and other branch pushes never deploy
documentation. Toolchain 0.13.0 exposes only the JVM module classpath to local
plugins, so the platform build first materializes the transformed Common plus
matching JS and WasmJS standard-library KLIBs in the Toolchain cache. The Dokka
plugin discovers those KLIBs from the resolved JVM stdlib version and fails with
the prebuild command above if any one is missing.
The legacy Gradle-only `DOKKA_PATH` property/environment override is not
implemented by the Toolchain plugin. Toolchain documentation is deliberately
restricted to generated directories under `build/dokka` so cleanup cannot
target an arbitrary external path. Use the retained Gradle path when that
legacy override is required.
## Local publication and compatibility testing
Publication is enabled for the KSLog module, while the two local build-plugin
modules are intentionally not publishable. Select the library module explicitly:
```shell
isolated_repo="$(mktemp -d)"
KOTLIN_CLI_JAVA_OPTIONS="-Dmaven.repo.local=$isolated_repo" \
KOTLIN_TOOLCHAIN_SIGNING_KEY="$(gpg --armor --export-secret-keys YOUR_KEY_ID)" \
./kotlin publish -m kslog mavenLocal
```
Use a throwaway key for local validation. Never use the command above without
the isolated Maven path for version 2.1.0, because it would overwrite the normal
local copy of that released version.
The retained Gradle publication can be checked without credentials or signing
keys against an archive of the exact migration base:
```shell
bash verification/verify_gradle_fallback.sh
```
The verifier creates isolated Gradle user homes and Maven repositories, checks
the baseline/current 46-file publication inventories, source JARs, required POM
metadata, source bytes, JVM/Android public method and field signatures, module
identities, and compiles the committed consumer probe. It never writes to the
normal Maven local repository.
## Remote publication and release
The validation workflow used for pushes and pull requests never publishes
packages or creates tags/releases. The separate `master`-only KDocs workflow
intentionally deploys generated HTML to the `kdocs` branch after a read-only
generation job succeeds. Toolchain remote package publication is disabled in
the resolved model: the module exposes only `mavenLocal`, has no Inmo Nexus
repository/credentials, and does not enable Maven Central. `mavenLocal` is not
inherently isolated; without the `-Dmaven.repo.local=...` JVM override shown
above it writes to the user's normal Maven Local repository. There is no
configured Toolchain destination that uploads packages remotely.
Do not re-enable Toolchain Nexus or Central publication until both the missing
Linux ARM32 target and the `kslog-wasm-js` coordinate have compatible
implementations and the complete publication diff has been reviewed again.
The retained Gradle build is the only full-target remote publication path. Its
existing release tasks remain explicit maintainer operations and are not run by
CI:
```shell
INMONEXUS_USER=... INMONEXUS_PASSWORD=... \
./gradlew --no-daemon publishAllPublicationsToInmoNexusRepository
SONATYPE_USER=... SONATYPE_PASSWORD=... \
./gradlew --no-daemon publishAllPublicationsToSonatypeRepository
```
Those commands require the existing Gradle/GPG release configuration and cause
real uploads. They are documented only to preserve the fallback release
contract; do not run them as migration validation.
Preview the GitHub release payload without network mutation:
```shell
KSLOG_GITHUB_RELEASE_DRY_RUN=true ./kotlin do githubRelease
```
A real release requires `GITHUB_RELEASE_TOKEN`, `GH_TOKEN`, or `GITHUB_TOKEN`
and an explicit `./kotlin do githubRelease` invocation. The command first runs
the release/version consistency preflight, then delegates to the standard
`gh release create` CLI. It may create the remote tag when that tag is absent;
it does not alter local git state. Validation CI never invokes it.
+159
View File
@@ -0,0 +1,159 @@
# KSLog Gradle to Kotlin Toolchain migration plan
This inventory was written before adding any Kotlin Toolchain YAML. It records the
Gradle contract at commit `0b9fb30862c7319bb228039ee33eef9a0ddef95c` and is the
checklist for the migration.
## Pinned inputs
- Kotlin Toolchain release: `v0.13.0`, source commit
`abc7a5f7579e9ef267107782048c76777f0bf932`.
- Official wrapper from that tag: CLI distribution `0.13.0-dev-4444`, SHA-256
`13c2448146ac6dba0fa3a4e5d367dab73d8cfd7400bab348bebe905c165adecf`.
- Kotlin: `2.4.20` (set explicitly even though the release defaults to it).
- Dokka: `2.2.0`, source commit
`656ca46fbbd676d872b0bd383042fc12ae7adcdd`.
- Migration guidance: the four official Kotlin agent skills installed from
`Kotlin/kotlin-agent-skills@087f5689c1934135ed58718cdf524525e452b7e9`.
The pinned Toolchain documentation and source confirm that nested module
templates are supported in 0.13.0. This resolves the contradictory statement in
the core agent skill, although this one-module project does not need a template
hierarchy.
## Existing project contract
### Source and API
- One Kotlin Multiplatform library with 20 production Kotlin files and no tests.
- Source sets: `commonMain`, `jvmMain`, `androidMain`, `jsMain`, `wasmJsMain`,
and `nativeMain`.
- Production source bytes must remain unchanged. Moving source roots to the
stable `kslog` Toolchain module's KMP layout is allowed; legacy Gradle
source-set paths will be mapped to those directories while the compatibility
fallback remains.
- Public package/API/behavior must not change.
### Targets and execution environments
| Existing declaration | Required Toolchain mapping | Status in Toolchain 0.13.0 |
|---|---|---|
| JVM, bytecode 8 | `jvm`, target-specific `-jvm-target=1.8` | Supported; verified class major 52 |
| Android, bytecode 17 | `android`, target-specific `-jvm-target=17` | Supported; verified class major 61 |
| Android min/compile/build tools 21/37/37.0.0 | `settings.android` | Supported |
| JS IR, browser + Node.js | `js` | Library compilation/publication supported; runtime selection is not configurable |
| WasmJS, browser + Node.js + d8 | `wasmJs` | Library compilation/publication and browser tests supported; Node.js/d8 runners are not exposed |
| linuxX64 | `linuxX64` | Supported |
| linuxArm64 | `linuxArm64` | Supported (cross-compilation on this host) |
| linuxArm32Hfp | none | **Blocked:** not present in the 0.13.0 platform schema |
| mingwX64 | `mingwX64` | Supported (cross-compilation on this host) |
| Shared native implementation | `src@native` | Supported |
The pinned `Platform` enum omits `linuxArm32Hfp`; a minimal model-load probe
reports an empty effective platform list at that value. The probe remains
outside the active project model and its result is recorded in
`MIGRATION_REPORT.md`.
Toolchain 0.13.0 does not allow `settings.jvm.release` to vary by platform, so
the common value is unset and the JVM/Android compiler targets are supplied as
platform-specific Kotlin arguments. Inspected output proves the required
bytecode levels.
### Build plugins and custom logic
| Gradle plugin/feature | Decision | Replacement/justification |
|---|---|---|
| Kotlin Multiplatform | Native | `product.type: kmp/lib` and explicit platforms |
| Android KMP library plugin | Native | Android platform in the KMP product |
| Maven Publish | Native for isolated local validation | `settings.publishing` plus the sole publish repository `mavenLocal`; Toolchain remotes stay disabled while parity is blocked |
| NMCP aggregation/Central Portal | Retained in Gradle only | Toolchain `mavenCentral` remains at its disabled default; the complete Gradle fallback preserves the existing remote path |
| Signing plugin/GPG command | Native | `signArtifacts` and Toolchain signing environment variables |
| Dokka Gradle plugin | Local plugin | Vendor/extend the official Toolchain Dokka build plugin; add KMP source sets, HTML and GFM commands |
| GitHub release Gradle plugin | Local plugin using standard `gh` CLI | Explicit `githubRelease` command, dry-run support, no automatic release |
| Ben Manes versions plugin | Drop | Dependency maintenance only; Renovate remains |
| Dexcount | Drop from Toolchain path | No library/API/publication output; the Toolchain has no native equivalent |
| `javadocsJar` | Partial native publication behavior | Toolchain 0.13.0 publishes placeholder Javadoc JARs for platform artifacts but omits the root `kslog-2.1.0-javadoc.jar` |
| `changelog_parser.sh` | Preserve | Used by the GitHub release command |
The official Toolchain Dokka plugin was reviewed first. It explicitly says it
does not support KMP and only exposes HTML. The local adaptation keeps its
Apache-2.0 headers and provenance, models the actual KSLog source-set graph, and
adds the official Dokka GFM plugin. No existing Toolchain/GitHub-release plugin
was found; `gh` is already installed and is the standard implementation used by
the thin release action.
### Publication contract
- Group/version/base artifact: `dev.inmo:kslog:2.1.0`.
- Existing variant artifact IDs:
- `kslog-android`
- `kslog-js`
- `kslog-jvm`
- `kslog-linuxarm32hfp`
- `kslog-linuxarm64`
- `kslog-linuxx64`
- `kslog-mingwx64`
- `kslog-wasm-js`
- Publish KMP root metadata, per-platform artifacts, sources JARs, and empty
javadoc JARs.
- Preserve POM name/description/homepage, both developers, MIT license, and SCM
URL/developer connection.
- Preserve complete Inmo Nexus/Maven Central/signing capability through the
retained Gradle fallback and preserve the explicit GitHub release command.
Toolchain remote repositories stay absent/disabled until parity is restored.
Never run a remote publication/release as migration validation.
- Validate to an isolated Maven local directory via
`-Dmaven.repo.local=<temporary path>`; never overwrite normal `~/.m2` artifacts.
Known 0.13.0 publication mismatch: built-in Toolchain publication derives the
Wasm artifact suffix from `wasmJs` as `-wasmjs`, while Kotlin Gradle publishes
`-wasm-js`. Together with the missing Linux ARM32 target, this prevents a
publication-compatible cutover. The legacy Gradle files therefore remain until
these two blockers are resolved; the Toolchain implementation is a reversible,
working supported-target migration rather than a claim of full parity.
The stable `kslog` module directory plus narrow compiler-identity overrides
preserve the existing JVM Kotlin module and KLIB unique/short names. Remaining
built-in POM, Javadoc-placeholder, AAR-layout, and JS manifest-shape differences
are itemized in `MIGRATION_REPORT.md`.
### CI and documentation contract
- Replace the stale push workflow that names nonexistent Gitea and
GitHub-Packages Gradle tasks with build/check validation only.
- Run the complete retained Gradle build plus an isolated baseline/current
local-publication, public-signature, and consumer verifier in validation CI.
- Push and pull-request validation must not publish artifacts, docs, tags, or
releases.
- Keep documentation deployment master-only and change generation to the
Toolchain `dokkaHtml` command with output `build/dokka/html`.
- Generate docs in a read-only job without persisted checkout credentials, then
deploy the generated artifact in a separate write-enabled job.
- Keep `dokkaGfm` with output `build/dokka/gfm` for local/consumer workflows.
- Remote publication and GitHub release remain explicit manual commands and are
documented, not automatically broadened.
## Implementation checklist
- [x] Add the checked-in 0.13.0 wrappers.
- [x] Add `project.yaml`, stable `kslog/module.yaml`, and extend the existing version catalog.
- [x] Move source roots byte-for-byte to Toolchain KMP layout.
- [x] Map legacy Gradle source sets to the moved roots while blockers remain.
- [x] Add focused JVM behavior tests so validation is not `NO-SOURCE`.
- [x] Vendor and adapt the official Dokka plugin with source/commit/license.
- [x] Add the narrow GitHub release plugin with a non-network dry-run.
- [x] Load the project model and enumerate modules, platforms, commands, and checks.
- [x] Clean-build every declared Toolchain-supported target.
- [x] Run real tests/checks and distinguish host-unrunnable targets.
- [x] Generate HTML and GFM docs at the preserved paths.
- [x] Publish Toolchain artifacts with an ephemeral signing key to an isolated
Maven repository before disabling all Toolchain remote destinations.
- [x] Inspect artifacts, signatures, POMs, and Gradle module metadata.
- [x] Add a committed verifier that republishes the untouched base and the
post-move Gradle fallback with isolated Gradle homes and no signing secrets.
- [x] Run Gradle KMP and Android consumer smoke tests against the isolated repository.
- [x] Compare production source hashes and JVM/Android public bytecode/API with Gradle.
- [x] Validate CI YAML, shell scripts, and `git diff --check`.
- [x] Rebuild `ast-index` after source moves and edits.
- [x] Record exact commands, results, logs, blockers, and remote-only checks in
`MIGRATION_REPORT.md`.
+278
View File
@@ -0,0 +1,278 @@
# KSLog Kotlin Toolchain migration report
Date: 2026-11-02
Base commit: `0b9fb30862c7319bb228039ee33eef9a0ddef95c`
Kotlin Toolchain: `v0.13.0` source commit
`abc7a5f7579e9ef267107782048c76777f0bf932`
## Result
KSLog now has a checked-in, release-pinned Kotlin Toolchain project that loads,
builds every platform supported by Toolchain 0.13.0, runs real JVM tests,
generates both documentation formats, can publish signed KMP artifacts through
the configured `mavenLocal` destination, and is consumable by Gradle KMP and
Android projects. Migration validation redirects Maven Local to a temporary
repository; the destination itself is not inherently isolated. Toolchain Nexus
credentials/repository configuration is absent and resolved Maven Central
publishing is disabled.
This is **not yet a publication-compatible full cutover**. Toolchain 0.13.0
cannot represent `linuxArm32Hfp` and hard-codes the WasmJS artifact suffix as
`-wasmjs` instead of KSLog's released `-wasm-js`. The Gradle build is therefore
retained, mapped to the moved sources, and validated as the complete target and
publication fallback. No remote artifact, documentation, tag, or release was
published during this migration.
## Implemented layout
- `kotlin` and `kotlin.bat` preserve the official 0.13.0 wrapper content. Git
attributes enforce LF for `kotlin`/Kotlin sources and CRLF for `kotlin.bat`.
The wrappers select CLI `0.13.0-dev-4444` with the official pinned
distribution SHA-256.
- `project.yaml` registers the stable `kslog` library module and two local build
plugin modules. The stable directory name prevents compiler module identity
from depending on the checkout/worktree directory name.
- `kslog/module.yaml` declares Android, JS, JVM, WasmJS, Linux x64/ARM64, and
MinGW x64, plus Android/JDK/Kotlin settings and full publication metadata.
- The 20 production files moved byte-for-byte into `kslog/src*` Toolchain
source roots. `build.gradle` maps the retained Gradle source sets to those
roots, including Linux ARM32.
- Compiler module identities are pinned to the existing published identities:
JVM/Android `META-INF/dev.inmo_kslog.kotlin_module`, leaf KLIB
`unique_name=dev.inmo:kslog`, common metadata `kslog_commonMain`, and native
metadata `dev.inmo:kslog_nativeMain`/`kslog_nativeMain`.
- `kslog/test@jvm/CallbackKSLogTest.kt` adds two focused tests covering exact
callback forwarding and rejected lazy-message evaluation.
- `plugins/dokka` adapts the official Apache-2.0 Toolchain Dokka plugin for
KSLog's KMP graph and Dokka 2.2.0 HTML/GFM output. Each adapted Apache source
carries a prominent KSLog modification/provenance notice, and cleanup rejects
symbolic-link ancestors and requires real-path containment. Platform analysis
receives JVM/common, JS, and WasmJS standard-library classpaths. Because the
plugin API only exposes the JVM module classpath, the documented JS/WasmJS
prebuild materializes the transformed Common and matching platform KLIBs for
deterministic discovery. Generation
fails before deployment if unresolved-type placeholders remain.
- `plugins/github-release` provides an explicit `gh release create` command and
a non-network dry-run mode. It runs the committed version consistency
preflight first; no workflow invokes it.
- `DEVELOPMENT.md` documents Toolchain use, the Gradle fallback, isolated local
publication, disabled Toolchain remotes, the retained Gradle release path,
and release gating.
- `verification/` contains portable release/parser/wrapper checks plus the
baseline/current Gradle publication, source-identity, public-signature, and
consumer verifier used by CI.
## Target and test matrix
| Target | Toolchain result | Runtime/test result | Contract notes |
|---|---|---|---|
| JVM | PASS | 2 tests found, 2 started, 2 passed | Bytecode major 52; 25-class list and public JVM method/field signatures from `javap -public -s -constants` match Gradle |
| Android | PASS | No device tests in the project; Android consumer compilation PASS | Bytecode major 61; 24-class list and public JVM method/field signatures match Gradle; minSdk 21, compileSdk/build tools 37 |
| JS | PASS | No test sources; Toolchain 0.13.0 exposes no browser/Node runner selection for a library | Gradle fallback keeps browser + Node declarations |
| WasmJS | PASS | No test sources; Toolchain 0.13.0 exposes no Node/d8 selection and only has browser test support | Gradle fallback keeps browser + Node + d8 declarations |
| linuxX64 | PASS | No test sources | Host-compilable KLIB |
| linuxArm64 | PASS | Cross-compiled; not runnable on this x64 host | KLIB publication produced |
| mingwX64 | PASS | Cross-compiled; not runnable on this Linux host | KLIB publication produced |
| linuxArm32Hfp | **UNSUPPORTED** | Gradle clean build compiles it; no tests | Absent from Toolchain 0.13.0 `Platform`; minimal model probe fails |
`./kotlin check` did not mistake empty platform suites for passing tests: the
report only claims the two JVM tests. Gradle reports JS, WasmJS, Android, and
native tests as `NO-SOURCE` or skipped, while still compiling all declared
targets, including Linux ARM32.
## Verification evidence
The commands below are reproducible from the repository root. Temporary build
logs and repositories are intentionally not part of the published report.
### Model, build, checks, and compatibility
| Command | Result |
|---|---|
| `./kotlin show modules` | PASS; modules `dokka`, `github-release`, `kslog` |
| `./kotlin show settings -m kslog` plus `./kotlin show tasks` | PASS; `mavenCentral.enabled: false`, only local publish repository configured, no Nexus/Central task references |
| `./kotlin show commands` / `show checks` | PASS; Dokka/release commands and builtin tests enumerated |
| `./kotlin clean && ./kotlin build` | PASS; all seven Toolchain platforms plus both plugin modules |
| `./kotlin check` | PASS; KSLog JVM tests 2/2 plus Dokka guard/classpath tests 4/4 |
| `./kotlin test -m dokka -p jvm` | PASS 4/4; transformed/platform classpath mapping, unresolved-output rejection, symlink containment, and scoped cleanup |
| `./gradlew --no-daemon clean build` | PASS; complete legacy build, including Linux ARM32 |
| `bash verification/verify_gradle_fallback.sh` | PASS; isolated baseline/current Gradle publication and committed consumer probe |
| `python3 verification/check_release_consistency.py` and parser/wrapper checks | PASS; four version copies, literal changelog heading, LF/CRLF attributes |
| Linux ARM32 minimal model probe | Expected failure: `Product platforms list should not be empty` at `linuxArm32Hfp` |
| GitHub YAML, actionlint 1.7.7, shell syntax, `git diff --check` | PASS |
| `ast-index rebuild` | PASS after final edits |
The Toolchain emits the existing insignificant inline warning. It also warns
that `-module-name`/`-Xshort-module-name` are passed more than once because the
built-in checkout-derived names are intentionally overridden; inspection of
the output JAR/KLIB manifests proves the last values match the released Gradle
module identities.
### Source and ABI preservation
- All 20 production source files match the base commit byte-for-byte after
their moves.
- The JVM 25-class and Android 24-class lists match; public JVM method/field
signature dumps from `javap -public -s -constants` have zero differences.
- Gradle/Toolchain bytecode majors are JVM 52/52 and Android 61/61.
- Published JVM, common/native metadata, and supported leaf KLIB module
identities match Gradle.
These checks are deliberately narrow. Source identity, consumer compilation,
JVM class/signature comparison, and inspected module names are not a complete
Kotlin metadata or KLIB ABI proof. Full cutover remains blocked and requires a
dedicated metadata/KLIB compatibility review in addition to resolving the two
upstream capability gaps.
### Documentation and release command
| Command | Result |
|---|---|
| `./kotlin build -m kslog -p js -p wasmJs` | PASS; materializes the Common/JS/WasmJS analysis KLIBs required by the pinned plugin API |
| `./kotlin do dokkaHtml` | PASS; 156 files at `build/dokka/html`, zero unresolved-type placeholders |
| `./kotlin do dokkaGfm` | PASS; 101 files at `build/dokka/gfm`, zero unresolved-type placeholders |
| Output/link/platform inspection | PASS; canonical source links and Common/JVM/Android/JS/WasmJS/Native tabs |
| `KSLOG_GITHUB_RELEASE_DRY_RUN=true ./kotlin do githubRelease` | PASS; consistency preflight and `v2.1.0` payload, no network mutation |
The legacy `DOKKA_PATH` override is intentionally not ported. It remains
available only through Gradle because Toolchain cleanup is restricted to direct
children of `build/dokka`.
### Isolated publication and consumers
Toolchain publication used a temporary Maven repository selected with the JVM
`maven.repo.local` override, `-m kslog`, and a generated throwaway signing key.
The configured destination is named `mavenLocal` and would use the normal Maven
Local repository without that override. The temporary private key was deleted
after verification, and the normal Maven Local repository was not written.
| Check | Result |
|---|---|
| `./kotlin publish -m kslog mavenLocal` | PASS in an isolated repository |
| Toolchain artifact/signature inventory | 40 primary files and 40 detached signatures; 40/40 signatures verified with the throwaway public key |
| Toolchain required POM metadata comparison | PASS for root and every supported platform artifact |
| Gradle KMP consumer compile | PASS for JVM, JS, WasmJS, linuxX64, linuxArm64, mingwX64 |
| Gradle Android KMP consumer compile | PASS |
| `bash verification/verify_gradle_fallback.sh` | PASS; untouched-base and post-move Gradle outputs each contain the same 46 primary files, nine source JARs, and nine required-metadata POMs; no signing task/key/config is used |
Produced IDs are `kslog`, `kslog-android`, `kslog-js`, `kslog-jvm`,
`kslog-linuxarm64`, `kslog-linuxx64`, `kslog-mingwx64`, and `kslog-wasmjs`.
Root Gradle module metadata, Kotlin Tooling metadata, and sources are present.
Platform Toolchain publications contain placeholder Javadoc JARs, while the
root Toolchain Javadoc JAR is absent. The committed Gradle verifier uses a fresh
Gradle user home with only optional `caches`/`wrapper` links, an empty GnuPG
home, sanitized credential/signing environment variables, and separate Maven
repositories; it neither reads user Gradle signing configuration nor writes to
the normal Maven local repository.
## Exact parity blockers and differences
1. **Linux ARM32:** `linuxArm32Hfp` is absent from the pinned 0.13.0
`Platform` enum and schema. Adding it to `product.platforms` is rejected at
model load, before compilation or publication can be customized.
2. **Wasm artifact coordinate:** built-in KMP publication maps `wasmJs` to
`kslog-wasmjs`; there is no per-platform artifact-suffix override. KSLog's
released coordinate is `kslog-wasm-js`.
3. **Runtime declarations:** JS browser/Node and WasmJS browser/Node/d8 runner
selection are not exposed for this Toolchain library model. Compilation and
publication work; those execution contracts stay in Gradle.
4. **POM shape:** required name/description/URL/developers/license/SCM URL and
developer connection are preserved. Toolchain additionally generates the
SCM `connection`. Its root KMP POM uses `<packaging>pom</packaging>` and a
`kslog-jvm` compile dependency, while Gradle's root POM carries a Kotlin
stdlib runtime dependency. The Toolchain Android POM additionally declares
`kotlin-stdlib-jdk7` and `kotlin-stdlib-jdk8`; its JS POM omits Gradle's
`kotlin-dom-api-compat`; and its Linux ARM64, Linux x64, and MinGW POMs move
the Kotlin stdlib dependency from compile to runtime scope. Gradle module
metadata consumer selection and the committed consumers passed, but direct
POM consumers can observe these dependency/scope changes, so this is not
publication-shape parity and requires review before full cutover.
5. **Root Javadoc artifact:** the 46-file Gradle publication has
`kslog-2.1.0-javadoc.jar`; the 40-file Toolchain publication omits it. This is
the sixth missing primary file in addition to the five Linux ARM32 files.
Toolchain platform Javadoc JARs contain a manifest plus a 78-byte `README.md`
placeholder, while Gradle's Javadoc JARs contain only the manifest.
6. **Android manifest and AAR layout:** Gradle's manifest declares only
`minSdkVersion=21`; Toolchain additionally emits `targetSdkVersion=37`.
Gradle uses `classes.jar`; Toolchain uses
`libs/kslog-jvm.jar`. Both are valid AAR layouts, the bytecode level and
compared public JVM method/field signatures match, and an Android KMP
consumer compiles successfully.
7. **JS manifest detail:** public KLIB identity matches
`unique_name=dev.inmo:kslog`, but Toolchain's JS/Wasm KLIB does not include
Gradle's `jsOutputName=kslog` manifest entry.
8. **Android resource exclusions:** `resourcePackaging` is not accepted for a
Toolchain `kmp/lib`. The produced AAR contains only the manifest and library
JAR, so none of the three legacy serialization metadata exclusions are
currently present. Gradle keeps the exclusions for the fallback.
9. **Per-platform JVM release:** Toolchain 0.13.0 does not allow `jvm.release`
to differ by platform. The module uses target-specific `-jvm-target=1.8`
and `-jvm-target=17`; inspected bytecode proves majors 52 and 61.
10. **Documentation output override:** Gradle honors `DOKKA_PATH`; Toolchain
intentionally does not because arbitrary external cleanup would weaken the
generated-output containment guarantee.
Because items 1 and 2 change released target/coordinate contracts, remote
Toolchain publication must remain blocked until Toolchain gains the required
capabilities or an explicitly reviewed compatibility implementation is added.
## CI behavior
- `.github/workflows/build.yml` replaces the stale workflow that attempted
nonexistent Gitea/GitHub-Packages Gradle tasks on every push. Pushes and pull
requests now run release/parser/wrapper preflights, Toolchain build/check, the
complete retained Gradle build, and the isolated publication/API/consumer
verifier. Checkout fetches full history so the verifier's pinned migration
base commit is available. It has read-only repository permissions and no
secrets or remote publication commands.
- `.github/workflows/kdocs.yml` remains `master`-only. Its read-only generation
job checks out with `persist-credentials: false`, materializes the platform
analysis KLIBs, runs Toolchain/Dokka, and
uploads the generated artifact. A separate write-enabled job downloads only
that artifact and deploys it to `kdocs`; project/plugin code never runs with a
write credential.
- All workflow actions are pinned to resolved commit SHAs.
- Hosted CI status is reported on [draft PR #136](https://github.com/InsanusMokrassar/KSLog/pull/136); the local evidence above does not imply a hosted pass. SDK setup explicitly requests `platforms;android-37.0` (API 37) and excludes the removed legacy `tools` package.
## Remote-only checks not performed
- Inmo Nexus upload.
- Maven Central bundle upload/validation/release.
- GitHub release creation.
- Documentation deployment to the `kdocs` branch.
- Android device tests and browser/Node/d8 tests (there are currently no such
test sources); Linux ARM64 and MinGW runtime execution on matching hosts.
## Recommended pull request
Suggested title:
> build: add Kotlin Toolchain migration with compatibility fallback
Suggested body:
> ## Summary
> - add the pinned Kotlin Toolchain 0.13.0 wrappers and a stable `kslog` KMP module
> - migrate source roots byte-for-byte and retain a validated Gradle fallback
> - add real JVM tests, KMP-capable Dokka HTML/GFM tasks, and an explicit GitHub release command
> - replace stale package-publishing CI with non-publishing Toolchain and full Gradle validation
> - document isolated publication, release gating, and full verification evidence
>
> ## Validation
> - all Toolchain-supported targets build
> - JVM tests: 2/2 pass
> - retained Gradle clean build passes, including linuxArm32Hfp
> - JVM/Android class lists and public JVM method/field signature diffs are empty; bytecode remains 8/17
> - signed isolated KMP publication verified and consumed by Gradle KMP + Android projects
> - Dokka HTML/GFM and GitHub release dry-run pass
>
> ## Known blockers
> Toolchain 0.13.0 has no linuxArm32Hfp platform and publishes `-wasmjs` instead
> of the released `-wasm-js` coordinate. Therefore Gradle remains the publication
> fallback and remote Toolchain publication is not enabled. See
> `MIGRATION_REPORT.md` for the complete matrix and artifact differences.
This migration is ready only for a **draft PR**. Full Gradle removal is not
review-ready and remains blocked by Toolchain 0.13.0's missing Linux ARM32
platform and incompatible Wasm artifact ID; the narrow checks above do not
waive those requirements.
+55 -4
View File
@@ -1,15 +1,33 @@
# How to use
# KSLog
It is simple and easy-to-use tool for logging on the most popular platforms in Kotlin Multiplatform:
![JVM](https://img.shields.io/badge/JVM-red?style=for-the-badge&logo=openjdk&logoColor=white)
![Android](https://img.shields.io/badge/Android-green?style=for-the-badge&logo=android&logoColor=white)
![Js](https://img.shields.io/badge/JavaScript-323330?style=for-the-badge&logo=javascript&logoColor=F7DF1E)
![ARM x64](https://img.shields.io/badge/ARMx64-0091BD?style=for-the-badge&logo=arm&logoColor=F7DF1E)
![ARM x32](https://img.shields.io/badge/ARMx32-0091BD?style=for-the-badge&logo=arm&logoColor=F7DF1E)
![Linux x64](https://img.shields.io/badge/Linuxx64-FCC624?style=for-the-badge&logo=linux&logoColor=F7DF1E)
It is simple logging tool which is using built-in tools for logging:
[![KDocs](https://img.shields.io/badge/KDocs-323330?style=for-the-badge&logo=Kotlin&logoColor=7F52FF)](https://insanusmokrassar.github.io/KSLog/)
[![Tutorials](https://img.shields.io/badge/Tutorials-0288D1?style=for-the-badge&logo=mkdocs&logoColor=white)](https://docs.inmo.dev/kslog/index.html)
By default, KSLog is using built-in tools for logging on each supported platform:
* `java.util.logging.Logger` for `JVM`
* `android.util.Log` for `Android`
* `Console` for `JS`
But you always may create your logger and customize as you wish:
```kotlin
KSLog.default = KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
// do your logging
}
```
**This library also supports native targets in experimental mode. By default all native targets will use simple printing in the console**
## How to use
### Fast-travel
@@ -22,17 +40,27 @@ KSLog.i("Some message")
KSLog.i("Some tag", "Some message")
// OR
KSLog.i("Some tag", "Some message", IllegalArgumentException("So, that is exception :)"))
// OR
KSLog.i("Some optional tag", Exception("Optional")) { "Lazy inited message" }
// OR
KSLog.iS("Some optional tag", Exception("Optional")) { "Lazy inited message for suspendable calculation of text" }
// OR EVEN
KSLog.l(LogLevel.INFO, "Some tag", "Some message", IllegalArgumentException("So, that is exception :)"))
// OR
KSLog.l(LogLevel.INFO, "Some optional tag", IllegalArgumentException("So, that is exception :)")) { "And lazily inited message" }
```
### A little bit deeper
There are several important things in context of this library:
There are several important "terms" in context of this library:
* Default logger (available via `KSLog`)
* Default logger (available via `KSLog.default` or simply `KSLog`)
* Local logger (can be created via `KSLog` functions and passed anywhere as `KSLog`)
* Logging shortcuts like `KSLog.i`/`KSLog.info`
* Built-in extension `Any.logger` which allow you to create logger binded to the default with the tag based on the class of receiver
* __Be careful with the receivers: if you will use some extension like `apply`, the receiver will be different with your class inside of that `apply`__
Every logging extension (like `KSLog.i`) have its analog with lazy inited message text and the same one with suffix `S` (like `KSLog.iS`) for the suspendable message calculation.
Default logger can be created by passing `defaultTag` and one of variants log level filters: set or minimal loggable level. In `JVM` you also may setup any logger as base logger for default realizations of `KSLog`. Besides, you may use your own callback (on **any target platform**) as output of logging:
@@ -63,3 +91,26 @@ implementation "dev.inmo:kslog:$kslog_version"
<version>${kslog_version}</version>
</dependency>
```
## Building from source
The checked-in `kotlin`/`kotlin.bat` wrappers pin JetBrains Kotlin Toolchain
0.13.0. With JDK 17 and Android SDK 37 installed, the main validation commands
are:
```shell
./kotlin build
./kotlin check
./kotlin do dokkaHtml
```
Kotlin Toolchain 0.13.0 cannot model the existing `linuxArm32Hfp` target and
derives `kslog-wasmjs` instead of the released `kslog-wasm-js` artifact ID.
The Gradle build is retained as a compatibility path until those publication
contracts can be represented without dropping a target or changing consumer
coordinates.
See [DEVELOPMENT.md](DEVELOPMENT.md) for target-specific commands, documentation
generation, and explicitly gated publishing/release procedures. The migration
inventory and verification evidence are in [MIGRATION_PLAN.md](MIGRATION_PLAN.md)
and [MIGRATION_REPORT.md](MIGRATION_REPORT.md).
+72 -12
View File
@@ -19,9 +19,30 @@ buildscript {
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.dokka)
alias(libs.plugins.versions)
alias(libs.plugins.nmcp.aggregation)
}
apply plugin: "com.android.library"
apply plugin: "com.android.kotlin.multiplatform.library"
apply from: "defaultAndroidSettings.gradle"
apply from: "github_release.gradle"
apply from: "publish.gradle"
apply from: "dokka.gradle"
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
nmcpAggregation {
centralPortal {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
validationTimeout = Duration.ofHours(4)
publishingType = System.getenv('PUBLISHING_TYPE') != "" ? System.getenv('PUBLISHING_TYPE') : "USER_MANAGED"
}
publishAllProjectsProbablyBreakingProjectIsolation()
}
}
project.version = "$version"
project.group = "$group"
@@ -33,32 +54,71 @@ repositories {
}
kotlin {
jvm()
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js(IR) {
browser()
nodejs()
}
android {
publishAllLibraryVariants()
project.ext.configureAndroidTarget(delegate)
withHostTest {}
withDeviceTest {}
}
linuxArm64()
linuxArm32Hfp()
linuxX64()
wasm {
browser()
nodejs()
d8()
}
mingwX64()
sourceSets {
commonMain {
kotlin.srcDir("kslog/src")
dependencies {
implementation libs.kotlin
}
}
commonTest {
kotlin.srcDir("kslog/test")
dependencies {
implementation libs.kotlin.test.common
implementation libs.kotlin.test.annotations.common
}
}
androidMain {
kotlin.srcDir("kslog/src@android")
}
jsMain {
kotlin.srcDir("kslog/src@js")
}
jvmMain {
kotlin.srcDir("kslog/src@jvm")
}
jvmTest {
kotlin.srcDir("kslog/test@jvm")
dependencies {
implementation libs.kotlin.test.junit
}
}
nativeMain {
kotlin.srcDir("kslog/src@native")
}
wasmJsMain {
kotlin.srcDir("kslog/src@wasmJs")
}
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
apply from: "defaultAndroidSettings.gradle"
apply from: "github_release.gradle"
apply from: "publish.gradle"
apply from: "dokka.gradle"
+19 -8
View File
@@ -1,21 +1,32 @@
#!/bin/bash
function parse() {
version="$1"
local version="$1"
local line
local found=""
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
do
: # do nothing
while IFS= read -r line; do
if [[ "$line" == "## $version" ]]; then
found="yes"
break
fi
done
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
do
echo "$line"
if [[ -z "$found" ]]; then
printf 'No literal changelog heading found for %s\n' "$version" >&2
return 1
fi
while IFS= read -r line; do
if [[ "$line" == \#* ]]; then
break
fi
printf '%s\n' "$line"
done
}
version="$1"
file="$2"
file="${2:-}"
if [ -n "$file" ]; then
parse "$version" < "$file"
+13 -61
View File
@@ -1,66 +1,18 @@
apply plugin: 'com.getkeepsafe.dexcount'
android {
ext {
jvmKotlinFolderFile = {
String sep = File.separator
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
}
enableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
if (jvmKotlinFolder.exists()) {
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
}
}
disableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
String[] oldDirs = android.sourceSets.main.java.srcDirs
android.sourceSets.main.java.srcDirs = []
for (oldDir in oldDirs) {
if (oldDir != jvmKotlinFolder.path) {
android.sourceSets.main.java.srcDirs += oldDir
}
}
ext.configureAndroidTarget = { target ->
target.compileSdk = libs.versions.android.compileSdk.get().toInteger()
target.buildToolsVersion = libs.versions.android.buildTools.get()
target.minSdk = libs.versions.android.minSdk.get().toInteger()
target.namespace = "${group}.${project.name}"
target.packaging {
resources {
excludes.add 'META-INF/kotlinx-serialization-runtime.kotlin_module'
excludes.add 'META-INF/kotlinx-serialization-cbor.kotlin_module'
excludes.add 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
}
compileSdkVersion libs.versions.android.compileSdk.get().toInteger()
buildToolsVersion libs.versions.android.buildTools.get()
defaultConfig {
minSdkVersion libs.versions.android.minSdk.get().toInteger()
targetSdkVersion libs.versions.android.compileSdk.get().toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
}
buildTypes {
release {
minifyEnabled false
}
debug {
debuggable true
}
}
packagingOptions {
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
String sep = File.separator
main.java.srcDirs += "src${sep}main${sep}kotlin"
}
target.compilerOptions.jvmTarget.set(
target.compilerOptions.jvmTarget.get().class.fromTarget("17")
)
}
+2 -2
View File
@@ -1,10 +1,10 @@
Object callback = {
switch (true) {
case project.hasProperty("DOKKA_PATH"):
outputDirectory = project.property("DOKKA_PATH").toString()
outputDirectory.set(file(project.property("DOKKA_PATH").toString()))
break
case System.getenv("DOKKA_PATH") != null:
outputDirectory = System.getenv("DOKKA_PATH")
outputDirectory.set(file(System.getenv("DOKKA_PATH")))
break
}
+25 -11
View File
@@ -1,9 +1,15 @@
interface InjectedExecOps {
@Inject //@javax.inject.Inject
ExecOperations getExecOps()
}
private String getCurrentVersionChangelog() {
OutputStream changelogDataOS = new ByteArrayOutputStream()
exec {
def injected = project.objects.newInstance(InjectedExecOps)
injected.execOps.exec {
commandLine 'chmod', "+x", './changelog_parser.sh'
}
exec {
injected.execOps.exec {
standardOutput = changelogDataOS
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
}
@@ -11,20 +17,28 @@ private String getCurrentVersionChangelog() {
return changelogDataOS.toString().trim()
}
if (new File(projectDir, "secret.gradle").exists()) {
apply from: './secret.gradle'
def githubTokenVariableName = "GITHUB_RELEASE_TOKEN"
def githubTokenVariableFromEnv = System.getenv(githubTokenVariableName)
def secretFile = new File(projectDir, "secret.gradle")
if (secretFile.exists() || project.hasProperty(githubTokenVariableName) || (githubTokenVariableFromEnv != "" && githubTokenVariableFromEnv != null)) {
if (secretFile.exists()) {
apply from: './secret.gradle'
}
apply plugin: "com.github.breadmoirai.github-release"
def githubReleaseToken = project.hasProperty(githubTokenVariableName) ? project.property(githubTokenVariableName).toString() : githubTokenVariableFromEnv
githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}"
token githubReleaseToken
owner "InsanusMokrassar"
repo "KSLog"
owner = "InsanusMokrassar"
repo = "KSLog"
tagName "${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"
tagName = "v${project.version}"
releaseName = "${project.version}"
targetCommitish = "${project.version}"
body getCurrentVersionChangelog()
body = getCurrentVersionChangelog()
}
}
+3 -4
View File
@@ -1,13 +1,12 @@
kotlin.code.style=official
org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=true
kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx512m
# Project data
group=dev.inmo
version=0.2.0
android_code_version=6
version=2.1.0
android_code_version=44
+29 -12
View File
@@ -1,18 +1,23 @@
[versions]
kotlin = "1.6.21"
kotlin-gradle-plugin = "7.0.4"
kotlin = "2.4.20"
android-gradle-plugin = "9.1.1"
dexcount = "3.1.0"
junit_version = "4.12"
test_ext_junit_version = "1.1.2"
espresso_core = "3.3.0"
nmcp = "1.6.2"
versions = "0.64.0"
dokka = "2.2.0"
dexcount = "4.0.0"
junit_version = "4.13.2"
test_ext_junit_version = "1.3.0"
espresso_core = "3.7.0"
android-minSdk = "21"
android-compileSdk = "32"
android-buildTools = "32.0.0"
android-compileSdk = "37"
android-buildTools = "37.0.0"
github-release = "2.3.7"
github-release = "2.5.2"
[libraries]
@@ -26,13 +31,25 @@ android-test-espresso-core = { module = "androidx.test.espresso:espresso-core",
# buildscript classpaths
android-tools-build = { module = "com.android.tools.build:gradle", version.ref = "kotlin-gradle-plugin" }
android-tools-build = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" }
android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gradle-plugin", version.ref = "dexcount" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "kotlin" }
kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
github-release = { module = "com.github.breadmoirai:github-release", version.ref = "github-release" }
# Kotlin Toolchain local Dokka plugin
dokka-cli = { module = "org.jetbrains.dokka:dokka-cli", version.ref = "dokka" }
dokka-base = { module = "org.jetbrains.dokka:dokka-base", version.ref = "dokka" }
dokka-analysis-kotlin-descriptors = { module = "org.jetbrains.dokka:analysis-kotlin-descriptors", version.ref = "dokka" }
dokka-gfm = { module = "org.jetbrains.dokka:gfm-plugin", version.ref = "dokka" }
kotlinx-html-jvm = "org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.0"
freemarker = "org.freemarker:freemarker:2.3.31"
[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" }
kotlin-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
versions = { id = "io.github.ben-manes.versions", version.ref = "versions" }
nmcp-aggregation = { id = "com.gradleup.nmcp.aggregation", version.ref = "nmcp" }
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Executable
+286
View File
@@ -0,0 +1,286 @@
#!/bin/sh
#
# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
#
# Possible environment variables:
# KOTLIN_CLI_DOWNLOAD_ROOT Maven repository to download the Kotlin CLI dist from.
# default: https://packages.jetbrains.team/maven/p/amper/amper
# KOTLIN_CLI_JRE_DOWNLOAD_ROOT Url prefix to download the JRE to run the Kotlin CLI
# default: https:/
# KOTLIN_CLI_BOOTSTRAP_CACHE_DIR Cache directory to store the extracted JRE and Kotlin CLI distribution
# KOTLIN_CLI_JAVA_HOME JRE to run the Kotlin CLI itself (optional, does not affect compilation)
# KOTLIN_CLI_JAVA_OPTIONS JVM options to pass to the JVM running the Kotlin CLI (does not affect the user's application)
# KOTLIN_CLI_NO_WELCOME_BANNER Disables the first-run welcome message if set to a non-empty value
set -e -u
# The version of the Kotlin Toolchain (and CLI) distribution to provision and use
kotlin_cli_version=0.13.0-dev-4444
# Establish chain of trust from here by specifying exact checksum of Kotlin Toolchain (and CLI) distribution to be run
kotlin_cli_sha256=13c2448146ac6dba0fa3a4e5d367dab73d8cfd7400bab348bebe905c165adecf
KOTLIN_CLI_DOWNLOAD_ROOT="${KOTLIN_CLI_DOWNLOAD_ROOT:-https://packages.jetbrains.team/maven/p/amper/amper}"
die() {
echo >&2
echo "$@" >&2
echo >&2
exit 1
}
# usage: check_sha SOURCE_MONIKER FILE SHA_CHECKSUM SHA_SIZE
# $1 SOURCE_MONIKER (e.g. url)
# $2 FILE
# $3 SHA hex string
# $4 SHA size in bits (256, 512, ...)
check_sha() {
sha_size=$4
if command -v shasum >/dev/null 2>&1; then
echo "$3 *$2" | shasum -a "$sha_size" --status -c || {
die "ERROR: Checksum mismatch for $2 (downloaded from $1): expected checksum $3 but got $(shasum --binary -a "$sha_size" "$2" | awk '{print $1}')"
}
return 0
fi
shaNsumCommand="sha${sha_size}sum"
if command -v "$shaNsumCommand" >/dev/null 2>&1; then
# discard the output as sha*sum may print redundant warnings in some versions
echo "$3 *$2" | $shaNsumCommand -w -c >/dev/null 2>&1 || {
die "ERROR: Checksum mismatch for $2 (downloaded from $1): expected checksum $3 but got $($shaNsumCommand "$2" | awk '{print $1}')"
}
return 0
fi
echo "Both 'shasum' and 'sha${sha_size}sum' utilities are missing. Please install one of them"
return 1
}
download_and_extract() {
moniker="$1"
file_url="$2"
file_sha="$3"
sha_size="$4"
cache_dir="$5"
extract_dir="$6"
show_banner_on_cache_miss="$7"
if [ -e "$extract_dir/.flag" ] && [ "$(cat "$extract_dir/.flag")" = "${file_sha}" ]; then
# Everything is up-to-date in $extract_dir, do nothing
return 0;
fi
mkdir -p "$cache_dir"
# Take a lock for the download of this file
short_sha=$(echo "$file_sha" | cut -c1-32) # cannot use the ${short_sha:0:32} syntax in regular /bin/sh
download_lock_file="$cache_dir/download-${short_sha}.lock"
process_lock_file="$cache_dir/download-${short_sha}.$$.lock"
echo $$ >"$process_lock_file"
while ! ln "$process_lock_file" "$download_lock_file" 2>/dev/null; do
lock_owner=$(cat "$download_lock_file" 2>/dev/null || true)
# We use `kill -0` instead of `ps -p` as the first one is more portable
if [ -n "$lock_owner" ] && kill -0 "$lock_owner" >/dev/null; then
echo "Another Kotlin CLI instance (pid $lock_owner) is downloading $moniker. Awaiting the result..."
sleep 1
elif [ -n "$lock_owner" ] && [ "$(cat "$download_lock_file" 2>/dev/null)" = "$lock_owner" ]; then
rm -f "$download_lock_file"
# We don't want to simply loop again here, because multiple concurrent processes may face this at the same time,
# which means the 'rm' command above from another script could delete our new valid lock file. Instead, we just
# ask the user to try again. This doesn't 100% eliminate the race, but the probability of issues is drastically
# reduced because it would involve 4 processes with perfect timing. We can revisit this later.
die "Another Kotlin CLI instance (pid $lock_owner) locked the download of $moniker, but is no longer running. The lock file is now removed, please try again."
fi
done
# shellcheck disable=SC2064
trap "rm -f \"$download_lock_file\"" EXIT
rm -f "$process_lock_file"
unlock_and_cleanup() {
rm -f "$download_lock_file"
trap - EXIT
return 0
}
if [ -e "$extract_dir/.flag" ] && [ "$(cat "$extract_dir/.flag")" = "${file_sha}" ]; then
# Everything is up-to-date in $extract_dir, just release the lock
unlock_and_cleanup
return 0;
fi
if [ "$show_banner_on_cache_miss" = "true" ] && [ -z "${KOTLIN_CLI_NO_WELCOME_BANNER:-}" ]; then
echo
cat <<EOF
Welcome to
@@@ @@@@ @@@ @@@
@@@ @@@@ @@@ @@@ @@@
@@@ #@@@" ,@@@ @@@
@@@ ,@@@% ,@@@@@@@, @@@@@@@@@@ @@@ @@@ @@@ ,@@@@@,
@@@ @@@@ @@@@@%"%@@@@@ @@@@@@@@@@ @@@ @@@ @@@@@@%%@@@@@
@@@@@@% @@@% %@@@ @@@ @@@ @@@ @@@@ %@@%
@@@ @@@@= #@@@ @@@# @@@ @@@ @@@ @@@ @@@
@@@ @@@@ #@@@ @@@# @@@ @@@ @@@ @@@ @@@
@@@ *@@@% @@@@ @@@@ @@@ @@@ @@@ @@@ @@@
@@@ %@@@= %@@@@*,*@@@@% @@@@### @@@ @@@ @@@ @@@
@@@ @@@@ "@@@@@@@" %@@@@@ @@@ @@@ @@@ @@@
EOF
echo
echo "This is the first run of the Kotlin CLI v$kotlin_cli_version, so we need to download the Kotlin Toolchain."
echo "Please give us a few seconds, subsequent runs will be faster."
echo
fi
echo "Downloading $moniker..."
temp_file="$cache_dir/download-file-$$.bin"
rm -f "$temp_file"
if command -v curl >/dev/null 2>&1; then
if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--silent --show-error"; fi
# shellcheck disable=SC2086
curl $CURL_PROGRESS -L --fail --retry 5 --connect-timeout 30 --output "${temp_file}" "$file_url"
elif command -v wget >/dev/null 2>&1; then
if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi
wget $WGET_PROGRESS --tries=5 --connect-timeout=30 --read-timeout=120 -O "${temp_file}" "$file_url"
else
die "ERROR: Please install 'wget' or 'curl', as one of them is required to download $moniker"
fi
check_sha "$file_url" "$temp_file" "$file_sha" "$sha_size"
rm -rf "$extract_dir"
mkdir -p "$extract_dir"
case "$file_url" in
*".zip")
if command -v unzip >/dev/null 2>&1; then
unzip -q "$temp_file" -d "$extract_dir"
else
die "ERROR: Please install 'unzip', which is required to extract $moniker"
fi ;;
*)
if command -v tar >/dev/null 2>&1; then
tar -x -f "$temp_file" -C "$extract_dir"
else
die "ERROR: Please install 'tar', which is required to extract $moniker"
fi ;;
esac
rm -f "$temp_file"
echo "$file_sha" >"$extract_dir/.flag"
# Unlock and cleanup the lock file
unlock_and_cleanup
echo "Download complete."
echo
}
# ********** Project-local version detection **********
# 1. Search upwards for an executable `amper` file and/or `project.yaml`
# Sets wrapper_script to the found wrapper path, or empty string if not found.
find_project_context() {
wrapper_script=""
this_script="$(realpath "$0")"
project_dir=$(pwd)
while [ "$project_dir" != "/" ] && [ -n "$project_dir" ]; do
wrapper_candidate="$project_dir/kotlin"
if [ "$this_script" = "$wrapper_candidate" ]; then
# Found itself (local wrapper case), no need to update any version or search further.
return 1
fi
if [ -f "$wrapper_candidate" ] && [ -x "$wrapper_candidate" ]; then
# Found the wrapper — check that a project context exists alongside it
if [ -f "$project_dir/project.yaml" ] || [ -f "$project_dir/module.yaml" ]; then
wrapper_script="$wrapper_candidate"
return 0
else
echo "WARNING: Found wrapper script '$wrapper_candidate', but no project.yaml or module.yaml near it. Skipping." >&2
# Continue the search
fi
elif [ -f "$project_dir/project.yaml" ]; then
# Found project.yaml but no executable wrapper alongside it
echo "WARNING: Found a project.yaml in '$project_dir', but the wrapper script is missing; using Kotlin Toolchain v$kotlin_cli_version." >&2
return 1
fi
project_dir=$(dirname "$project_dir")
done
# Do not check root '/' - it's an unlikely candidate for a project
return 1
}
parse_project_context() {
# Parse kotlin_cli_version and kotlin_cli_sha256 from "$wrapper_script" without executing it.
parsed_kotlin_cli_version=$(
sed -n 's/^kotlin_cli_version=\([A-Za-z0-9._+-]\{1,\}\)[[:space:]]*$/\1/p' "$wrapper_script" \
| head -n 1
)
parsed_kotlin_cli_sha256=$(
sed -n 's/^kotlin_cli_sha256=\([0-9a-fA-F]\{64\}\)[[:space:]]*$/\1/p' "$wrapper_script" \
| head -n 1
)
if [ -z "$parsed_kotlin_cli_version" ]; then
echo "ERROR: Suspicious local wrapper script: failed to detect the distribution version in '$wrapper_script'" >&2
return 1
fi
if [ -z "$parsed_kotlin_cli_sha256" ]; then
echo "ERROR: Suspicious local wrapper script: failed to detect the distribution checksum in '$wrapper_script'" >&2
return 1
fi
# overwrite builtin values and proceed
kotlin_cli_version=$parsed_kotlin_cli_version
kotlin_cli_sha256=$parsed_kotlin_cli_sha256
return 0
}
if [ -z "${KOTLIN_CLI_WRAPPER_ALWAYS_USE_INTRINSIC_VERSION:-}" ]; then
find_project_context && parse_project_context
fi
# ********** System detection **********
kernelName=$(uname -s)
case "$kernelName" in
Darwin* )
default_kotlin_cli_cache_dir="$HOME/Library/Caches/JetBrains/Kotlin/cli"
;;
Linux* )
default_kotlin_cli_cache_dir="$HOME/.cache/JetBrains/Kotlin/cli"
;;
CYGWIN* | MSYS* | MINGW* )
if command -v cygpath >/dev/null 2>&1; then
default_kotlin_cli_cache_dir=$(cygpath -u "$LOCALAPPDATA\JetBrains\Kotlin\cli")
else
die "The 'cypath' command is not available, but the Kotlin CLI needs it. Use kotlin.bat instead, or try a Cygwin or MSYS environment."
fi
;;
*)
die "Unsupported platform $kernelName"
;;
esac
kotlin_cli_cache_dir="${KOTLIN_CLI_BOOTSTRAP_CACHE_DIR:-$default_kotlin_cli_cache_dir}"
# ********** Provision the Kotlin Toolchain distribution **********
kotlin_cli_url="$KOTLIN_CLI_DOWNLOAD_ROOT/org/jetbrains/kotlin/kotlin-cli/$kotlin_cli_version/kotlin-cli-$kotlin_cli_version-dist.tgz"
kotlin_cli_target_dir="$kotlin_cli_cache_dir/kotlin-cli-$kotlin_cli_version"
download_and_extract "Kotlin Toolchain distribution v$kotlin_cli_version" "$kotlin_cli_url" "$kotlin_cli_sha256" 256 "$kotlin_cli_cache_dir" "$kotlin_cli_target_dir" "true"
# ********** Launch the Kotlin CLI **********
launcher_script="$kotlin_cli_target_dir/bin/launcher.sh"
KOTLIN_CLI_WRAPPER_PATH="$(realpath "$0")" \
exec /bin/sh "$launcher_script" "$@"
+265
View File
@@ -0,0 +1,265 @@
@echo off
@rem
@rem Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@rem
@rem Possible environment variables:
@rem KOTLIN_CLI_DOWNLOAD_ROOT Maven repository to download the Kotlin CLI dist from
@rem default: https://packages.jetbrains.team/maven/p/amper/amper
@rem KOTLIN_CLI_JRE_DOWNLOAD_ROOT Url prefix to download the Kotlin CLI JRE from.
@rem default: https:/
@rem KOTLIN_CLI_BOOTSTRAP_CACHE_DIR Cache directory to store the extracted JRE and Kotlin CLI distribution
@rem KOTLIN_CLI_JAVA_HOME JRE to run the Kotlin CLI itself (optional, does not affect compilation)
@rem KOTLIN_CLI_JAVA_OPTIONS JVM options to pass to the JVM running the Kotlin CLI (does not affect the user's application)
@rem KOTLIN_CLI_NO_WELCOME_BANNER Disables the first-run welcome message if set to a non-empty value
setlocal
@rem The version of the Kotlin Toolchain distribution to provision and use
set kotlin_cli_version=0.13.0-dev-4444
@rem Establish chain of trust from here by specifying the exact checksum of the Kotlin Toolchain distribution to be run
set kotlin_cli_sha256=13c2448146ac6dba0fa3a4e5d367dab73d8cfd7400bab348bebe905c165adecf
if not defined KOTLIN_CLI_DOWNLOAD_ROOT set KOTLIN_CLI_DOWNLOAD_ROOT=https://packages.jetbrains.team/maven/p/amper/amper
if not defined KOTLIN_CLI_BOOTSTRAP_CACHE_DIR set KOTLIN_CLI_BOOTSTRAP_CACHE_DIR=%LOCALAPPDATA%\JetBrains\Kotlin\cli
@rem remove trailing \ if present
if [%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR:~-1%] EQU [\] set KOTLIN_CLI_BOOTSTRAP_CACHE_DIR=%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR:~0,-1%
goto :after_function_declarations
REM ********** Download and extract any zip or .tar.gz archive **********
:download_and_extract
setlocal
set moniker=%~1
set url=%~2
set target_dir=%~3
set sha=%~4
set sha_size=%~5
set show_banner_on_cache_miss=%~6
setlocal enableDelayedExpansion
set flag_file=%target_dir%\.flag
if exist "%flag_file%" (
set /p current_flag=<"%flag_file%"
if "!current_flag!" == "%sha%" exit /b
)
set NL=^
@rem two empty lines required above for the NL character
@rem We have to build the welcome banner here as an env var because we
@rem can't pass a multiline string through the single line powershell
set welcome_banner=!NL! ^
Welcome to !NL! ^
!NL! ^
@@@ @@@@ @@@ @@@ !NL! ^
@@@ @@@@ @@@ @@@ @@@ !NL! ^
@@@ #@@@^" ,@@@ @@@ !NL! ^
@@@ ,@@@%% ,@@@@@@@, @@@@@@@@@@ @@@ @@@ @@@ ,@@@@@, !NL! ^
@@@ @@@@ @@@@@%%^"%%@@@@@ @@@@@@@@@@ @@@ @@@ @@@@@@%%%%@@@@@ !NL! ^
@@@@@@%% @@@%% %%@@@ @@@ @@@ @@@ @@@@ %%@@%% !NL! ^
@@@ @@@@= #@@@ @@@# @@@ @@@ @@@ @@@ @@@ !NL! ^
@@@ @@@@ #@@@ @@@# @@@ @@@ @@@ @@@ @@@ !NL! ^
@@@ *@@@%% @@@@ @@@@ @@@ @@@ @@@ @@@ @@@ !NL! ^
@@@ %%@@@= %%@@@@*,*@@@@%% @@@@### @@@ @@@ @@@ @@@ !NL! ^
@@@ @@@@ ^"@@@@@@@^" %%@@@@@ @@@ @@@ @@@ @@@ !NL!
@rem This multiline string is actually passed as a single line to powershell, meaning #-comments are not possible.
@rem So here are a few comments about the code below:
@rem - we need to support both .zip and .tar.gz archives (for the Kotlin Toolchain distribution and the JRE)
@rem - tar should be present in all Windows machines since 2018 (and usable from both cmd and powershell)
@rem - tar requires the destination dir to exist
@rem - We use (New-Object Net.WebClient).DownloadFile instead of Invoke-WebRequest for performance. See the issue
@rem https://github.com/PowerShell/PowerShell/issues/16914, which is still not fixed in Windows PowerShell 5.1
@rem - DownloadFile requires the directories in the destination file's path to exist
set download_and_extract_ps1= ^
Set-StrictMode -Version 3.0; ^
$ErrorActionPreference = 'Stop'; ^
^
$createdNew = $false; ^
$lock = New-Object System.Threading.Mutex($true, ('Global\kotlin-cli-bootstrap.' + '%target_dir%'.GetHashCode().ToString()), [ref]$createdNew); ^
if (-not $createdNew) { ^
Write-Host 'Another Kotlin CLI instance is bootstrapping. Waiting for our turn...'; ^
[void]$lock.WaitOne(); ^
} ^
^
try { ^
if ((Get-Content '%flag_file%' -ErrorAction Ignore) -ne '%sha%') { ^
if (('%show_banner_on_cache_miss%' -eq 'true') -and [string]::IsNullOrEmpty('%KOTLIN_CLI_NO_WELCOME_BANNER%')) { ^
Write-Host \"$env:welcome_banner\"; ^
Write-Host ''; ^
Write-Host 'This is the first run of the Kotlin CLI v%kotlin_cli_version%, so we need to download the Kotlin Toolchain.'; ^
Write-Host 'Please give us a few seconds now, subsequent runs will be faster.'; ^
Write-Host ''; ^
} ^
$temp_file = '%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR%\' + [System.IO.Path]::GetRandomFileName(); ^
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ^
Write-Host 'Downloading %moniker%...'; ^
[void](New-Item '%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR%' -ItemType Directory -Force); ^
if (Get-Command curl.exe -errorAction SilentlyContinue) { ^
curl.exe -L --silent --show-error --fail --output $temp_file '%url%'; ^
} else { ^
(New-Object Net.WebClient).DownloadFile('%url%', $temp_file); ^
} ^
^
$actualSha = (Get-FileHash -Algorithm SHA%sha_size% -Path $temp_file).Hash.ToString(); ^
if ($actualSha -ne '%sha%') { ^
$writeErr = if ($Host.Name -eq 'ConsoleHost') { [Console]::Error.WriteLine } else { $host.ui.WriteErrorLine } ^
$writeErr.Invoke(\"ERROR: Checksum mismatch for $temp_file (downloaded from %url%): expected checksum %sha% but got $actualSha\"); ^
exit 1; ^
} ^
^
if (Test-Path '%target_dir%') { ^
Remove-Item '%target_dir%' -Recurse; ^
} ^
if ($temp_file -like '*.zip') { ^
Add-Type -A 'System.IO.Compression.FileSystem'; ^
[IO.Compression.ZipFile]::ExtractToDirectory($temp_file, '%target_dir%'); ^
} else { ^
[void](New-Item '%target_dir%' -ItemType Directory -Force); ^
tar -xzf $temp_file -C '%target_dir%'; ^
} ^
Remove-Item $temp_file; ^
^
Set-Content '%flag_file%' -Value '%sha%'; ^
Write-Host 'Download complete.'; ^
Write-Host ''; ^
} ^
} ^
finally { ^
$lock.ReleaseMutex(); ^
}
rem We reset the PSModulePath in case this batch script was called from PowerShell Core
rem See https://github.com/PowerShell/PowerShell/issues/18108#issuecomment-2269703022
set PSModulePath=
set powershell=%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe
"%powershell%" -NonInteractive -NoProfile -NoLogo -Command %download_and_extract_ps1%
if errorlevel 1 exit /b 1
exit /b 0
:find_project_context
@rem Search upwards for a kotlin.bat wrapper file and/or project.yaml
@rem Sets wrapper_script to the found wrapper path, or empty string if not found.
@rem Returns errorlevel 0 if a valid wrapper (that is not this script itself) was found, 1 otherwise.
set wrapper_script=
set this_script=%~f0
set project_dir=%CD%
:find_loop
set wrapper_candidate=%project_dir%\kotlin.bat
if "%this_script%"=="%wrapper_candidate%" (
@rem Found itself (local wrapper case), no need to update any version or search further.
exit /b 1
)
if exist "%wrapper_candidate%" (
@rem Found a wrapper — check that a project context exists alongside it
if exist "%project_dir%\project.yaml" (
set wrapper_script=%wrapper_candidate%
exit /b 0
)
if exist "%project_dir%\module.yaml" (
set wrapper_script=%wrapper_candidate%
exit /b 0
)
echo WARNING: Found wrapper script '%wrapper_candidate%', but no project.yaml or module.yaml near it. Skipping. >&2
@rem Continue the search
goto :find_next_parent
)
if exist "%project_dir%\project.yaml" (
@rem Found project.yaml but no wrapper alongside it
echo WARNING: Found a project.yaml in '%project_dir%', but the wrapper script is missing; using Kotlin Toolchain v%kotlin_cli_version%. >&2
exit /b 1
)
:find_next_parent
@rem Move to parent directory
for %%P in ("%project_dir%\..") do set parent_dir=%%~fP
if "%parent_dir%"=="%project_dir%" (
@rem Reached the root, stop searching
exit /b 1
)
set project_dir=%parent_dir%
goto :find_loop
:parse_project_context
@rem Parse kotlin_cli_version and kotlin_cli_sha256 from the found wrapper_script without executing it.
set parsed_kotlin_cli_version=
set parsed_kotlin_cli_sha256=
for /f "tokens=2 delims==" %%A in ('findstr /r /c:"^set kotlin_cli_version=[A-Za-z0-9._+-]*$" "%wrapper_script%"') do (
if not defined parsed_kotlin_cli_version set parsed_kotlin_cli_version=%%A
)
for /f "tokens=2 delims==" %%A in ('findstr /r /c:"^set kotlin_cli_sha256=[0-9a-fA-F]*$" "%wrapper_script%"') do (
if not defined parsed_kotlin_cli_sha256 set parsed_kotlin_cli_sha256=%%A
)
if not defined parsed_kotlin_cli_version (
echo ERROR: Suspicious local wrapper script: failed to detect the distribution version in '%wrapper_script%' >&2
exit /b 1
)
if not defined parsed_kotlin_cli_sha256 (
echo ERROR: Suspicious local wrapper script: failed to detect the distribution checksum in '%wrapper_script%' >&2
exit /b 1
)
@rem Overwrite builtin values and proceed
set kotlin_cli_version=%parsed_kotlin_cli_version%
set kotlin_cli_sha256=%parsed_kotlin_cli_sha256%
exit /b 0
:fail
echo ERROR: Kotlin CLI bootstrap failed, see errors above
exit /b 1
:after_function_declarations
REM ********** Project-local version detection **********
if defined KOTLIN_CLI_WRAPPER_ALWAYS_USE_INTRINSIC_VERSION goto :after_local_version_detection
call :find_project_context
if errorlevel 1 goto :after_local_version_detection
call :parse_project_context
if errorlevel 1 goto fail
:after_local_version_detection
REM ********** Provision the Kotlin Toolchain distribution **********
set kotlin_cli_url=%KOTLIN_CLI_DOWNLOAD_ROOT%/org/jetbrains/kotlin/kotlin-cli/%kotlin_cli_version%/kotlin-cli-%kotlin_cli_version%-dist.tgz
set kotlin_cli_target_dir=%KOTLIN_CLI_BOOTSTRAP_CACHE_DIR%\kotlin-cli-%kotlin_cli_version%
call :download_and_extract "Kotlin Toolchain distribution v%kotlin_cli_version%" "%kotlin_cli_url%" "%kotlin_cli_target_dir%" "%kotlin_cli_sha256%" "256" "true"
if errorlevel 1 goto fail
REM ********** Launch the Kotlin CLI **********
rem Determine the correct busybox binary based on architecture
if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
set busybox_exe=%kotlin_cli_target_dir%\bin\busybox64a.exe
) else if "%PROCESSOR_ARCHITECTURE%"=="AMD64" (
set busybox_exe=%kotlin_cli_target_dir%\bin\busybox64u.exe
) else (
echo Unsupported architecture %PROCESSOR_ARCHITECTURE% >&2
goto fail
)
rem We use busybox here because it doesn't reinterpret the user-passed command-line arguments (that we pass via %*).
rem Also this way we can use the unified launcher script (.sh)
set KOTLIN_CLI_WRAPPER_PATH=%~f0
rem The '& call' after the app run is to avoid the "Terminate batch job (Y/N)?" prompt
"%busybox_exe%" sh "%kotlin_cli_target_dir%\bin\launcher.sh" %* & call :exitWithErrorLevel
:exitWithErrorLevel
@rem We use "%COMSPEC%" /d /c exit so that and/or operators work properly when calling kotlin.bat directly from a script
@rem without the `call` command. With a plain `exit /B %ERRORLEVEL%`, using `kotlin.bat && echo success` would print
@rem 'success' even in case of failure. Consumers would have to use `call kotlin.bat && echo success` for it to work.`
"%COMSPEC%" /d /c exit %ERRORLEVEL%
+119
View File
@@ -0,0 +1,119 @@
product:
type: kmp/lib
platforms:
- android
- js
- jvm
- linuxArm64
- linuxX64
- mingwX64
- wasmJs
description: Simple logging library for kotlin
repositories:
- id: mavenLocal
url: mavenLocal
resolve: false
publish: true
plugins:
dokka:
enabled: true
moduleName: KSLog
moduleVersion: 2.1.0
sourceLinkBaseUrl: https://github.com/InsanusMokrassar/KSLog/blob/master/kslog
skipDeprecated: true
github-release:
enabled: true
owner: InsanusMokrassar
repository: KSLog
version: 2.1.0
tagPrefix: v
targetCommitish: 2.1.0
changelogFile: CHANGELOG.md
settings:
android:
buildToolsVersion: 37.0.0
compileSdk: 37
minSdk: 21
namespace: dev.inmo.kslog
junit: junit-4
jvm:
jdk:
version: 17
release: null
kotlin:
version: 2.4.20
publishing:
enabled: true
group: dev.inmo
version: 2.1.0
artifactId: kslog
signArtifacts: true
publishSources: true
pom:
name: kslog
description: Simple logging library for kotlin
url: https://github.com/InsanusMokrassar/KSLog
scm:
url: https://github.com/InsanusMokrassar/KSLog.git
developerConnection: scm:git:[fetch=]https://github.com/InsanusMokrassar/KSLog.git[push=]https://github.com/InsanusMokrassar/KSLog.git
developers:
- id: Akkihi
name: Akkihi
email: akkihiguy@gmail.com
- id: InsanusMokrassar
name: InsanusMokrassar
email: ovsyannikov.alexey95@gmail.com
licenses:
- name: MIT License
url: https://opensource.org/licenses/MIT
settings@android:
kotlin:
freeCompilerArgs:
- -module-name=dev.inmo_kslog
- -jvm-target=17
settings@js:
kotlin:
freeCompilerArgs:
- -Xir-module-name=dev.inmo:kslog
settings@jvm:
kotlin:
freeCompilerArgs:
- -module-name=dev.inmo_kslog
- -jvm-target=1.8
settings@linuxArm64:
kotlin:
freeCompilerArgs:
- -module-name=dev.inmo:kslog
- -Xshort-module-name=kslog
settings@linuxX64:
kotlin:
freeCompilerArgs:
- -module-name=dev.inmo:kslog
- -Xshort-module-name=kslog
settings@mingwX64:
kotlin:
freeCompilerArgs:
- -module-name=dev.inmo:kslog
- -Xshort-module-name=kslog
settings@native:
kotlin:
freeCompilerArgs:
- -module-name=dev.inmo:kslog_nativeMain
- -Xshort-module-name=kslog_nativeMain
- -Xexport-kdoc
settings@wasmJs:
kotlin:
freeCompilerArgs:
- -Xir-module-name=dev.inmo:kslog
+20
View File
@@ -0,0 +1,20 @@
package dev.inmo.kslog.common
/**
* Creating base tag using class simple name of receiver
*
* @throws IllegalStateException If there is no opportunity to take simple name of receiver class
*/
val Any.logTag
get() = this::class.simpleName ?: error("Unable to retrieve log tag")
/**
* Creating [TagLogger] with [logTag] as base tag
*/
val Any.logger: KSLog
get() = TagLogger(logTag)
/**
* Creating [TagLogger] using [logger] extension property with [tagBase] as `this`
*/
fun taggedLogger(tagBase: Any): KSLog = tagBase.logger
+22
View File
@@ -0,0 +1,22 @@
package dev.inmo.kslog.common
import kotlin.jvm.JvmInline
typealias SimpleKSLogCallback = (level: LogLevel, tag: String?, message: Any, throwable: Throwable?) -> Unit
/**
* Creates simple [KSLog] which will pass all incoming [performLog] calls to [performLogCallback]
*/
@JvmInline
value class CallbackKSLog(
private val performLogCallback: SimpleKSLogCallback
) : KSLog {
override fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?) = performLogCallback(level, tag, message, throwable)
}
/**
* Creating [CallbackKSLog] using [performLogCallback] as an argument for constructor
*/
fun KSLog(
performLogCallback: SimpleKSLogCallback
) = CallbackKSLog(performLogCallback)
+54
View File
@@ -0,0 +1,54 @@
package dev.inmo.kslog.common
import dev.inmo.kslog.common.filter.filtered
/**
* Logger based on [KSLoggerDefaultPlatformLoggerLambda] or [logging] parameter
*
* @param defaultTag will be used in case when `tag` parameter in [performLog] omitted
* @param messageFormatter special formatter which creating [String] inside of [performLog] for each call. Defaults to
* [defaultMessageFormatter]
* @param logging target lambda which will be called with a result of [messageFormatter] logs formatting as a message
* and tag as tag if not `null` and [defaultTag] otherwise
*/
class DefaultKSLog(
private val defaultTag: String,
private val messageFormatter: MessageFormatter = defaultMessageFormatter,
private val logging: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit = KSLoggerDefaultPlatformLoggerLambda
) : KSLog {
override fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?) {
val resultTag = tag ?: defaultTag
val text = messageFormatter(level, resultTag, message, throwable)
logging(level, resultTag, text, throwable)
}
override fun performLog(level: LogLevel, tag: String?, throwable: Throwable?, messageBuilder: () -> Any) {
val resultTag = tag ?: defaultTag
val text = messageFormatter(level, resultTag, messageBuilder(), throwable)
logging(level, resultTag, text, throwable)
}
override suspend fun performLogS(
level: LogLevel,
tag: String?,
throwable: Throwable?,
messageBuilder: suspend () -> Any
) {
val resultTag = tag ?: defaultTag
val text = messageFormatter(level, resultTag, messageBuilder(), throwable)
logging(level, resultTag, text, throwable)
}
}
@Deprecated("Filtering should be replaced with FilterKSLog")
fun DefaultKSLog(
defaultTag: String,
filter: MessageFilter = { _, _, _ -> true },
messageFormatter: MessageFormatter = defaultMessageFormatter,
logging: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit = KSLoggerDefaultPlatformLoggerLambda
) = DefaultKSLog(
defaultTag, messageFormatter, logging
).filtered(filter)
+223
View File
@@ -0,0 +1,223 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.kslog.common
/**
* Extension functions for convenient logging with [KSLog]
*
* This file provides:
* - Generic log functions for all log levels
* - Specific functions for each log level (trace, debug, verbose, info, warning, error, assert)
* - Short aliases (t, d, v, i, w, e, wtf) for quick logging
* - Suspendable variants (with 'S' suffix) for async contexts
* - Support for lazy message evaluation via lambda builders
* - Both tagged and untagged variants
*/
inline fun KSLog.log(level: LogLevel, tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = performLog(level, tag, e, messageBuilder)
inline fun KSLog.log(level: LogLevel, tag: String, noinline messageBuilder: () -> Any) = performLog(level, tag, null, messageBuilder)
suspend inline fun KSLog.logS(level: LogLevel, tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = performLogS(level, tag, e, messageBuilder)
suspend inline fun KSLog.logS(level: LogLevel, tag: String, noinline messageBuilder: suspend () -> Any) = performLogS(level, tag, null, messageBuilder)
inline fun KSLog.log(level: LogLevel, e: Throwable? = null, noinline messageBuilder: () -> Any) = performLog(level, null, e, messageBuilder)
suspend inline fun KSLog.logS(level: LogLevel, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = performLogS(level, null, e, messageBuilder)
inline fun KSLog.log(level: LogLevel, message: Any, e: Throwable?) = performLog(level, message, e)
inline fun KSLog.log(level: LogLevel, message: String, e: Throwable) = log(level, message as Any, e)
inline fun KSLog.log(level: LogLevel, message: Any) = performLog(level, message, null)
inline fun KSLog.log(level: LogLevel, tag: String, message: Any, e: Throwable?) = performLog(level, tag, message, e)
inline fun KSLog.log(level: LogLevel, tag: String, message: Any) = performLog(level, tag, message, null)
inline fun KSLog.trace(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.TRACE, null, e, messageBuilder)
inline fun KSLog.trace(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.TRACE, tag, e, messageBuilder)
inline fun KSLog.trace(tag: String, noinline messageBuilder: () -> Any) = trace(tag, null, messageBuilder)
suspend inline fun KSLog.traceS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.TRACE, null, e, messageBuilder)
suspend inline fun KSLog.traceS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.TRACE, tag, e, messageBuilder)
suspend inline fun KSLog.traceS(tag: String, noinline messageBuilder: suspend () -> Any) = traceS(tag, null, messageBuilder)
inline fun KSLog.trace(message: Any, e: Throwable?) = log(LogLevel.TRACE, message, e)
inline fun KSLog.trace(message: String, e: Throwable) = trace(message as Any, e)
inline fun KSLog.trace(message: Any) = trace(message, null)
inline fun KSLog.trace(tag: String, message: Any, e: Throwable?) = log(LogLevel.TRACE, tag, message, e)
inline fun KSLog.trace(tag: String, message: Any) = trace(tag, message, null)
inline fun KSLog.debug(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.DEBUG, null, e, messageBuilder)
inline fun KSLog.debug(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.DEBUG, tag, e, messageBuilder)
inline fun KSLog.debug(tag: String, noinline messageBuilder: () -> Any) = debug(tag, null, messageBuilder)
suspend inline fun KSLog.debugS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.DEBUG, null, e, messageBuilder)
suspend inline fun KSLog.debugS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.DEBUG, tag, e, messageBuilder)
suspend inline fun KSLog.debugS(tag: String, noinline messageBuilder: suspend () -> Any) = debugS(tag, null, messageBuilder)
inline fun KSLog.debug(message: Any, e: Throwable?) = log(LogLevel.DEBUG, message, e)
inline fun KSLog.debug(message: String, e: Throwable) = debug(message as Any, e)
inline fun KSLog.debug(message: Any) = debug(message, null)
inline fun KSLog.debug(tag: String, message: Any, e: Throwable?) = log(LogLevel.DEBUG, tag, message, e)
inline fun KSLog.debug(tag: String, message: Any) = debug(tag, message, null)
inline fun KSLog.verbose(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.VERBOSE, null, e, messageBuilder)
inline fun KSLog.verbose(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.VERBOSE, tag, e, messageBuilder)
inline fun KSLog.verbose(tag: String, noinline messageBuilder: () -> Any) = verbose(tag, null, messageBuilder)
suspend inline fun KSLog.verboseS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.VERBOSE, null, e, messageBuilder)
suspend inline fun KSLog.verboseS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.VERBOSE, tag, e, messageBuilder)
suspend inline fun KSLog.verboseS(tag: String, noinline messageBuilder: suspend () -> Any) = verboseS(tag, null, messageBuilder)
inline fun KSLog.verbose(message: Any, e: Throwable?) = log(LogLevel.VERBOSE, message, e)
inline fun KSLog.verbose(message: String, e: Throwable) = verbose(message as Any, e)
inline fun KSLog.verbose(message: Any) = verbose(message, null)
inline fun KSLog.verbose(tag: String, message: Any, e: Throwable?) = log(LogLevel.VERBOSE, tag, message, e)
inline fun KSLog.verbose(tag: String, message: Any) = verbose(tag, message, null)
inline fun KSLog.info(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.INFO, null, e, messageBuilder)
inline fun KSLog.info(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.INFO, tag, e, messageBuilder)
inline fun KSLog.info(tag: String, noinline messageBuilder: () -> Any) = info(tag, null, messageBuilder)
suspend inline fun KSLog.infoS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.INFO, null, e, messageBuilder)
suspend inline fun KSLog.infoS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.INFO, tag, e, messageBuilder)
suspend inline fun KSLog.infoS(tag: String, noinline messageBuilder: suspend () -> Any) = infoS(tag, null, messageBuilder)
inline fun KSLog.info(message: Any, e: Throwable?) = log(LogLevel.INFO, message, e)
inline fun KSLog.info(message: String, e: Throwable) = info(message as Any, e)
inline fun KSLog.info(message: Any) = info(message, null)
inline fun KSLog.info(tag: String, message: Any, e: Throwable?) = log(LogLevel.INFO, tag, message, e)
inline fun KSLog.info(tag: String, message: Any) = info(tag, message, null)
inline fun KSLog.warning(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.WARNING, null, e, messageBuilder)
inline fun KSLog.warning(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.WARNING, tag, e, messageBuilder)
inline fun KSLog.warning(tag: String, noinline messageBuilder: () -> Any) = warning(tag, null, messageBuilder)
suspend inline fun KSLog.warningS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.WARNING, null, e, messageBuilder)
suspend inline fun KSLog.warningS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.WARNING, tag, e, messageBuilder)
suspend inline fun KSLog.warningS(tag: String, noinline messageBuilder: suspend () -> Any) = warningS(tag, null, messageBuilder)
inline fun KSLog.warning(message: Any, e: Throwable?) = log(LogLevel.WARNING, message, e)
inline fun KSLog.warning(message: String, e: Throwable) = warning(message as Any, e)
inline fun KSLog.warning(message: Any) = warning(message, null)
inline fun KSLog.warning(tag: String, message: Any, e: Throwable?) = log(LogLevel.WARNING, tag, message, e)
inline fun KSLog.warning(tag: String, message: Any) = warning(tag, message, null)
inline fun KSLog.error(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.ERROR, null, e, messageBuilder)
inline fun KSLog.error(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.ERROR, tag, e, messageBuilder)
inline fun KSLog.error(tag: String, noinline messageBuilder: () -> Any) = error(tag, null, messageBuilder)
suspend inline fun KSLog.errorS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.ERROR, null, e, messageBuilder)
suspend inline fun KSLog.errorS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.ERROR, tag, e, messageBuilder)
suspend inline fun KSLog.errorS(tag: String, noinline messageBuilder: suspend () -> Any) = errorS(tag, null, messageBuilder)
inline fun KSLog.error(message: Any, e: Throwable?) = log(LogLevel.ERROR, message, e)
inline fun KSLog.error(message: String, e: Throwable) = error(message as Any, e)
inline fun KSLog.error(message: Any) = error(message, null)
inline fun KSLog.error(tag: String, message: Any, e: Throwable?) = log(LogLevel.ERROR, tag, message, e)
inline fun KSLog.error(tag: String, message: Any) = error(tag, message, null)
inline fun KSLog.assert(e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.ASSERT, null, e, messageBuilder)
inline fun KSLog.assert(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(LogLevel.ASSERT, tag, e, messageBuilder)
inline fun KSLog.assert(tag: String, noinline messageBuilder: () -> Any) = assert(tag, null, messageBuilder)
suspend inline fun KSLog.assertS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.ASSERT, null, e, messageBuilder)
suspend inline fun KSLog.assertS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(LogLevel.ASSERT, tag, e, messageBuilder)
suspend inline fun KSLog.assertS(tag: String, noinline messageBuilder: suspend () -> Any) = assertS(tag, null, messageBuilder)
inline fun KSLog.assert(message: Any, e: Throwable?) = log(LogLevel.ASSERT, message, e)
inline fun KSLog.assert(message: String, e: Throwable) = assert(message as Any, e)
inline fun KSLog.assert(message: Any) = assert(message, null)
inline fun KSLog.assert(tag: String, message: Any, e: Throwable?) = log(LogLevel.ASSERT, tag, message, e)
inline fun KSLog.assert(tag: String, message: Any) = assert(tag, message, null)
// ----- Short names
inline fun KSLog.l(level: LogLevel, tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(level, tag, e, messageBuilder)
inline fun KSLog.l(level: LogLevel, tag: String, noinline messageBuilder: () -> Any) = l(level, tag, null, messageBuilder)
inline fun KSLog.l(level: LogLevel, e: Throwable? = null, noinline messageBuilder: () -> Any) = log(level, e, messageBuilder)
suspend inline fun KSLog.lS(level: LogLevel, tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(level, tag, e, messageBuilder)
suspend inline fun KSLog.lS(level: LogLevel, tag: String, noinline messageBuilder: suspend () -> Any) = lS(level, tag, null, messageBuilder)
suspend inline fun KSLog.lS(level: LogLevel, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = logS(level, e, messageBuilder)
inline fun KSLog.l(level: LogLevel, message: Any, e: Throwable?) = log(level, message, e)
inline fun KSLog.l(level: LogLevel, message: String, e: Throwable) = l(level, message as Any, e)
inline fun KSLog.l(level: LogLevel, message: Any) = log(level, message, null)
inline fun KSLog.l(level: LogLevel, tag: String, message: Any, e: Throwable?) = log(level, tag, message, e)
inline fun KSLog.l(level: LogLevel, tag: String, message: Any) = log(level, tag, message, null)
inline fun KSLog.t(e: Throwable? = null, noinline messageBuilder: () -> Any) = trace(e, messageBuilder)
inline fun KSLog.t(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = trace(tag, e, messageBuilder)
inline fun KSLog.t(tag: String, noinline messageBuilder: () -> Any) = t(tag, null, messageBuilder)
suspend inline fun KSLog.tS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = traceS(e, messageBuilder)
suspend inline fun KSLog.tS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = traceS(tag, e, messageBuilder)
suspend inline fun KSLog.tS(tag: String, noinline messageBuilder: suspend () -> Any) = tS(tag, null, messageBuilder)
inline fun KSLog.t(message: Any, e: Throwable?) = trace(message, e)
inline fun KSLog.t(message: String, e: Throwable) = t(message as Any, e)
inline fun KSLog.t(message: Any) = t(message, null)
inline fun KSLog.t(tag: String, message: Any, e: Throwable?) = trace(tag, message, e)
inline fun KSLog.t(tag: String, message: Any) = t(tag, message, null)
inline fun KSLog.d(e: Throwable? = null, noinline messageBuilder: () -> Any) = debug(e, messageBuilder)
inline fun KSLog.d(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = debug(tag, e, messageBuilder)
inline fun KSLog.d(tag: String, noinline messageBuilder: () -> Any) = d(tag, null, messageBuilder)
suspend inline fun KSLog.dS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = debugS(e, messageBuilder)
suspend inline fun KSLog.dS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = debugS(tag, e, messageBuilder)
suspend inline fun KSLog.dS(tag: String, noinline messageBuilder: suspend () -> Any) = dS(tag, null, messageBuilder)
inline fun KSLog.d(message: Any, e: Throwable?) = debug(message, e)
inline fun KSLog.d(message: String, e: Throwable) = d(message as Any, e)
inline fun KSLog.d(message: Any) = d(message, null)
inline fun KSLog.d(tag: String, message: Any, e: Throwable?) = debug(tag, message, e)
inline fun KSLog.d(tag: String, message: Any) = d(tag, message, null)
inline fun KSLog.v(e: Throwable? = null, noinline messageBuilder: () -> Any) = verbose(e, messageBuilder)
inline fun KSLog.v(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = verbose(tag, e, messageBuilder)
inline fun KSLog.v(tag: String, noinline messageBuilder: () -> Any) = v(tag, null, messageBuilder)
suspend inline fun KSLog.vS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = verboseS(e, messageBuilder)
suspend inline fun KSLog.vS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = verboseS(tag, e, messageBuilder)
suspend inline fun KSLog.vS(tag: String, noinline messageBuilder: suspend () -> Any) = vS(tag, null, messageBuilder)
inline fun KSLog.v(message: Any, e: Throwable?) = verbose(message, e)
inline fun KSLog.v(message: String, e: Throwable) = v(message as Any, e)
inline fun KSLog.v(message: Any) = v(message, null)
inline fun KSLog.v(tag: String, message: Any, e: Throwable?) = verbose(tag, message, e)
inline fun KSLog.v(tag: String, message: Any) = v(tag, message, null)
inline fun KSLog.i(e: Throwable? = null, noinline messageBuilder: () -> Any) = info(e, messageBuilder)
inline fun KSLog.i(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = info(tag, e, messageBuilder)
inline fun KSLog.i(tag: String, noinline messageBuilder: () -> Any) = i(tag, null, messageBuilder)
suspend inline fun KSLog.iS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = infoS(e, messageBuilder)
suspend inline fun KSLog.iS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = infoS(tag, e, messageBuilder)
suspend inline fun KSLog.iS(tag: String, noinline messageBuilder: suspend () -> Any) = iS(tag, null, messageBuilder)
inline fun KSLog.i(message: Any, e: Throwable?) = info(message, e)
inline fun KSLog.i(message: String, e: Throwable) = i(message as Any, e)
inline fun KSLog.i(message: Any) = i(message, null)
inline fun KSLog.i(tag: String, message: Any, e: Throwable?) = info(tag, message, e)
inline fun KSLog.i(tag: String, message: Any) = i(tag, message, null)
inline fun KSLog.w(e: Throwable? = null, noinline messageBuilder: () -> Any) = warning(e, messageBuilder)
inline fun KSLog.w(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = warning(tag, e, messageBuilder)
inline fun KSLog.w(tag: String, noinline messageBuilder: () -> Any) = w(tag, null, messageBuilder)
suspend inline fun KSLog.wS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = warningS(e, messageBuilder)
suspend inline fun KSLog.wS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = warningS(tag, e, messageBuilder)
suspend inline fun KSLog.wS(tag: String, noinline messageBuilder: suspend () -> Any) = wS(tag, null, messageBuilder)
inline fun KSLog.w(message: Any, e: Throwable?) = warning(message, e)
inline fun KSLog.w(message: String, e: Throwable) = w(message as Any, e)
inline fun KSLog.w(message: Any) = w(message, null)
inline fun KSLog.w(tag: String, message: Any, e: Throwable?) = warning(tag, message, e)
inline fun KSLog.w(tag: String, message: Any) = w(tag, message, null)
inline fun KSLog.e(e: Throwable? = null, noinline messageBuilder: () -> Any) = error(e, messageBuilder)
inline fun KSLog.e(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = error(tag, e, messageBuilder)
inline fun KSLog.e(tag: String, noinline messageBuilder: () -> Any) = e(tag, null, messageBuilder)
suspend inline fun KSLog.eS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = errorS(e, messageBuilder)
suspend inline fun KSLog.eS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = errorS(tag, e, messageBuilder)
suspend inline fun KSLog.eS(tag: String, noinline messageBuilder: suspend () -> Any) = eS(tag, null, messageBuilder)
inline fun KSLog.e(message: Any, e: Throwable?) = error(message, e)
inline fun KSLog.e(message: String, e: Throwable) = e(message as Any, e)
inline fun KSLog.e(message: Any) = e(message, null)
inline fun KSLog.e(tag: String, message: Any, e: Throwable?) = error(tag, message, e)
inline fun KSLog.e(tag: String, message: Any) = e(tag, message, null)
inline fun KSLog.wtf(e: Throwable? = null, noinline messageBuilder: () -> Any) = assert(e, messageBuilder)
inline fun KSLog.wtf(tag: String?, e: Throwable? = null, noinline messageBuilder: () -> Any) = assert(tag, e, messageBuilder)
inline fun KSLog.wtf(tag: String, noinline messageBuilder: () -> Any) = wtf(tag, null, messageBuilder)
suspend inline fun KSLog.wtfS(e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = assertS(e, messageBuilder)
suspend inline fun KSLog.wtfS(tag: String?, e: Throwable? = null, noinline messageBuilder: suspend () -> Any) = assertS(tag, e, messageBuilder)
suspend inline fun KSLog.wtfS(tag: String, noinline messageBuilder: suspend () -> Any) = wtfS(tag, null, messageBuilder)
inline fun KSLog.wtf(message: Any, e: Throwable?) = assert(message, e)
inline fun KSLog.wtf(message: String, e: Throwable) = wtf(message as Any, e)
inline fun KSLog.wtf(message: Any) = wtf(message, null)
inline fun KSLog.wtf(tag: String, message: Any, e: Throwable?) = assert(tag, message, e)
inline fun KSLog.wtf(tag: String, message: Any) = wtf(tag, message, null)
+300
View File
@@ -0,0 +1,300 @@
package dev.inmo.kslog.common
import dev.inmo.kslog.common.filter.filtered
import dev.inmo.kslog.common.utils.plus
/**
* Enumeration of all available log levels ordered from least to most severe
*
* Used to categorize and filter log messages by importance
*/
enum class LogLevel {
/** Trace level - most detailed logging, typically for tracing code execution */
TRACE,
/** Debug level - detailed information useful for debugging */
DEBUG,
/** Verbose level - detailed informational messages */
VERBOSE,
/** Info level - general informational messages about application state */
INFO,
/** Warning level - potentially harmful situations */
WARNING,
/** Error level - error events that might still allow the application to continue */
ERROR,
/** Assert level - severe errors that should never happen */
ASSERT,
}
/**
* Base interface for any logger in KSLog framework
*
* This is the core logging interface that all logger implementations must implement.
* It provides multiple overloads for logging messages with various combinations of parameters.
*
* The interface supports:
* - Synchronous and suspendable logging
* - Lazy message evaluation via lambda builders
* - Optional tags for categorizing logs
* - Exception/throwable logging
* - All standard log levels (TRACE, DEBUG, VERBOSE, INFO, WARNING, ERROR, ASSERT)
*
* @see default Default logger instance
* @see DefaultKSLog Default implementation
* @see FilterKSLog Logger with filtering capabilities
* @see TypedKSLog Logger with type-based routing
*/
interface KSLog {
/**
* The core logging method that must be implemented by all logger implementations
*
* All other [performLog] overloads will eventually call this method by default.
*
* @param level The severity level of the log message
* @param tag Optional tag to categorize or identify the source of the log
* @param message The message to be logged (can be any object, will be converted to string)
* @param throwable Optional exception or throwable to be logged alongside the message
*/
fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?)
/**
* Logs a message without a tag
*
* Convenience method that calls the main [performLog] with `tag` set to `null`
*
* @param level The severity level of the log message
* @param message The message to be logged
* @param throwable Optional exception or throwable to be logged
*/
fun performLog(level: LogLevel, message: Any, throwable: Throwable?) = performLog(level, null, message, throwable)
/**
* Logs a message with lazy evaluation of the message content
*
* The [messageBuilder] lambda will only be executed if the message is actually logged.
* This is useful for expensive message construction that should be avoided when the
* message would be filtered out.
*
* This method should be overridden in logger implementations that support lazy evaluation
* (like [dev.inmo.kslog.common.filter.FilterKSLog]).
*
* @param level The severity level of the log message
* @param tag Optional tag to categorize the log
* @param throwable Optional exception or throwable to be logged
* @param messageBuilder Lambda that builds the message only when needed
*/
fun performLog(
level: LogLevel,
tag: String?,
throwable: Throwable?,
messageBuilder: () -> Any
) = performLog(level, tag, messageBuilder(), throwable)
/**
* Suspendable variant of [performLog] with lazy message evaluation
*
* Similar to the non-suspending [performLog] with [messageBuilder], but allows the
* message builder to be a suspending function. This enables logging with messages
* that require async operations to construct.
*
* By default, executes the suspending [messageBuilder] and calls the regular [performLog].
*
* @param level The severity level of the log message
* @param tag Optional tag to categorize the log
* @param throwable Optional exception or throwable to be logged
* @param messageBuilder Suspending lambda that builds the message only when needed
*/
suspend fun performLogS(
level: LogLevel,
tag: String?,
throwable: Throwable?,
messageBuilder: suspend () -> Any
) = performLog(level, tag, messageBuilder(), throwable)
companion object : KSLog {
private var defaultLogger: KSLog? = null
/**
* Default logger instance used when calling logging extensions without an explicit receiver
*
* If not explicitly set, a default logger with tag "app" will be created automatically.
* You can customize this by setting your own logger implementation.
*
* @see setDefaultKSLog
* @see addDefaultKSLog
*/
var default: KSLog
get() {
return defaultLogger ?: KSLog("app").also {
defaultLogger = it
}
}
set(value) {
defaultLogger = value
}
override fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?) = default.performLog(level, tag, message, throwable)
override fun performLog(level: LogLevel, message: Any, throwable: Throwable?) = default.performLog(level, message, throwable)
override fun performLog(
level: LogLevel,
tag: String?,
throwable: Throwable?,
messageBuilder: () -> Any
) = default.performLog(level, tag, throwable, messageBuilder)
override suspend fun performLogS(
level: LogLevel,
tag: String?,
throwable: Throwable?,
messageBuilder: suspend () -> Any
) = default.performLogS(level, tag, throwable, messageBuilder)
}
}
/**
* Creates a [CallbackKSLog] by invoking a [KSLog] instance with a callback function
*
* This operator allows convenient logger creation syntax:
* ```kotlin
* val logger = KSLog { level, tag, message, throwable ->
* // Custom logging logic
* }
* ```
*
* @param performLogCallback The callback function to handle log messages
* @return A new [CallbackKSLog] instance
*/
operator fun KSLog.invoke(performLogCallback: (level: LogLevel, tag: String?, message: Any, throwable: Throwable?) -> Unit) = CallbackKSLog(performLogCallback)
/**
* Simple logging implementation that uses `println` to output formatted log messages
*
* Uses [defaultMessageFormatter] to format messages before outputting to standard output.
* This is used as the default logging backend for Native and WASM platforms.
*
* @see defaultMessageFormatter
*/
internal val printlnLogging: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit = { l, t, m, e ->
println(defaultMessageFormatter(l, t, m, e))
}
/**
* Creates a [DefaultKSLog] logger with the specified default tag
*
* @param defaultTag The default tag to use for all log messages from this logger
* @return A new [DefaultKSLog] instance
*/
fun KSLog(
defaultTag: String,
): KSLog = DefaultKSLog(defaultTag)
/**
* Creates a [DefaultKSLog] logger with a custom message formatter
*
* @param defaultTag The default tag to use for all log messages
* @param messageFormatter Custom formatter for converting log parameters to strings
* @return A new [DefaultKSLog] instance
*/
fun KSLog(
defaultTag: String,
messageFormatter: MessageFormatter
): KSLog = DefaultKSLog(
defaultTag,
messageFormatter
)
@Deprecated("Filtering should be replaced with FilterKSLog")
fun KSLog(
defaultTag: String,
filter: MessageFilter,
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog = KSLog (
defaultTag,
messageFormatter
).filtered(filter)
/**
* Creates a filtered [KSLog] that only logs messages at specific levels
*
* Builds a logger that will only process log messages whose level is included in the [levels] collection.
* All other log levels will be filtered out.
*
* @param defaultTag The default tag to use for all log messages
* @param levels Collection of log levels that should be logged
* @param messageFormatter Custom formatter for converting log parameters to strings (defaults to [defaultMessageFormatter])
* @return A new filtered [KSLog] instance
*/
fun KSLog(
defaultTag: String,
levels: Iterable<LogLevel>,
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog {
val levelsSet = levels.toSet()
return KSLog (defaultTag, messageFormatter).filtered { l, _, _ ->
l in levelsSet
}
}
/**
* Creates a filtered [KSLog] that only logs messages at the specified levels
*
* Convenience overload that accepts individual log levels as parameters.
*
* @param defaultTag The default tag to use for all log messages
* @param firstLevel First log level to include
* @param secondLevel Second log level to include
* @param otherLevels Additional log levels to include
* @param messageFormatter Custom formatter for converting log parameters to strings (defaults to [defaultMessageFormatter])
* @return A new filtered [KSLog] instance
*/
fun KSLog(
defaultTag: String,
firstLevel: LogLevel,
secondLevel: LogLevel,
vararg otherLevels: LogLevel,
messageFormatter: MessageFormatter = defaultMessageFormatter,
): KSLog = KSLog(defaultTag, setOf(firstLevel, secondLevel, *otherLevels), messageFormatter)
/**
* Creates a filtered [KSLog] that only logs messages at or above a minimum severity level
*
* Uses the ordinal values of [LogLevel] enum to determine which messages to log.
* For example, if [minLoggingLevel] is INFO, then INFO, WARNING, ERROR, and ASSERT will be logged,
* while TRACE, DEBUG, and VERBOSE will be filtered out.
*
* @param defaultTag The default tag to use for all log messages
* @param minLoggingLevel Minimum log level to include (inclusive)
* @param messageFormatter Custom formatter for converting log parameters to strings (defaults to [defaultMessageFormatter])
* @return A new filtered [KSLog] instance
*/
fun KSLog(
defaultTag: String,
minLoggingLevel: LogLevel,
messageFormatter: MessageFormatter = defaultMessageFormatter,
): KSLog = KSLog (
defaultTag,
messageFormatter
).filtered { l, _, _ ->
minLoggingLevel.ordinal <= l.ordinal
}
/**
* Sets the [KSLog.default] logger to a new instance
*
* This replaces the current default logger completely.
*
* @param newDefault The new default logger to use
*/
fun setDefaultKSLog(newDefault: KSLog) { KSLog.default = newDefault }
/**
* Adds an additional logger to the [KSLog.default] logger chain
*
* Uses the [plus] operator to combine the current default logger with [newDefault],
* so that log messages will be sent to both loggers.
*
* @param newDefault Additional logger to add to the default logger chain
* @see dev.inmo.kslog.common.utils.plus
*/
fun addDefaultKSLog(newDefault: KSLog) {
KSLog.default += newDefault
}
+43
View File
@@ -0,0 +1,43 @@
package dev.inmo.kslog.common
/**
* Function type for formatting log messages
*
* @param l The log level
* @param t Optional tag for the log message
* @param m The message object to be logged
* @param Throwable Optional exception/throwable to be logged
* @return Formatted string representation of the log message
*/
typealias MessageFormatter = (l: LogLevel, t: String?, m: Any, Throwable?) -> String
/**
* Helper function to create a [MessageFormatter] from a lambda
*/
inline fun MessageFormatter(noinline formatter: MessageFormatter) = formatter
/**
* Function type for filtering log messages
*
* @param l The log level
* @param t Optional tag for the log message
* @param Throwable Optional exception/throwable associated with the log
* @return `true` if the message should be logged, `false` to filter it out
*/
typealias MessageFilter = (l: LogLevel, t: String?, Throwable?) -> Boolean
/**
* Default message formatter that formats logs as: `[LEVEL] tag - message`
*
* Example output: `[LogLevel.INFO] MyTag - Hello world`
*/
val defaultMessageFormatter: MessageFormatter = { l, t, m, _ -> "[$l] ${t ?.let { "$it " } ?: ""}- $m" }
/**
* Message formatter that includes stack trace of exceptions in the output
*
* Formats logs as: `[LEVEL] tag - message: stacktrace`
*
* Example output: `[LogLevel.ERROR] MyTag - Error occurred: java.lang.Exception: ...`
*/
val defaultMessageFormatterWithErrorPrint: MessageFormatter = { l, t, m, e -> "[$l] ${t ?.let { "$it " } ?: ""}- $m${e ?.let { ": ${it.stackTraceToString()}" } ?: ""}" }
@@ -0,0 +1,21 @@
package dev.inmo.kslog.common
/**
* Platform-specific default logging implementation
*
* This is an `expect` declaration that provides platform-specific logging behavior:
* - **Android**: Uses `android.util.Log` (Log.d, Log.i, Log.w, Log.e, etc.)
* - **JVM**: Uses `java.util.logging.Logger`
* - **JS**: Uses browser `console` (console.debug, console.info, console.warn, console.error)
* - **Native/WASM**: Uses `println` for output
*
* This lambda is used by [DefaultKSLog] as the default logging backend when no custom
* logging function is provided.
*
* **Warning**: This is a global mutable variable. Changing it will affect all loggers
* that use the default platform logger. It's primarily used by [KSLog.default].
*
* @see DefaultKSLog
* @see KSLog.default
*/
expect var KSLoggerDefaultPlatformLoggerLambda: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit
@@ -2,9 +2,12 @@ package dev.inmo.kslog.common
import kotlin.jvm.JvmInline
/**
* Logger which will use [tag] as default one in cases when [performLog] have `null` tag
*/
@JvmInline
value class TagLogger(val tag: String) : KSLog {
override fun performLog(level: LogLevel, tag: String?, message: String, throwable: Throwable?) {
override fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?) {
KSLog.performLog(level, tag ?: this.tag, message, throwable)
}
}
+49
View File
@@ -0,0 +1,49 @@
package dev.inmo.kslog.common.filter
import dev.inmo.kslog.common.*
/**
* A logger that filters messages before passing them to a fallback logger
*
* This logger implementation applies a [messageFilter] predicate to all incoming log requests.
* Only when the filter returns `true` will the message be passed to the [fallbackLogger].
*
* This enables:
* - Log level filtering (e.g., only log ERROR and above)
* - Tag-based filtering (e.g., only log from specific components)
* - Conditional logging based on exception presence
* - Lazy message evaluation - messages are only constructed if they pass the filter
*
* @param fallbackLogger The underlying logger that will receive filtered messages
* @param messageFilter Predicate function that determines which messages to log
*
* @see filtered Extension function for convenient filter creation
*/
class FilterKSLog(
private val fallbackLogger: KSLog,
private val messageFilter: MessageFilter
) : KSLog {
override fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?) {
if (messageFilter(level, tag, throwable)) {
fallbackLogger.performLog(level, tag, message, throwable)
}
}
override fun performLog(level: LogLevel, message: Any, throwable: Throwable?) {
if (messageFilter(level, null, throwable)) {
fallbackLogger.performLog(level, message, throwable)
}
}
override fun performLog(level: LogLevel, tag: String?, throwable: Throwable?, messageBuilder: () -> Any) {
if (messageFilter(level, tag, throwable)) {
fallbackLogger.performLog(level, tag, throwable, messageBuilder)
}
}
override suspend fun performLogS(level: LogLevel, tag: String?, throwable: Throwable?, messageBuilder: suspend () -> Any) {
if (messageFilter(level, tag, throwable)) {
fallbackLogger.performLogS(level, tag, throwable, messageBuilder)
}
}
}
+25
View File
@@ -0,0 +1,25 @@
package dev.inmo.kslog.common.filter
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.MessageFilter
/**
* Creates a [FilterKSLog] that wraps this logger with a message filter
*
* This extension function provides a convenient way to add filtering to any existing logger.
* The resulting logger will only pass messages to this logger when the [filter] returns `true`.
*
* Example:
* ```kotlin
* val logger = KSLog("app").filtered { level, tag, throwable ->
* level.ordinal >= LogLevel.INFO.ordinal // Only log INFO and above
* }
* ```
*
* @param filter Predicate function that receives (level, tag, throwable) and returns whether to log
* @return A new [FilterKSLog] instance wrapping this logger
* @see FilterKSLog
*/
fun KSLog.filtered(
filter: MessageFilter
) = FilterKSLog(this, filter)
+44
View File
@@ -0,0 +1,44 @@
package dev.inmo.kslog.common.typed
import dev.inmo.kslog.common.*
import kotlin.reflect.KClass
/**
* A logger that routes messages to different loggers based on the message type
*
* This logger uses the Kotlin class ([KClass]) of the log message to determine which logger to use.
* This enables type-based routing of log messages, allowing different message types to be
* handled by different loggers.
*
* The routing works as follows:
* 1. Extract the [KClass] from the incoming message object
* 2. Look up the class in [typedLoggers] map
* 3. If found, use that logger
* 4. If not found, fall back to the logger with `null` key (if it exists)
* 5. If no default logger exists, skip logging
*
* Example use case: Route structured log events to different destinations based on their type.
*
* @param typedLoggers Map from message class to logger. Use `null` key for the default/fallback logger
*
* @see TypedKSLogBuilder Builder for creating typed loggers
* @see buildTypedLogger DSL function for convenient creation
*/
class TypedKSLog(
private val typedLoggers: Map<KClass<*>?, KSLog>
) : KSLog {
override fun performLog(level: LogLevel, tag: String?, message: Any, throwable: Throwable?) {
(typedLoggers[message::class] ?: typedLoggers[null]) ?.performLog(level, tag, message, throwable)
}
override fun performLog(level: LogLevel, message: Any, throwable: Throwable?) {
(typedLoggers[message::class] ?: typedLoggers[null]) ?.performLog(level, message, throwable)
}
override fun performLog(level: LogLevel, tag: String?, throwable: Throwable?, messageBuilder: () -> Any) {
val message = messageBuilder()
(typedLoggers[message::class] ?: typedLoggers[null]) ?.performLog(level, tag, message, throwable)
}
override suspend fun performLogS(level: LogLevel, tag: String?, throwable: Throwable?, messageBuilder: suspend () -> Any) {
val message = messageBuilder()
(typedLoggers[message::class] ?: typedLoggers[null]) ?.performLog(level, tag, message, throwable)
}
}
+101
View File
@@ -0,0 +1,101 @@
package dev.inmo.kslog.common.typed
import dev.inmo.kslog.common.*
import kotlin.reflect.KClass
/**
* Builder for creating [TypedKSLog] instances with a fluent API
*
* Provides convenient methods for configuring type-based routing of log messages.
* Supports both explicit [KClass] specifications and reified type parameters.
*
* @param preset Optional initial map of type-to-logger associations to start with
*
* @see buildTypedLogger DSL function for convenient usage
*/
class TypedKSLogBuilder(
private val preset: Map<KClass<*>?, KSLog> = emptyMap()
) {
private val loggers = mutableMapOf<KClass<*>?, KSLog>().apply {
putAll(preset)
}
/**
* Associates a specific class with a logger
*
* @param kClass The class to route, or `null` for the default/fallback logger
* @param with The logger to use for this class
*/
fun on(kClass: KClass<*>?, with: KSLog) = loggers.put(kClass, with)
/**
* Associates a specific class with a callback-based logger
*
* @param kClass The class to route, or `null` for the default/fallback logger
* @param with The callback function to handle logs for this class
*/
fun on(kClass: KClass<*>?, with: SimpleKSLogCallback) = loggers.put(kClass, CallbackKSLog(with))
/**
* Sets the default/fallback logger for messages of unregistered types
*
* @param with The logger to use as default
*/
fun default(with: KSLog) = on(null, with)
/**
* Sets the default/fallback logger using a callback function
*
* @param with The callback function to handle logs from unregistered types
*/
fun default(with: SimpleKSLogCallback) = on(null, with)
/**
* Associates a specific type with a logger using reified type parameter
*
* Example: `on<MyEvent>(customLogger)`
*
* @param T The type to route
* @param with The logger to use for this type
*/
inline fun <reified T: Any> on(with: KSLog) = on(T::class, with)
/**
* Associates a specific type with a callback-based logger using reified type parameter
*
* Example: `on<MyEvent> { level, tag, message, error -> ... }`
*
* @param T The type to route
* @param with The callback function to handle logs for this type
*/
inline fun <reified T: Any> on(noinline with: SimpleKSLogCallback) = on(T::class, with)
/**
* Builds the [TypedKSLog] instance with the configured type-to-logger associations
*/
fun build() = TypedKSLog(loggers.toMap())
}
/**
* DSL function for building a [TypedKSLog] with a fluent configuration block
*
* Provides a convenient way to create typed loggers with type-to-logger associations.
*
* Example:
* ```kotlin
* val logger = buildTypedLogger {
* on<ErrorEvent>(errorLogger)
* on<WarningEvent>(warningLogger)
* default(defaultLogger)
* }
* ```
*
* @param preset Optional initial map of type-to-logger associations
* @param block Configuration block for specifying type-to-logger associations
* @return A new [TypedKSLog] instance
* @see TypedKSLogBuilder
*/
inline fun buildTypedLogger(
preset: Map<KClass<*>?, KSLog> = emptyMap(),
block: TypedKSLogBuilder.() -> Unit
) = TypedKSLogBuilder(preset).apply(block).build()
+36
View File
@@ -0,0 +1,36 @@
package dev.inmo.kslog.common.utils
import dev.inmo.kslog.common.CallbackKSLog
import dev.inmo.kslog.common.KSLog
/**
* Combines two loggers into one that sends messages to both
*
* Creates a new logger that will invoke [performLog] on both this logger and the [other] logger
* for every log message. Both loggers will be called even if one fails.
*
* Error handling:
* - If this logger throws an exception, it will be rethrown after attempting to log to [other]
* - If [other] throws an exception, it will also be rethrown
* - If both throw exceptions, both will be rethrown (first this logger's, then other's)
*
* Example:
* ```kotlin
* val combinedLogger = consoleLogger + fileLogger
* combinedLogger.info("Message") // Logs to both console and file
* ```
*
* @param other The second logger to combine with this one
* @return A new [CallbackKSLog] that logs to both loggers
* @see addDefaultKSLog Uses this operator to add loggers to the default logger
*/
infix operator fun KSLog.plus(other: KSLog) = CallbackKSLog { l, t, m, e ->
val resultOfFirst = runCatching {
this@plus.performLog(l, t, m, e)
}
val resultOfSecond = runCatching {
other.performLog(l, t, m, e)
}
resultOfFirst.onFailure { throw it }
resultOfSecond.onFailure { throw it }
}
@@ -0,0 +1,31 @@
package dev.inmo.kslog.common
import android.util.Log
/**
* Android platform implementation of the default logger
*
* Uses Android's `[android.util.Log]` as the logging backend, mapping KSLog levels
* to Android log levels:
* - [LogLevel.TRACE] → [Log.d] (debug)
* - [LogLevel.DEBUG] → [Log.d] (debug)
* - [LogLevel.VERBOSE] → [Log.v] (verbose)
* - [LogLevel.INFO] → [Log.i] (info)
* - [LogLevel.WARNING] → [Log.w] (warning)
* - [LogLevel.ERROR] → [Log.e] (error)
* - [LogLevel.ASSERT] → [Log.wtf] (what a terrible failure)
*
* Logs can be viewed using `adb logcat` or Android Studio's Logcat window.
*/
actual var KSLoggerDefaultPlatformLoggerLambda: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit = { l, t, m, e ->
val messageString = m.toString()
when(l) {
LogLevel.TRACE -> Log.d(t, messageString, e)
LogLevel.DEBUG -> Log.d(t, messageString, e)
LogLevel.VERBOSE -> Log.v(t, messageString, e)
LogLevel.INFO -> Log.i(t, messageString, e)
LogLevel.WARNING -> Log.w(t, messageString, e)
LogLevel.ERROR -> Log.e(t, messageString, e)
LogLevel.ASSERT -> Log.wtf(t, messageString, e)
}
}
+63
View File
@@ -0,0 +1,63 @@
package dev.inmo.kslog.common
import kotlin.js.Console
/**
* Extended Console interface that includes additional console methods
* not present in Kotlin's standard Console interface
*
* Provides access to browser/Node.js console methods like `debug`, `trace`, and `assert`.
*/
external interface ExtendedConsole : Console {
/**
* Outputs a stack trace to the console
*/
fun trace()
/**
* Outputs a debug message to the console
*
* @param o Objects to output
*/
fun debug(vararg o: Any?)
/**
* Writes an error message to the console if the assertion is false
*
* @param assertion Boolean condition to test
* @param o Objects to output if assertion is false
*/
fun assert(assertion: Boolean, vararg o: Any?)
}
/**
* Extension function to output debug messages to the console
*
* Maps to the browser's `console.debug()` method.
*
* [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/console/debug_static)
*
* @param args Objects to output
*/
fun Console.debug(vararg args: Any?) = unsafeCast<ExtendedConsole>().debug(*args)
/**
* Extension function to write an error message if assertion fails
*
* Maps to the browser's `console.assert()` method.
*
* [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/console/assert_static)
*
* @param assertion Boolean condition to test
* @param args Objects to output if assertion is false
*/
fun Console.assert(assertion: Boolean, vararg args: Any?) = unsafeCast<ExtendedConsole>().assert(assertion, *args)
/**
* Extension function to output a stack trace to the console
*
* Maps to the browser's `console.trace()` method.
*
* [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/console/trace_static)
*/
fun Console.trace() = unsafeCast<ExtendedConsole>().trace()
@@ -0,0 +1,37 @@
package dev.inmo.kslog.common
/**
* JavaScript platform implementation of the default logger
*
* Uses browser/Node.js `console` API as the logging backend, mapping KSLog levels
* to console methods:
* - [LogLevel.TRACE] → console.trace() + console.debug()
* - [LogLevel.DEBUG] → console.debug()
* - [LogLevel.VERBOSE] → console.info()
* - [LogLevel.INFO] → console.info()
* - [LogLevel.WARNING] → console.warn()
* - [LogLevel.ERROR] → console.error()
* - [LogLevel.ASSERT] → console.error()
*
* Logs can be viewed in the browser's Developer Tools console or Node.js console output.
*
* **Note**: The tag parameter is ignored in JS implementation as console methods
* don't support tagging natively.
*/
actual var KSLoggerDefaultPlatformLoggerLambda: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit = { l, _, m, e ->
val args = e ?.let {
arrayOf(m, e)
} ?: arrayOf(m)
when (l) {
LogLevel.TRACE -> {
console.trace()
console.debug(*args)
}
LogLevel.DEBUG -> console.debug(*args)
LogLevel.VERBOSE,
LogLevel.INFO -> console.info(*args)
LogLevel.WARNING -> console.warn(*args)
LogLevel.ERROR -> console.error(*args)
LogLevel.ASSERT -> console.error(*args)
}
}
+132
View File
@@ -0,0 +1,132 @@
package dev.inmo.kslog.common
import dev.inmo.kslog.common.filter.filtered
import java.util.logging.Level
import java.util.logging.Logger
/**
* Default Java Logger instance used by KSLog on JVM platform
*
* Initialized lazily with the name "KSLog"
*/
internal val defaultKSLogLogger by lazy {
Logger.getLogger("KSLog")
}
/**
* Extension function to log with KSLog levels using Java Logger
*
* Maps KSLog [LogLevel] to Java [Level]:
* - [LogLevel.TRACE] → [Level.FINEST]
* - [LogLevel.DEBUG] → [Level.FINER]
* - [LogLevel.VERBOSE] → [Level.FINE]
* - [LogLevel.INFO] → [Level.INFO]
* - [LogLevel.WARNING] → [Level.WARNING]
* - [LogLevel.ERROR] → [Level.SEVERE]
* - [LogLevel.ASSERT] → [Level.SEVERE]
*
* @param l KSLog log level
* @param m Message string
* @param e Optional throwable
*/
internal fun Logger.doLog(
l: LogLevel, m: String, e: Throwable?
) = log(
when(l) {
LogLevel.TRACE -> Level.FINEST
LogLevel.DEBUG -> Level.FINER
LogLevel.VERBOSE -> Level.FINE
LogLevel.INFO -> Level.INFO
LogLevel.WARNING -> Level.WARNING
LogLevel.ERROR -> Level.SEVERE
LogLevel.ASSERT -> Level.SEVERE
},
m,
e
)
@Deprecated("Filtering should be replaced with FilterKSLog")
fun KSLog(
defaultTag: String,
logger: Logger,
filter: MessageFilter,
messageFormatter: MessageFormatter = defaultMessageFormatter
) = KSLog { l, t, m, e ->
if (!filter(l, t, e)) return@KSLog
val text = messageFormatter(l,t ?: defaultTag,m.toString(),e)
logger.doLog(l, text, e)
}
/**
* Creates a KSLog that uses a specific Java [Logger] instance
*
* @param defaultTag Default tag to use when tag is not specified
* @param logger The Java Logger instance to use for logging
* @param messageFormatter Formatter for log messages (defaults to [defaultMessageFormatter])
* @return A new [CallbackKSLog] instance wrapping the Java logger
*/
fun KSLog(
defaultTag: String,
logger: Logger,
messageFormatter: MessageFormatter = defaultMessageFormatter
) = KSLog { l, t, m, e ->
val text = messageFormatter(l,t ?: defaultTag,m.toString(),e)
logger.doLog(l, text, e)
}
/**
* Creates a filtered KSLog using a Java [Logger] that only logs specific levels
*
* @param defaultTag Default tag to use when tag is not specified
* @param logger The Java Logger instance to use for logging
* @param levels Collection of log levels to include
* @param messageFormatter Formatter for log messages (defaults to [defaultMessageFormatter])
* @return A new filtered [KSLog] instance
*/
fun KSLog(
defaultTag: String,
logger: Logger,
levels: Iterable<LogLevel>,
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog {
val levelsSet = levels.toSet()
return KSLog (defaultTag, logger, messageFormatter).filtered { l, _, _ -> l in levelsSet }
}
/**
* Creates a filtered KSLog using a Java [Logger] with specific log levels
*
* @param defaultTag Default tag to use when tag is not specified
* @param logger The Java Logger instance to use for logging
* @param firstLevel First log level to include
* @param secondLevel Second log level to include
* @param otherLevels Additional log levels to include
* @param messageFormatter Formatter for log messages (defaults to [defaultMessageFormatter])
* @return A new filtered [KSLog] instance
*/
fun KSLog(
defaultTag: String,
logger: Logger,
firstLevel: LogLevel,
secondLevel: LogLevel,
vararg otherLevels: LogLevel,
messageFormatter: MessageFormatter = defaultMessageFormatter,
): KSLog = KSLog (defaultTag, logger, setOf(firstLevel, secondLevel, *otherLevels), messageFormatter)
/**
* Creates a filtered KSLog using a Java [Logger] with a minimum log level
*
* Only logs messages at or above the specified minimum level.
*
* @param defaultTag Default tag to use when tag is not specified
* @param logger The Java Logger instance to use for logging
* @param minLoggingLevel Minimum log level to include (defaults to TRACE - the first level)
* @param messageFormatter Formatter for log messages (defaults to [defaultMessageFormatter])
* @return A new filtered [KSLog] instance
*/
fun KSLog(
defaultTag: String,
logger: Logger,
minLoggingLevel: LogLevel = LogLevel.values().first(),
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog = KSLog (defaultTag, logger, messageFormatter).filtered { l, _, _ -> minLoggingLevel.ordinal <= l.ordinal }
@@ -0,0 +1,13 @@
package dev.inmo.kslog.common
/**
* JVM platform implementation of the default logger
*
* Uses `[java.util.logging.Logger]` as the logging backend, mapping KSLog levels
* to Java logging levels.
*
* @see defaultKSLogLogger
*/
actual var KSLoggerDefaultPlatformLoggerLambda: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit = { l, t, m, e ->
defaultKSLogLogger.doLog(l, m.toString(), e)
}
@@ -0,0 +1,12 @@
package dev.inmo.kslog.common
/**
* Native platform implementation of the default logger
*
* Uses [printlnLogging] which formats and outputs logs using `[println]`.
* Logs are written to standard output.
*
* @see printlnLogging
*/
actual var KSLoggerDefaultPlatformLoggerLambda: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit =
printlnLogging
@@ -0,0 +1,11 @@
package dev.inmo.kslog.common
/**
* WebAssembly JavaScript platform implementation of the default logger
*
* Uses [printlnLogging] which formats and outputs logs using `[println]`.
*
* @see printlnLogging
*/
actual var KSLoggerDefaultPlatformLoggerLambda: (level: LogLevel, tag: String, message: Any, throwable: Throwable?) -> Unit =
printlnLogging
+57
View File
@@ -0,0 +1,57 @@
package dev.inmo.kslog.common
import dev.inmo.kslog.common.filter.filtered
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertSame
import kotlin.test.assertTrue
class CallbackKSLogTest {
@Test
fun forwardsExactArguments() {
val expectedThrowable = IllegalStateException("boom")
var observedLevel: LogLevel? = null
var observedTag: String? = null
var observedMessage: Any? = null
var observedThrowable: Throwable? = null
val logger = KSLog { level, tag, message, throwable ->
observedLevel = level
observedTag = tag
observedMessage = message
observedThrowable = throwable
}
logger.performLog(LogLevel.WARNING, "network", "retrying", expectedThrowable)
assertEquals(LogLevel.WARNING, observedLevel)
assertEquals("network", observedTag)
assertEquals("retrying", observedMessage)
assertSame(expectedThrowable, observedThrowable)
}
@Test
fun filteredLoggerDoesNotBuildRejectedLazyMessage() {
var callbackInvoked = false
var messageBuilt = false
val logger = KSLog { _, _, _, _ -> callbackInvoked = true }
.filtered { level, _, _ -> level >= LogLevel.INFO }
logger.performLog(LogLevel.DEBUG, "test", null) {
messageBuilt = true
"expensive"
}
assertFalse(callbackInvoked)
assertFalse(messageBuilt)
logger.performLog(LogLevel.ERROR, "test", null) {
messageBuilt = true
"needed"
}
assertTrue(callbackInvoked)
assertTrue(messageBuilt)
}
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020-2021 JetBrains s.r.o. and and respective authors and developers.
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.
+27
View File
@@ -0,0 +1,27 @@
# KSLog Dokka Toolchain plugin
This local plugin is adapted from JetBrains Kotlin Toolchain's
`build-sources/dokka` plugin at release `v0.13.0`, commit
`abc7a5f7579e9ef267107782048c76777f0bf932`.
Upstream source: <https://github.com/JetBrains/kotlin-toolchain/tree/abc7a5f7579e9ef267107782048c76777f0bf932/build-sources/dokka>
Changes made for KSLog:
- models the KMP common/JVM/Android/JS/WasmJS/native source-set graph;
- uses Dokka 2.2.0 to match the pre-migration build;
- provides separate `dokkaHtml` and `dokkaGfm` commands;
- writes to the preserved `build/dokka/html` and `build/dokka/gfm` paths;
- preserves deprecated-member filtering and GitHub source links;
- supplies the resolved JVM classpath plus transformed Common and matching
JS/WasmJS standard-library KLIBs materialized by the documented prebuild;
- fails generation if Dokka emits an unresolved-type placeholder, preventing
the deployment workflow from uploading incomplete API signatures.
The legacy Gradle `DOKKA_PATH` override remains Gradle-only. The Toolchain
plugin intentionally accepts only output directories below `build/dokka`,
rejects symbolic-link ancestors, and verifies real-path containment before
cleaning generated output.
The adapted Kotlin files retain the upstream Apache-2.0 header. See
`LICENSE.txt` in this directory.
+20
View File
@@ -0,0 +1,20 @@
product: jvm/amper-plugin
description: KMP-capable Dokka HTML and GFM tasks adapted from the Kotlin Toolchain Dokka plugin.
pluginInfo:
id: dokka
settingsClass: dev.inmo.kslog.build.dokka.DokkaSettings
test-dependencies:
- $libs.kotlin.test.junit
settings:
junit: junit-4
jvm:
jdk:
version: 17
release: 17
kotlin:
version: 2.4.20
serialization: json
+37
View File
@@ -0,0 +1,37 @@
tasks:
html:
action: !dev.inmo.kslog.build.dokka.dokkaHtml
projectRootDir: ${project.rootDir}
moduleRootDir: ${module.rootDir}
moduleCompileClasspath: ${module.compileClasspath}
runtimeClasspath:
- $libs.dokka.cli
pluginsClasspath:
- $libs.dokka.base
- $libs.dokka.analysis.kotlin.descriptors
- $libs.kotlinx.html.jvm
- $libs.freemarker
outputDir: ${project.rootDir}/build/dokka/html
settings: ${pluginSettings}
gfm:
action: !dev.inmo.kslog.build.dokka.dokkaGfm
projectRootDir: ${project.rootDir}
moduleRootDir: ${module.rootDir}
moduleCompileClasspath: ${module.compileClasspath}
runtimeClasspath:
- $libs.dokka.cli
pluginsClasspath:
- $libs.dokka.base
- $libs.dokka.analysis.kotlin.descriptors
- $libs.dokka.gfm
- $libs.kotlinx.html.jvm
- $libs.freemarker
outputDir: ${project.rootDir}/build/dokka/gfm
settings: ${pluginSettings}
commands:
- name: dokkaHtml
performedBy: html
- name: dokkaGfm
performedBy: gfm
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
/*
* KSLog modification notice: adapted from JetBrains Kotlin Toolchain v0.13.0
* build-sources/dokka. KSLog extends the configuration model for its KMP
* source-set graph and HTML/GFM plugin configuration. See ../README.md.
*/
package dev.inmo.kslog.build.dokka
import kotlinx.serialization.Serializable
@Serializable
internal data class DokkaConfiguration(
val moduleName: String,
val moduleVersion: String,
val outputDir: String,
val sourceSets: List<DokkaSourceSet>,
val pluginsClasspath: List<String>,
val pluginsConfiguration: List<PluginConfiguration>,
val failOnWarning: Boolean = false,
)
@Serializable
internal data class SourceSetId(
val scopeId: String,
val sourceSetName: String,
)
@Serializable
internal data class DokkaSourceSet(
val sourceSetID: SourceSetId,
val displayName: String,
val dependentSourceSets: List<SourceSetId>,
val classpath: List<String>,
val sourceRoots: List<String>,
val documentedVisibilities: List<String> = listOf("PUBLIC"),
val skipDeprecated: Boolean,
val jdkVersion: Int,
val languageVersion: String = "2.4",
val apiVersion: String = "2.4",
val analysisPlatform: String,
val sourceLinks: List<SourceLink>,
)
@Serializable
internal data class SourceLink(
val localDirectory: String,
val remoteUrl: String,
val remoteLineSuffix: String,
)
@Serializable
internal data class PluginConfiguration(
val fqPluginName: String,
val serializationFormat: String,
val values: String,
)
@Serializable
internal data class DokkaBase(
val footerMessage: String? = null,
)
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
/*
* KSLog modification notice: adapted from JetBrains Kotlin Toolchain v0.13.0
* build-sources/dokka. KSLog adds versioned KMP documentation settings and
* project-specific source-link defaults. See ../README.md for provenance.
*/
package dev.inmo.kslog.build.dokka
import org.jetbrains.amper.plugins.Configurable
@Configurable
interface DokkaSettings {
val moduleName: String get() = "KSLog"
val moduleVersion: String
val sourceLinkBaseUrl: String get() = "https://github.com/InsanusMokrassar/KSLog/blob/master/"
val sourceLinkLineSuffix: String get() = "#L"
val skipDeprecated: Boolean get() = true
}
+340
View File
@@ -0,0 +1,340 @@
/*
* Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
/*
* KSLog modification notice: adapted from JetBrains Kotlin Toolchain v0.13.0
* build-sources/dokka. KSLog adds KMP source sets, HTML/GFM entry points, and
* project-confined, symlink-safe generated-output cleanup. See ../README.md.
*/
package dev.inmo.kslog.build.dokka
import kotlinx.serialization.json.Json
import org.jetbrains.amper.plugins.Classpath
import org.jetbrains.amper.plugins.ExecutionAvoidance
import org.jetbrains.amper.plugins.Input
import org.jetbrains.amper.plugins.Output
import org.jetbrains.amper.plugins.TaskAction
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.Path
import kotlin.concurrent.thread
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.deleteRecursively
import kotlin.io.path.exists
import kotlin.io.path.pathString
import kotlin.io.path.writeText
@TaskAction(ExecutionAvoidance.Disabled)
fun dokkaHtml(
projectRootDir: String,
moduleRootDir: String,
@Input moduleCompileClasspath: Classpath,
@Input runtimeClasspath: Classpath,
@Input pluginsClasspath: Classpath,
@Output outputDir: Path,
settings: DokkaSettings,
) = runDokka(
projectRootDir,
moduleRootDir,
moduleCompileClasspath,
runtimeClasspath,
pluginsClasspath,
outputDir,
settings,
)
@TaskAction(ExecutionAvoidance.Disabled)
fun dokkaGfm(
projectRootDir: String,
moduleRootDir: String,
@Input moduleCompileClasspath: Classpath,
@Input runtimeClasspath: Classpath,
@Input pluginsClasspath: Classpath,
@Output outputDir: Path,
settings: DokkaSettings,
) = runDokka(
projectRootDir,
moduleRootDir,
moduleCompileClasspath,
runtimeClasspath,
pluginsClasspath,
outputDir,
settings,
)
@OptIn(ExperimentalPathApi::class)
private fun runDokka(
projectRootDir: String,
moduleRootDir: String,
moduleCompileClasspath: Classpath,
runtimeClasspath: Classpath,
pluginsClasspath: Classpath,
outputDir: Path,
settings: DokkaSettings,
) {
val normalizedRoot = Path.of(moduleRootDir).toRealPath()
val normalizedOutput = prepareDokkaOutput(projectRootDir, outputDir)
val commonId = SourceSetId(settings.moduleName, "commonMain")
val sourceLink = SourceLink(
localDirectory = normalizedRoot.pathString,
remoteUrl = settings.sourceLinkBaseUrl,
remoteLineSuffix = settings.sourceLinkLineSuffix,
)
val analysisClasspaths = resolveDokkaAnalysisClasspaths(moduleCompileClasspath.resolvedFiles)
fun sourceSet(
directoryName: String,
sourceSetName: String,
displayName: String,
analysisPlatform: String,
dependentOnCommon: Boolean,
classpath: List<String> = emptyList(),
): DokkaSourceSet? {
val sourceRoot = normalizedRoot.resolve(directoryName)
if (!sourceRoot.exists()) return null
return DokkaSourceSet(
sourceSetID = SourceSetId(settings.moduleName, sourceSetName),
displayName = displayName,
dependentSourceSets = if (dependentOnCommon) listOf(commonId) else emptyList(),
classpath = classpath,
sourceRoots = listOf(sourceRoot.pathString),
skipDeprecated = settings.skipDeprecated,
jdkVersion = if (displayName == "JVM") 8 else 17,
analysisPlatform = analysisPlatform,
sourceLinks = listOf(sourceLink),
)
}
val androidClasspath = buildList {
addAll(analysisClasspaths.jvm)
val sdkRoot = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
if (sdkRoot != null) {
val androidJar = listOf("android-37.0", "android-37")
.map { Path.of(sdkRoot).resolve("platforms/$it/android.jar") }
.firstOrNull { Files.isRegularFile(it) }
if (androidJar != null) add(androidJar.pathString)
}
}
val sourceSets = listOfNotNull(
sourceSet(
"src",
"commonMain",
"Common",
"common",
dependentOnCommon = false,
classpath = analysisClasspaths.commonNative,
),
sourceSet(
"src@jvm",
"jvmMain",
"JVM",
"jvm",
dependentOnCommon = true,
classpath = analysisClasspaths.jvm,
),
sourceSet("src@android", "androidMain", "Android", "jvm", dependentOnCommon = true, classpath = androidClasspath),
sourceSet("src@js", "jsMain", "JS", "js", dependentOnCommon = true, classpath = analysisClasspaths.js),
sourceSet(
"src@wasmJs",
"wasmJsMain",
"WasmJS",
"js",
dependentOnCommon = true,
classpath = analysisClasspaths.wasmJs,
),
sourceSet(
"src@native",
"nativeMain",
"Native",
"native",
dependentOnCommon = true,
classpath = analysisClasspaths.commonNative,
),
)
val configuration = DokkaConfiguration(
moduleName = settings.moduleName,
moduleVersion = settings.moduleVersion,
outputDir = normalizedOutput.pathString,
sourceSets = sourceSets,
pluginsClasspath = pluginsClasspath.resolvedFiles.map { it.pathString },
pluginsConfiguration = listOf(
PluginConfiguration(
fqPluginName = "org.jetbrains.dokka.base.DokkaBase",
serializationFormat = "JSON",
values = Json.encodeToString(DokkaBase(footerMessage = "KSLog ${settings.moduleVersion}")),
)
),
)
val configurationFile = Files.createTempFile("kslog-dokka-", ".json")
try {
configurationFile.writeText(Json.encodeToString(configuration))
val cliJar = runtimeClasspath.resolvedFiles.single()
val commandLine = listOf(
ProcessHandle.current().info().command().orElse("java"),
"-jar",
cliJar.pathString,
configurationFile.pathString,
)
val process = ProcessBuilder(commandLine)
.directory(normalizedRoot.toFile())
.start()
val capture = listOf(
thread { process.inputStream.copyTo(System.out) },
thread { process.errorStream.copyTo(System.err) },
)
val exitCode = process.waitFor()
capture.forEach { it.join() }
check(exitCode == 0) { "Dokka terminated with exit code $exitCode" }
requireNoUnresolvedTypes(normalizedOutput)
} finally {
configurationFile.deleteIfExists()
}
}
internal data class DokkaAnalysisClasspaths(
val commonNative: List<String>,
val jvm: List<String>,
val js: List<String>,
val wasmJs: List<String>,
)
internal fun resolveDokkaAnalysisClasspaths(moduleClasspath: List<Path>): DokkaAnalysisClasspaths {
val kotlinStdlib = checkNotNull(
moduleClasspath.firstOrNull { path ->
val version = path.parent?.fileName?.toString()
version != null &&
path.parent?.parent?.fileName?.toString() == "kotlin-stdlib" &&
path.fileName?.toString() == "kotlin-stdlib-$version.jar"
}
) {
"Cannot locate the Kotlin JVM stdlib in the module compile classpath; " +
"it is required to discover the matching JS and WasmJS analysis KLIBs"
}
val versionDirectory = checkNotNull(kotlinStdlib.parent)
val version = versionDirectory.fileName.toString()
val kotlinGroupDirectory = checkNotNull(versionDirectory.parent?.parent)
val mavenCacheDirectory = checkNotNull(kotlinGroupDirectory.parent?.parent?.parent)
check(mavenCacheDirectory.fileName.toString() == ".m2.cache") {
"Unexpected Kotlin Toolchain Maven cache layout for $kotlinStdlib"
}
val transformedStdlibDirectory = checkNotNull(mavenCacheDirectory.parent)
.resolve("kotlin/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin/kotlin-stdlib/$version")
val commonStdlib = if (Files.isDirectory(transformedStdlibDirectory)) {
Files.walk(transformedStdlibDirectory, 2).use { paths ->
paths
.filter { Files.isRegularFile(it) }
.filter { it.fileName.toString() == "kotlin-stdlib-commonMain-$version.klib" }
.sorted()
.findFirst()
.orElse(null)
}
} else {
null
}
fun platformStdlib(artifactId: String): Path = kotlinGroupDirectory
.resolve(artifactId)
.resolve(version)
.resolve("$artifactId-$version.klib")
val jsStdlib = platformStdlib("kotlin-stdlib-js")
val wasmJsStdlib = platformStdlib("kotlin-stdlib-wasm-js")
val missing = listOfNotNull(jsStdlib, wasmJsStdlib).filterNot(Files::isRegularFile)
check(commonStdlib != null && missing.isEmpty()) {
"Missing Kotlin analysis KLIB(s) for Dokka below $transformedStdlibDirectory or in " +
"${missing.joinToString()}. " +
"Run `./kotlin build -m kslog -p js -p wasmJs` before documentation generation."
}
return DokkaAnalysisClasspaths(
commonNative = listOf(commonStdlib.pathString),
jvm = moduleClasspath.map { it.pathString },
js = listOf(jsStdlib.pathString),
wasmJs = listOf(wasmJsStdlib.pathString),
)
}
internal fun requireNoUnresolvedTypes(outputDir: Path) {
val unresolvedFiles = Files.walk(outputDir).use { paths ->
paths
.filter { Files.isRegularFile(it) }
.filter { path ->
path.fileName.toString().substringAfterLast('.', "") in setOf("html", "json", "md")
}
.filter { Files.readString(it).contains("Error class: unknown class") }
.map { outputDir.relativize(it).pathString }
.sorted()
.toList()
}
check(unresolvedFiles.isEmpty()) {
"Dokka generated unresolved type placeholders in ${unresolvedFiles.size} file(s): " +
unresolvedFiles.joinToString(limit = 10)
}
}
@OptIn(ExperimentalPathApi::class)
internal fun prepareDokkaOutput(projectRootDir: String, outputDir: Path): Path {
val configuredProjectRoot = Path.of(projectRootDir).toAbsolutePath().normalize()
require(Files.isDirectory(configuredProjectRoot, LinkOption.NOFOLLOW_LINKS)) {
"Project root must be a real directory, not a symbolic link: $configuredProjectRoot"
}
val configuredGeneratedRoot = configuredProjectRoot.resolve("build/dokka").normalize()
val configuredOutput = outputDir.toAbsolutePath().normalize()
require(configuredOutput.parent == configuredGeneratedRoot) {
"Refusing to clean Dokka output outside a direct child of $configuredGeneratedRoot: $configuredOutput"
}
val realProjectRoot = configuredProjectRoot.toRealPath()
val relativeOutput = configuredProjectRoot.relativize(configuredOutput)
val generatedRoot = realProjectRoot.resolve("build/dokka").normalize()
val output = realProjectRoot.resolve(relativeOutput).normalize()
require(output.parent == generatedRoot) {
"Resolved Dokka output escaped $generatedRoot: $output"
}
requireNoSymbolicLinkAncestors(realProjectRoot, output)
Files.createDirectories(generatedRoot)
requireNoSymbolicLinkAncestors(realProjectRoot, output)
val realGeneratedRoot = generatedRoot.toRealPath()
require(realGeneratedRoot.startsWith(realProjectRoot)) {
"Resolved Dokka root escaped the project: $realGeneratedRoot"
}
if (Files.exists(output, LinkOption.NOFOLLOW_LINKS)) {
requireNoSymbolicLinkAncestors(realProjectRoot, output)
val realOutput = output.toRealPath()
require(realOutput.parent == realGeneratedRoot) {
"Resolved Dokka output escaped $realGeneratedRoot: $realOutput"
}
output.deleteRecursively()
}
output.createDirectories()
requireNoSymbolicLinkAncestors(realProjectRoot, output)
val createdOutput = output.toRealPath()
require(createdOutput.parent == realGeneratedRoot) {
"Created Dokka output escaped $realGeneratedRoot: $createdOutput"
}
return output
}
private fun requireNoSymbolicLinkAncestors(root: Path, target: Path) {
require(target.startsWith(root)) { "Path is outside the project root: $target" }
var current = root
for (component in root.relativize(target)) {
current = current.resolve(component)
require(!Files.isSymbolicLink(current)) {
"Refusing Dokka cleanup through symbolic link: $current"
}
}
}
@@ -0,0 +1,110 @@
package dev.inmo.kslog.build.dokka
import java.nio.file.Files
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteRecursively
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@OptIn(ExperimentalPathApi::class)
class RunDokkaOutputGuardTest {
@Test
fun rejectsSymlinkedDokkaAncestorWithoutTouchingOutsideFiles() {
val projectRoot = Files.createTempDirectory("kslog-dokka-project-")
val outside = Files.createTempDirectory("kslog-dokka-outside-")
val sentinel = outside.resolve("sentinel.txt")
sentinel.writeText("preserve me")
val build = projectRoot.resolve("build").createDirectories()
val dokkaLink = build.resolve("dokka")
Files.createSymbolicLink(dokkaLink, outside)
try {
val failure = assertFailsWith<IllegalArgumentException> {
prepareDokkaOutput(projectRoot.toString(), dokkaLink.resolve("html"))
}
assertTrue(failure.message.orEmpty().contains("symbolic link"))
assertEquals("preserve me", sentinel.readText())
} finally {
Files.deleteIfExists(dokkaLink)
projectRoot.deleteRecursively()
outside.deleteRecursively()
}
}
@Test
fun cleansOnlyTheSelectedGeneratedOutputDirectory() {
val projectRoot = Files.createTempDirectory("kslog-dokka-project-")
val html = projectRoot.resolve("build/dokka/html").createDirectories()
val stale = html.resolve("stale.txt")
stale.writeText("old")
val gfmSentinel = projectRoot.resolve("build/dokka/gfm/sentinel.txt")
gfmSentinel.parent.createDirectories()
gfmSentinel.writeText("keep")
try {
val prepared = prepareDokkaOutput(projectRoot.toString(), html)
assertEquals(html.toRealPath(), prepared.toRealPath())
assertTrue(prepared.exists())
assertFalse(stale.exists())
assertEquals("keep", gfmSentinel.readText())
} finally {
projectRoot.deleteRecursively()
}
}
@Test
fun rejectsGeneratedDocumentationWithUnresolvedTypePlaceholders() {
val output = Files.createTempDirectory("kslog-dokka-output-")
val generatedPage = output.resolve("broken.md")
generatedPage.writeText("fun example(): &lt;Error class: unknown class&gt;")
try {
val failure = assertFailsWith<IllegalStateException> {
requireNoUnresolvedTypes(output)
}
assertTrue(failure.message.orEmpty().contains("broken.md"))
} finally {
output.deleteRecursively()
}
}
@Test
fun resolvesMatchingCommonJsAndWasmAnalysisKlibsFromTheToolchainCache() {
val cache = Files.createTempDirectory("kslog-dokka-cache-")
val kotlinGroup = cache.resolve(".m2.cache/org/jetbrains/kotlin")
val version = "2.4.20"
val kotlinStdlib = kotlinGroup
.resolve("kotlin-stdlib/$version/kotlin-stdlib-$version.jar")
val commonStdlib = cache.resolve(
"kotlin/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin/" +
"kotlin-stdlib/$version/test-hash/kotlin-stdlib-commonMain-$version.klib"
)
val jsStdlib = kotlinGroup
.resolve("kotlin-stdlib-js/$version/kotlin-stdlib-js-$version.klib")
val wasmJsStdlib = kotlinGroup
.resolve("kotlin-stdlib-wasm-js/$version/kotlin-stdlib-wasm-js-$version.klib")
val dependency = cache.resolve("dependency.jar")
listOf(kotlinStdlib, commonStdlib, jsStdlib, wasmJsStdlib).forEach {
it.parent.createDirectories()
it.writeText("fixture")
}
dependency.writeText("fixture")
try {
val classpaths = resolveDokkaAnalysisClasspaths(listOf(kotlinStdlib, dependency))
assertEquals(listOf(commonStdlib.toString()), classpaths.commonNative)
assertEquals(listOf(kotlinStdlib.toString(), dependency.toString()), classpaths.jvm)
assertEquals(listOf(jsStdlib.toString()), classpaths.js)
assertEquals(listOf(wasmJsStdlib.toString()), classpaths.wasmJs)
} finally {
cache.deleteRecursively()
}
}
}
+17
View File
@@ -0,0 +1,17 @@
# KSLog GitHub release Toolchain plugin
Kotlin Toolchain 0.13.0 has no built-in GitHub release task. This local plugin
keeps the existing explicit release capability as a narrow wrapper around the
standard `gh release create` CLI.
It extracts the matching version section from `CHANGELOG.md`, preserves the
legacy tag/title/target conventions, accepts the existing
`GITHUB_RELEASE_TOKEN` variable plus standard `GH_TOKEN`/`GITHUB_TOKEN`, and
supports a non-network `KSLOG_GITHUB_RELEASE_DRY_RUN=true` validation mode. It
runs the committed release/version consistency preflight before rendering or
creating a release. No CI workflow invokes it.
`gh release create` does not modify local git state, but GitHub may create the
remote tag at `--target` when the requested tag does not already exist. A real
invocation therefore mutates both the remote release and potentially the remote
tag; dry-run mode performs neither action.
+15
View File
@@ -0,0 +1,15 @@
product: jvm/amper-plugin
description: Explicit GitHub release command backed by the standard GitHub CLI.
pluginInfo:
id: github-release
settingsClass: dev.inmo.kslog.build.release.GitHubReleaseSettings
settings:
jvm:
jdk:
version: 17
release: 17
kotlin:
version: 2.4.20
+9
View File
@@ -0,0 +1,9 @@
tasks:
release:
action: !dev.inmo.kslog.build.release.githubRelease
projectRootDir: ${project.rootDir}
settings: ${pluginSettings}
commands:
- name: githubRelease
performedBy: release
@@ -0,0 +1,86 @@
package dev.inmo.kslog.build.release
import org.jetbrains.amper.plugins.ExecutionAvoidance
import org.jetbrains.amper.plugins.TaskAction
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.deleteIfExists
import kotlin.io.path.isRegularFile
import kotlin.io.path.pathString
import kotlin.io.path.writeText
@TaskAction(ExecutionAvoidance.Disabled)
fun githubRelease(
projectRootDir: String,
settings: GitHubReleaseSettings,
) {
val root = Path.of(projectRootDir).toAbsolutePath().normalize()
val changelog = root.resolve(settings.changelogFile).normalize()
require(changelog.startsWith(root) && changelog.isRegularFile()) {
"Changelog must be an existing file inside the module: $changelog"
}
val releasePreflight = root.resolve("verification/check_release_consistency.py")
require(releasePreflight.isRegularFile()) { "Missing release consistency preflight: $releasePreflight" }
runCommand(root, listOf("python3", releasePreflight.pathString))
val parser = root.resolve("changelog_parser.sh")
require(parser.isRegularFile()) { "Missing changelog parser: $parser" }
val notes = runCommand(root, listOf("bash", parser.pathString, settings.version, changelog.pathString))
.trim()
require(notes.isNotEmpty()) { "No CHANGELOG.md section found for ${settings.version}" }
val tag = "${settings.tagPrefix}${settings.version}"
val targetCommitish = settings.targetCommitish.ifBlank { settings.version }
val repository = "${settings.owner}/${settings.repository}"
if (System.getenv("KSLOG_GITHUB_RELEASE_DRY_RUN").toBoolean()) {
println("Dry run: GitHub release $repository tag=$tag target=$targetCommitish title=${settings.version}")
println(notes)
return
}
val token = System.getenv("GITHUB_RELEASE_TOKEN")
?: System.getenv("GH_TOKEN")
?: System.getenv("GITHUB_TOKEN")
?: error("Set GITHUB_RELEASE_TOKEN, GH_TOKEN, or GITHUB_TOKEN before creating a release")
val notesFile = Files.createTempFile("kslog-release-notes-", ".md")
try {
notesFile.writeText(notes)
val process = ProcessBuilder(
"gh",
"release",
"create",
tag,
"--repo",
repository,
"--title",
settings.version,
"--target",
targetCommitish,
"--notes-file",
notesFile.pathString,
)
.directory(root.toFile())
.redirectErrorStream(true)
process.environment()["GH_TOKEN"] = token
val started = process.start()
val output = started.inputStream.bufferedReader().use { it.readText() }
val exitCode = started.waitFor()
check(exitCode == 0) { "gh release create failed with exit code $exitCode:\n$output" }
print(output)
} finally {
notesFile.deleteIfExists()
}
}
private fun runCommand(workingDirectory: Path, command: List<String>): String {
val process = ProcessBuilder(command)
.directory(workingDirectory.toFile())
.redirectErrorStream(true)
.start()
val output = process.inputStream.bufferedReader().use { it.readText() }
val exitCode = process.waitFor()
check(exitCode == 0) { "Command failed with exit code $exitCode: ${command.first()}\n$output" }
return output
}
@@ -0,0 +1,13 @@
package dev.inmo.kslog.build.release
import org.jetbrains.amper.plugins.Configurable
@Configurable
interface GitHubReleaseSettings {
val owner: String
val repository: String
val version: String
val tagPrefix: String get() = "v"
val targetCommitish: String get() = ""
val changelogFile: String get() = "CHANGELOG.md"
}
+8
View File
@@ -0,0 +1,8 @@
modules:
- kslog
- plugins/dokka
- plugins/github-release
plugins:
- //plugins/dokka
- //plugins/github-release
+63 -42
View File
@@ -1,7 +1,8 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
archiveClassifier = 'javadoc'
}
publishing {
@@ -19,50 +20,47 @@ publishing {
}
developers {
developer {
id = "Akkihi"
name = "Akkihi"
email = "akkihiguy@gmail.com"
}
developer {
id = "Akkihi"
name = "Akkihi"
email = "akkihiguy@gmail.com"
}
developer {
id = "InsanusMokrassar"
name = "InsanusMokrassar"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "InsanusMokrassar"
name = "InsanusMokrassar"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "MIT License"
url = "https://opensource.org/licenses/MIT"
}
}
}
repositories {
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/KSLog")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
license {
name = "MIT License"
url = "https://opensource.org/licenses/MIT"
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
repositories {
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
@@ -71,16 +69,39 @@ publishing {
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
// Workaround to make android sign operations depend on signing tasks
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks)
}
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
}
+1 -1
View File
@@ -1 +1 @@
{"licenses":[{"id":"MIT","title":"MIT License","url":"https://opensource.org/licenses/MIT"}],"mavenConfig":{"name":"${project.name}","description":"Simple logging library for kotlin","url":"https://github.com/InsanusMokrassar/KSLog","vcsUrl":"https://github.com/InsanusMokrassar/KSLog.git","developers":[{"id":"Akkihi","name":"Akkihi","eMail":"akkihiguy@gmail.com"},{"id":"InsanusMokrassar","name":"InsanusMokrassar","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/KSLog"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
{"licenses":[{"id":"MIT","title":"MIT License","url":"https://opensource.org/licenses/MIT"}],"mavenConfig":{"name":"${project.name}","description":"Simple logging library for kotlin","url":"https://github.com/InsanusMokrassar/KSLog","vcsUrl":"https://github.com/InsanusMokrassar/KSLog.git","developers":[{"id":"Akkihi","name":"Akkihi","eMail":"akkihiguy@gmail.com"},{"id":"InsanusMokrassar","name":"InsanusMokrassar","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}
-2
View File
@@ -1,3 +1 @@
rootProject.name = 'kslog'
enableFeaturePreview("VERSION_CATALOGS")
-6
View File
@@ -1,6 +0,0 @@
package dev.inmo.kslog.common
val Any.logTag
get() = this::class.simpleName ?: error("Unable to retrieve log tag")
val Any.logger: KSLog
get() = TagLogger(logTag)
-73
View File
@@ -1,73 +0,0 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.kslog.common
inline fun KSLog.log(level: LogLevel, tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = performLog(level, tag, messageBuilder(), e)
inline fun KSLog.log(level: LogLevel, message: String, e: Throwable? = null) = log(level, null, e) { message }
inline fun KSLog.log(level: LogLevel, tag: String, message: String, e: Throwable? = null) = log(level, tag, e) { message }
inline fun KSLog.verbose(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(LogLevel.VERBOSE, tag, e, messageBuilder)
inline fun KSLog.verbose(message: String, e: Throwable? = null) = log(LogLevel.VERBOSE, message, e)
inline fun KSLog.verbose(tag: String, message: String, e: Throwable? = null) = log(LogLevel.VERBOSE, tag, message, e)
inline fun KSLog.info(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(LogLevel.INFO, tag, e, messageBuilder)
inline fun KSLog.info(message: String, e: Throwable? = null) = log(LogLevel.INFO, message, e)
inline fun KSLog.info(tag: String, message: String, e: Throwable? = null) = log(LogLevel.INFO, tag, message, e)
inline fun KSLog.warning(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(LogLevel.WARNING, tag, e, messageBuilder)
inline fun KSLog.warning(message: String, e: Throwable? = null) = log(LogLevel.WARNING, message, e)
inline fun KSLog.warning(tag: String, message: String, e: Throwable? = null) = log(LogLevel.WARNING, tag, message, e)
inline fun KSLog.error(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(LogLevel.ERROR, tag, e, messageBuilder)
inline fun KSLog.error(message: String, e: Throwable? = null) = log(LogLevel.ERROR, message, e)
inline fun KSLog.error(tag: String, message: String, e: Throwable? = null) = log(LogLevel.ERROR, tag, message, e)
inline fun KSLog.assert(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(LogLevel.ASSERT, tag, e, messageBuilder)
inline fun KSLog.assert(message: String, e: Throwable? = null) = log(LogLevel.ASSERT, message, e)
inline fun KSLog.assert(tag: String, message: String, e: Throwable? = null) = log(LogLevel.ASSERT, tag, message, e)
inline fun KSLog.debug(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(LogLevel.DEBUG, tag, e, messageBuilder)
inline fun KSLog.debug(message: String, e: Throwable? = null) = log(LogLevel.DEBUG, message, e)
inline fun KSLog.debug(tag: String, message: String, e: Throwable? = null) = log(LogLevel.DEBUG, tag, message, e)
// ----- Short names
inline fun KSLog.l(level: LogLevel, tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = log(level, tag, e, messageBuilder)
inline fun KSLog.l(level: LogLevel, message: String, e: Throwable? = null) = log(level, message, e)
inline fun KSLog.l(level: LogLevel, tag: String, message: String, e: Throwable? = null) = log(level, tag, message, e)
inline fun KSLog.v(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = verbose(tag, e, messageBuilder)
inline fun KSLog.v(message: String, e: Throwable? = null) = verbose(message, e)
inline fun KSLog.v(tag: String, message: String, e: Throwable? = null) = verbose(tag, message, e)
inline fun KSLog.i(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = info(tag, e, messageBuilder)
inline fun KSLog.i(message: String, e: Throwable? = null) = info(message, e)
inline fun KSLog.i(tag: String, message: String, e: Throwable? = null) = info(tag, message, e)
inline fun KSLog.w(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = warning(tag, e, messageBuilder)
inline fun KSLog.w(message: String, e: Throwable? = null) = warning(message, e)
inline fun KSLog.w(tag: String, message: String, e: Throwable? = null) = warning(tag, message, e)
inline fun KSLog.e(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = error(tag, e, messageBuilder)
inline fun KSLog.e(message: String, e: Throwable? = null) = error(message, e)
inline fun KSLog.e(tag: String, message: String, e: Throwable? = null) = error(tag, message, e)
inline fun KSLog.wtf(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = assert(tag, e, messageBuilder)
inline fun KSLog.wtf(message: String, e: Throwable? = null) = assert(message, e)
inline fun KSLog.wtf(tag: String, message: String, e: Throwable? = null) = assert(tag, message, e)
inline fun KSLog.d(tag: String? = null, e: Throwable? = null, messageBuilder: () -> String) = debug(tag, e, messageBuilder)
inline fun KSLog.d(message: String, e: Throwable? = null) = debug(message, e)
inline fun KSLog.d(tag: String, message: String, e: Throwable? = null) = debug(tag, message, e)
-74
View File
@@ -1,74 +0,0 @@
package dev.inmo.kslog.common
enum class LogLevel {
DEBUG,
VERBOSE,
INFO,
WARNING,
ERROR,
ASSERT,
}
interface KSLog {
fun performLog(level: LogLevel, tag: String?, message: String, throwable: Throwable?)
companion object : KSLog {
private var defaultLogger: KSLog? = null
var default: KSLog
get() {
return defaultLogger ?: KSLog("app").also {
defaultLogger = it
}
}
set(value) {
defaultLogger = value
}
override fun performLog(level: LogLevel, tag: String?, message: String, throwable: Throwable?) = default.performLog(level, tag, message, throwable)
}
}
operator fun KSLog.invoke(performLogCallback: (level: LogLevel, tag: String?, message: String, throwable: Throwable?) -> Unit) = CallbackKSLog(performLogCallback)
class CallbackKSLog(
private val performLogCallback: (level: LogLevel, tag: String?, message: String, throwable: Throwable?) -> Unit
) : KSLog {
override fun performLog(level: LogLevel, tag: String?, message: String, throwable: Throwable?) = performLogCallback(level, tag, message, throwable)
}
expect fun KSLog(
defaultTag: String,
filter: MessageFilter = { _, _, _, _ -> true },
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog
fun KSLog(
defaultTag: String,
levels: Iterable<LogLevel>,
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog {
val levels = levels.toSet()
return KSLog (defaultTag, { l, _, _, _ ->
l in levels
}, messageFormatter)
}
fun KSLog(
defaultTag: String,
firstLevel: LogLevel,
secondLevel: LogLevel,
vararg otherLevels: LogLevel,
messageFormatter: MessageFormatter = defaultMessageFormatter,
): KSLog = KSLog(defaultTag, setOf(firstLevel, secondLevel, *otherLevels), messageFormatter)
fun KSLog(
defaultTag: String,
minLoggingLevel: LogLevel,
messageFormatter: MessageFormatter = defaultMessageFormatter,
): KSLog = KSLog (
defaultTag,
{ l, _, _, _ ->
minLoggingLevel.ordinal <= l.ordinal
},
messageFormatter
)
@@ -1,6 +0,0 @@
package dev.inmo.kslog.common
typealias MessageFormatter = (l: LogLevel, t: String, m: String, Throwable?) -> String
typealias MessageFilter = (l: LogLevel, t: String?, m: String, Throwable?) -> Boolean
val defaultMessageFormatter: MessageFormatter = { l, t, m, _ -> "[$l] $t - $m" }
-21
View File
@@ -1,21 +0,0 @@
package dev.inmo.kslog.common
actual fun KSLog(
defaultTag: String,
filter: MessageFilter,
messageFormatter: MessageFormatter
): KSLog = KSLog { l, t, m, e ->
if (!filter(l, t, m, e)) return@KSLog
val text = messageFormatter(l, t?:defaultTag, m, e)
val args = e ?.let {
arrayOf(text, e)
} ?: arrayOf(text)
when (l) {
LogLevel.DEBUG -> console.log(*args)
LogLevel.VERBOSE,
LogLevel.INFO -> console.info(*args)
LogLevel.WARNING -> console.warn(*args)
LogLevel.ERROR,
LogLevel.ASSERT -> console.error(*args)
}
}
-62
View File
@@ -1,62 +0,0 @@
package dev.inmo.kslog.common
import java.util.logging.Level
import java.util.logging.Logger
fun KSLog(
defaultTag: String,
logger: Logger,
filter: MessageFilter = { _, _, _, _ -> true },
messageFormatter: MessageFormatter = defaultMessageFormatter
) = KSLog { l, t, m, e ->
if (!filter(l, t, m, e)) return@KSLog
val text = messageFormatter(l,t ?: defaultTag,m,e)
logger.log(
when(l) {
LogLevel.DEBUG -> Level.FINEST
LogLevel.VERBOSE -> Level.FINE
LogLevel.INFO -> Level.INFO
LogLevel.WARNING -> Level.WARNING
LogLevel.ERROR -> Level.SEVERE
LogLevel.ASSERT -> Level.SEVERE
},
text,
e
)
}
fun KSLog(
defaultTag: String,
logger: Logger,
levels: Iterable<LogLevel>,
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog {
val levels = levels.toSet()
return KSLog (defaultTag, logger, { l, _, _, _ ->
l in levels
}, messageFormatter)
}
fun KSLog(
defaultTag: String,
logger: Logger,
firstLevel: LogLevel,
secondLevel: LogLevel,
vararg otherLevels: LogLevel,
messageFormatter: MessageFormatter = defaultMessageFormatter,
): KSLog = KSLog (defaultTag, logger, setOf(firstLevel, secondLevel, *otherLevels), messageFormatter)
fun KSLog(
defaultTag: String,
logger: Logger,
minLoggingLevel: LogLevel = LogLevel.values().first(),
messageFormatter: MessageFormatter = defaultMessageFormatter
): KSLog = KSLog (defaultTag, logger, { l, _, _, _ ->
minLoggingLevel.ordinal <= l.ordinal
}, messageFormatter)
actual fun KSLog(
defaultTag: String,
filter: MessageFilter,
messageFormatter: MessageFormatter
): KSLog = KSLog(defaultTag, Logger.getLogger("KSLog"), filter, messageFormatter)
-1
View File
@@ -1 +0,0 @@
<manifest package="dev.inmo.kslog"/>
-21
View File
@@ -1,21 +0,0 @@
package dev.inmo.kslog.common
import android.util.Log
actual fun KSLog(
defaultTag: String,
filter: MessageFilter,
messageFormatter: MessageFormatter
): KSLog = KSLog { l, t, m, e ->
if (!filter(l, t ?: defaultTag, m, e)) return@KSLog
val tag = t ?: defaultTag
val text = messageFormatter(l, tag, m, e)
when(l) {
LogLevel.DEBUG -> Log.d(tag, text, e)
LogLevel.VERBOSE -> Log.v(tag, text, e)
LogLevel.INFO -> Log.i(tag, text, e)
LogLevel.WARNING -> Log.w(tag, text, e)
LogLevel.ERROR -> Log.e(tag, text, e)
LogLevel.ASSERT -> Log.wtf(tag, text, e)
}
}
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Fail when KSLog's duplicated release version settings diverge."""
from __future__ import annotations
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
def fail(message: str) -> None:
raise SystemExit(f"release consistency check failed: {message}")
def gradle_property(name: str) -> str:
for raw_line in (ROOT / "gradle.properties").read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
if key.strip() == name:
return value.strip()
fail(f"missing {name!r} in gradle.properties")
def yaml_scalar(path: tuple[str, ...]) -> str:
stack: list[tuple[int, str]] = []
for raw_line in (ROOT / "kslog/module.yaml").read_text(encoding="utf-8").splitlines():
if not raw_line.strip() or raw_line.lstrip().startswith("#"):
continue
indent = len(raw_line) - len(raw_line.lstrip(" "))
content = raw_line.strip()
if ":" not in content or content.startswith("-"):
continue
key, value = content.split(":", 1)
while stack and stack[-1][0] >= indent:
stack.pop()
candidate = tuple(item[1] for item in stack) + (key,)
value = value.strip().strip('"\'')
if candidate == path:
if not value:
fail(f"empty YAML scalar at {'.'.join(path)}")
return value
if not value:
stack.append((indent, key))
fail(f"missing YAML scalar at {'.'.join(path)}")
def main() -> None:
version = gradle_property("version")
copies = {
"plugins.dokka.moduleVersion": yaml_scalar(("plugins", "dokka", "moduleVersion")),
"plugins.github-release.version": yaml_scalar(("plugins", "github-release", "version")),
"plugins.github-release.targetCommitish": yaml_scalar(
("plugins", "github-release", "targetCommitish")
),
"settings.publishing.version": yaml_scalar(("settings", "publishing", "version")),
}
mismatches = {name: value for name, value in copies.items() if value != version}
if mismatches:
rendered = ", ".join(f"{name}={value!r}" for name, value in mismatches.items())
fail(f"gradle.properties version is {version!r}, but {rendered}")
expected_heading = f"## {version}"
headings = [
(index, line)
for index, line in enumerate(
(ROOT / "CHANGELOG.md").read_text(encoding="utf-8").splitlines(), start=1
)
if line == expected_heading
]
if len(headings) != 1:
fail(f"expected exactly one literal {expected_heading!r} heading, found {len(headings)}")
print(
f"release consistency: PASS version={version} "
f"copies={len(copies)} changelogLine={headings[0][0]}"
)
if __name__ == "__main__":
try:
main()
except (OSError, UnicodeError) as error:
fail(str(error))
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Verify wrapper checkout attributes and on-disk line endings."""
from pathlib import Path
import subprocess
ROOT = Path(__file__).resolve().parents[1]
def fail(message: str) -> None:
raise SystemExit(f"wrapper line-ending check failed: {message}")
shell_bytes = (ROOT / "kotlin").read_bytes()
batch_bytes = (ROOT / "kotlin.bat").read_bytes()
if b"\r" in shell_bytes:
fail("kotlin must contain LF line endings only")
if b"\r\n" not in batch_bytes or b"\n" in batch_bytes.replace(b"\r\n", b""):
fail("kotlin.bat must contain CRLF line endings only")
completed = subprocess.run(
["git", "-C", str(ROOT), "check-attr", "text", "eol", "--", "kotlin", "kotlin.bat"],
check=True,
text=True,
stdout=subprocess.PIPE,
)
attributes = {}
for line in completed.stdout.splitlines():
path, attribute, value = line.split(": ", 2)
attributes[(path, attribute)] = value
expected = {
("kotlin", "text"): "set",
("kotlin", "eol"): "lf",
("kotlin.bat", "text"): "set",
("kotlin.bat", "eol"): "crlf",
}
if attributes != expected:
fail(f"unexpected attributes: {attributes}")
print("wrapper line endings: PASS kotlin=LF kotlin.bat=CRLF checkoutAttributes=PASS")
+39
View File
@@ -0,0 +1,39 @@
plugins {
id "org.jetbrains.kotlin.multiplatform" version "2.4.20"
id "com.android.kotlin.multiplatform.library" version "9.1.1"
}
def externalBuildDir = providers.environmentVariable("KSLOG_CONSUMER_BUILD_DIR").orNull
if (externalBuildDir != null) {
layout.buildDirectory.set(file(externalBuildDir))
}
kotlin {
jvm()
js {
nodejs()
}
wasmJs {
nodejs()
}
linuxX64()
linuxArm64()
mingwX64()
android {
namespace = "dev.inmo.kslog.verification"
compileSdk {
version = release(37)
}
minSdk {
version = release(21)
}
}
sourceSets {
commonMain {
dependencies {
implementation "dev.inmo:kslog:2.1.0"
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
def testRepository = providers.environmentVariable("KSLOG_TEST_REPOSITORY").orNull
if (testRepository == null) {
throw new GradleException("KSLOG_TEST_REPOSITORY must point to an isolated Maven repository")
}
maven { url = uri(testRepository) }
google()
mavenCentral()
}
}
rootProject.name = "kslog-publication-consumer"
@@ -0,0 +1,14 @@
package dev.inmo.kslog.verification
import dev.inmo.kslog.common.CallbackKSLog
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
fun exercisePublishedApi(): String {
var captured = ""
val logger: KSLog = CallbackKSLog { level, tag, message, throwable ->
captured = listOf(level, tag, message, throwable).joinToString("|")
}
logger.performLog(LogLevel.INFO, "consumer", "message", null)
return captured
}
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
fixture_dir=$(mktemp -d /tmp/kslog-changelog-parser.XXXXXX)
trap 'rm -rf -- "$fixture_dir"' EXIT
cat > "$fixture_dir/CHANGELOG.md" <<'EOF'
# Changelog
## 2x1x0
regex lookalike must not match
## 2.1.0
literal section
## 2.0.0
older section
EOF
actual=$(bash "$root_dir/changelog_parser.sh" 2.1.0 "$fixture_dir/CHANGELOG.md")
if [[ "$actual" != $'\nliteral section' ]]; then
printf 'unexpected parser output:\n%s\n' "$actual" >&2
exit 1
fi
stdin_actual=$(bash "$root_dir/changelog_parser.sh" 2.1.0 < "$fixture_dir/CHANGELOG.md")
if [[ "$stdin_actual" != "$actual" ]]; then
printf 'stdin/file parser behavior differs\n' >&2
exit 1
fi
if bash "$root_dir/changelog_parser.sh" 9.9.9 "$fixture_dir/CHANGELOG.md" >/dev/null 2>&1; then
printf 'missing literal heading unexpectedly succeeded\n' >&2
exit 1
fi
printf 'changelog parser: PASS literal heading selected; regex lookalike rejected\n'
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
set -euo pipefail
root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
base_commit=${KSLOG_MIGRATION_BASE_COMMIT:-0b9fb30862c7319bb228039ee33eef9a0ddef95c}
work_dir=$(mktemp -d /tmp/kslog-gradle-verifier.XXXXXX)
cleanup() {
if [[ "${KSLOG_KEEP_VERIFICATION_TEMP:-false}" == "true" ]]; then
printf 'verification workspace retained at %s\n' "$work_dir"
else
rm -rf -- "$work_dir"
fi
}
trap cleanup EXIT
for command_name in git tar python3; do
command -v "$command_name" >/dev/null || {
printf 'required command is unavailable: %s\n' "$command_name" >&2
exit 1
}
done
git -C "$root_dir" cat-file -e "$base_commit^{commit}"
baseline_dir="$work_dir/baseline"
baseline_repo="$work_dir/baseline-m2"
current_repo="$work_dir/current-m2"
gradle_home="$work_dir/gradle-home"
gnupg_home="$work_dir/gnupg"
archive="$work_dir/baseline.tar"
mkdir -p "$baseline_dir" "$baseline_repo" "$current_repo" "$gradle_home" "$gnupg_home"
chmod 700 "$gnupg_home"
git -C "$root_dir" archive --format=tar --output="$archive" "$base_commit"
tar -xf "$archive" -C "$baseline_dir"
cache_source=${KSLOG_GRADLE_CACHE_SOURCE:-${GRADLE_USER_HOME:-${HOME}/.gradle}}
for cache_entry in caches wrapper; do
if [[ -d "$cache_source/$cache_entry" ]]; then
ln -s "$cache_source/$cache_entry" "$gradle_home/$cache_entry"
fi
done
if [[ -e "$gradle_home/gradle.properties" || -e "$gradle_home/init.d" ]]; then
printf 'isolated Gradle home unexpectedly contains user configuration\n' >&2
exit 1
fi
run_publication() {
local project_dir=$1
local repository_dir=$2
local log_file=$3
(
cd "$project_dir"
unset \
GITHUB_RELEASE_TOKEN GH_TOKEN GITHUB_TOKEN \
INMONEXUS_USER INMONEXUS_PASSWORD \
SONATYPE_USER SONATYPE_PASSWORD PUBLISHING_TYPE \
KOTLIN_TOOLCHAIN_SIGNING_KEY KOTLIN_TOOLCHAIN_SIGNING_KEY_PASSPHRASE \
GRADLE_OPTS JAVA_TOOL_OPTIONS _JAVA_OPTIONS
export GRADLE_USER_HOME="$gradle_home"
export GNUPGHOME="$gnupg_home"
./gradlew --no-daemon \
-Dmaven.repo.local="$repository_dir" \
clean publishToMavenLocal
) | tee "$log_file"
if find "$repository_dir" -type f -name '*.asc' -print -quit | grep -q .; then
printf 'isolated publication unexpectedly produced signatures: %s\n' "$repository_dir" >&2
exit 1
fi
if grep -Eq '^> Task :sign[A-Z]' "$log_file"; then
printf 'isolated publication unexpectedly executed a signing task\n' >&2
exit 1
fi
}
printf 'Publishing untouched baseline %s to an isolated repository\n' "$base_commit"
run_publication "$baseline_dir" "$baseline_repo" "$work_dir/baseline-publish.log"
printf 'Publishing post-move Gradle fallback to an isolated repository\n'
run_publication "$root_dir" "$current_repo" "$work_dir/current-publish.log"
python3 "$root_dir/verification/verify_gradle_publication.py" \
--project-root "$root_dir" \
--base-commit "$base_commit" \
--baseline-repository "$baseline_repo" \
--current-repository "$current_repo"
printf 'Compiling the committed consumer probe against the post-move publication\n'
(
unset \
GITHUB_RELEASE_TOKEN GH_TOKEN GITHUB_TOKEN \
INMONEXUS_USER INMONEXUS_PASSWORD \
SONATYPE_USER SONATYPE_PASSWORD PUBLISHING_TYPE \
KOTLIN_TOOLCHAIN_SIGNING_KEY KOTLIN_TOOLCHAIN_SIGNING_KEY_PASSPHRASE \
GRADLE_OPTS JAVA_TOOL_OPTIONS _JAVA_OPTIONS
export GRADLE_USER_HOME="$gradle_home"
export GNUPGHOME="$gnupg_home"
export KSLOG_TEST_REPOSITORY="$current_repo"
export KSLOG_CONSUMER_BUILD_DIR="$work_dir/consumer-build"
"$root_dir/gradlew" --no-daemon \
--project-dir "$root_dir/verification/consumer" \
--project-cache-dir "$work_dir/consumer-project-cache" \
assemble
) | tee "$work_dir/consumer.log"
printf 'Gradle fallback verification: PASS baseline=%s primaryFiles=46 consumer=PASS\n' "$base_commit"
+292
View File
@@ -0,0 +1,292 @@
#!/usr/bin/env python3
"""Compare post-move Gradle publication/API output with the untouched base."""
from __future__ import annotations
import argparse
import hashlib
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import xml.etree.ElementTree as ET
import zipfile
EXPECTED_ARTIFACT_IDS = {
"kslog",
"kslog-android",
"kslog-js",
"kslog-jvm",
"kslog-linuxarm32hfp",
"kslog-linuxarm64",
"kslog-linuxx64",
"kslog-mingwx64",
"kslog-wasm-js",
}
EXPECTED_VERSION = "2.1.0"
SOURCE_ROOTS = {
"kslog/src": "src/commonMain/kotlin",
"kslog/src@android": "src/androidMain/kotlin",
"kslog/src@js": "src/jsMain/kotlin",
"kslog/src@jvm": "src/jvmMain/kotlin",
"kslog/src@native": "src/nativeMain/kotlin",
"kslog/src@wasmJs": "src/wasmJsMain/kotlin",
}
def fail(message: str) -> None:
raise SystemExit(f"Gradle fallback verification failed: {message}")
def primary_files(repository: Path) -> dict[str, Path]:
group_root = repository / "dev/inmo"
if not group_root.is_dir():
fail(f"missing publication group directory: {group_root}")
ignored_names = {"maven-metadata-local.xml", "_remote.repositories"}
ignored_suffixes = (".asc", ".md5", ".sha1", ".sha256", ".sha512", ".lastUpdated")
result = {}
for path in sorted(group_root.rglob("*")):
if not path.is_file() or path.name in ignored_names or path.name.endswith(ignored_suffixes):
continue
result[path.relative_to(group_root).as_posix()] = path
return result
def zip_payload(path: Path) -> dict[str, str]:
with zipfile.ZipFile(path) as archive:
return {
info.filename: hashlib.sha256(archive.read(info)).hexdigest()
for info in archive.infolist()
if not info.is_dir() and info.filename != "META-INF/MANIFEST.MF"
}
def xml_text(element: ET.Element, child: str) -> str:
found = element.find(f"{{*}}{child}")
return "" if found is None or found.text is None else found.text.strip()
def pom_model(path: Path) -> dict[str, object]:
root = ET.parse(path).getroot()
def records(container: str, record: str, fields: tuple[str, ...]) -> tuple[tuple[str, ...], ...]:
parent = root.find(f"{{*}}{container}")
if parent is None:
return ()
return tuple(
sorted(tuple(xml_text(item, field) for field in fields) for item in parent.findall(f"{{*}}{record}"))
)
scm = root.find("{*}scm")
return {
"groupId": xml_text(root, "groupId"),
"artifactId": xml_text(root, "artifactId"),
"version": xml_text(root, "version"),
"packaging": xml_text(root, "packaging"),
"name": xml_text(root, "name"),
"description": xml_text(root, "description"),
"url": xml_text(root, "url"),
"licenses": records("licenses", "license", ("name", "url")),
"developers": records("developers", "developer", ("id", "name", "email")),
"scm": ()
if scm is None
else (
xml_text(scm, "url"),
xml_text(scm, "connection"),
xml_text(scm, "developerConnection"),
),
"dependencies": records(
"dependencies",
"dependency",
("groupId", "artifactId", "version", "scope", "type", "classifier", "optional"),
),
}
def validate_required_pom(path: Path, model: dict[str, object]) -> None:
expected_artifact_id = path.parent.parent.name
required = {
"groupId": "dev.inmo",
"artifactId": expected_artifact_id,
"version": EXPECTED_VERSION,
"name": "kslog",
"description": "Simple logging library for kotlin",
"url": "https://github.com/InsanusMokrassar/KSLog",
"licenses": (("MIT License", "https://opensource.org/licenses/MIT"),),
"developers": (
("Akkihi", "Akkihi", "akkihiguy@gmail.com"),
("InsanusMokrassar", "InsanusMokrassar", "ovsyannikov.alexey95@gmail.com"),
),
"scm": (
"https://github.com/InsanusMokrassar/KSLog.git",
"",
"scm:git:[fetch=]https://github.com/InsanusMokrassar/KSLog.git[push=]https://github.com/InsanusMokrassar/KSLog.git",
),
}
for field, expected in required.items():
if model[field] != expected:
fail(f"required POM metadata mismatch in {path}: {field}={model[field]!r}")
def compare_publications(baseline_repository: Path, current_repository: Path) -> None:
baseline = primary_files(baseline_repository)
current = primary_files(current_repository)
if len(baseline) != 46 or len(current) != 46:
fail(f"expected 46 primary files, got baseline={len(baseline)} current={len(current)}")
if baseline.keys() != current.keys():
missing = sorted(baseline.keys() - current.keys())
added = sorted(current.keys() - baseline.keys())
fail(f"publication inventory differs; missing={missing}, added={added}")
artifact_ids = {relative.split("/", 1)[0] for relative in baseline}
if artifact_ids != EXPECTED_ARTIFACT_IDS:
fail(f"published artifact IDs differ: {sorted(artifact_ids)}")
source_jars = [relative for relative in baseline if relative.endswith("-sources.jar")]
if len(source_jars) != 9:
fail(f"expected 9 source JARs, found {len(source_jars)}")
for relative in source_jars:
if zip_payload(baseline[relative]) != zip_payload(current[relative]):
fail(f"source JAR payload differs: {relative}")
pom_paths = [relative for relative in baseline if relative.endswith(".pom")]
if len(pom_paths) != 9:
fail(f"expected 9 POMs, found {len(pom_paths)}")
for relative in pom_paths:
baseline_model = pom_model(baseline[relative])
current_model = pom_model(current[relative])
validate_required_pom(baseline[relative], baseline_model)
validate_required_pom(current[relative], current_model)
if baseline_model != current_model:
fail(f"POM model differs: {relative}")
print("publication inventory: PASS primaryFiles=46 coordinates=9 sourceJars=9 poms=9")
def verify_source_bytes(project_root: Path, base_commit: str) -> None:
checked = 0
for current_root, previous_root in SOURCE_ROOTS.items():
for current_path in sorted((project_root / current_root).rglob("*.kt")):
relative = current_path.relative_to(project_root / current_root)
previous_path = f"{previous_root}/{relative.as_posix()}"
completed = subprocess.run(
["git", "-C", str(project_root), "show", f"{base_commit}:{previous_path}"],
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if completed.returncode != 0:
fail(f"base source is missing: {previous_path}")
if completed.stdout != current_path.read_bytes():
fail(f"production source bytes changed: {current_path.relative_to(project_root)}")
checked += 1
if checked != 20:
fail(f"expected 20 production sources, checked {checked}")
print("production source identity: PASS files=20")
def javap_path() -> str:
java_home = Path(os.environ.get("JAVA_HOME", ""))
candidate = java_home / "bin/javap"
if candidate.is_file():
return str(candidate)
found = shutil.which("javap")
if found is None:
fail("javap is unavailable; set JAVA_HOME to a JDK")
return found
def public_api(archive_path: Path) -> tuple[tuple[str, ...], str, bytes]:
with zipfile.ZipFile(archive_path) as archive:
class_names = tuple(
sorted(
info.filename[:-6].replace("/", ".")
for info in archive.infolist()
if info.filename.endswith(".class") and not info.filename.startswith("META-INF/")
)
)
module_entry = "META-INF/dev.inmo_kslog.kotlin_module"
if module_entry not in archive.namelist():
fail(f"missing Kotlin module identity in {archive_path}")
module_bytes = archive.read(module_entry)
output = []
for class_name in class_names:
completed = subprocess.run(
[javap_path(), "-public", "-s", "-constants", "-classpath", str(archive_path), class_name],
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
if completed.returncode != 0:
fail(f"javap failed for {class_name}: {completed.stderr.strip()}")
output.append(completed.stdout)
return class_names, "\n".join(output), module_bytes
def nested_classes_jar(aar: Path, destination: Path) -> Path:
with zipfile.ZipFile(aar) as archive:
try:
classes = archive.read("classes.jar")
except KeyError as error:
fail(f"Gradle AAR lacks classes.jar: {aar}")
destination.write_bytes(classes)
return destination
def compare_public_jvm_signatures(baseline_repository: Path, current_repository: Path) -> None:
group = Path("dev/inmo")
jvm_relative = group / "kslog-jvm/2.1.0/kslog-jvm-2.1.0.jar"
android_relative = group / "kslog-android/2.1.0/kslog-android-2.1.0.aar"
baseline_jvm = public_api(baseline_repository / jvm_relative)
current_jvm = public_api(current_repository / jvm_relative)
if baseline_jvm != current_jvm:
fail("JVM class list, public method/field signatures, or Kotlin module bytes differ")
if len(current_jvm[0]) != 25:
fail(f"expected 25 JVM classes, found {len(current_jvm[0])}")
with tempfile.TemporaryDirectory(prefix="kslog-android-api-") as temporary:
temporary_path = Path(temporary)
baseline_android = public_api(
nested_classes_jar(baseline_repository / android_relative, temporary_path / "baseline.jar")
)
current_android = public_api(
nested_classes_jar(current_repository / android_relative, temporary_path / "current.jar")
)
if baseline_android != current_android:
fail("Android class list, public method/field signatures, or Kotlin module bytes differ")
if len(current_android[0]) != 24:
fail(f"expected 24 Android classes, found {len(current_android[0])}")
with zipfile.ZipFile(baseline_repository / android_relative) as baseline_aar:
baseline_manifest = baseline_aar.read("AndroidManifest.xml")
with zipfile.ZipFile(current_repository / android_relative) as current_aar:
current_manifest = current_aar.read("AndroidManifest.xml")
if baseline_manifest != current_manifest:
fail("post-move Gradle AndroidManifest.xml differs from the untouched baseline")
print("public JVM signatures: PASS jvmClasses=25 androidClasses=24 module=dev.inmo_kslog")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--project-root", type=Path, required=True)
parser.add_argument("--base-commit", required=True)
parser.add_argument("--baseline-repository", type=Path, required=True)
parser.add_argument("--current-repository", type=Path, required=True)
return parser.parse_args()
def main() -> None:
args = parse_args()
compare_publications(args.baseline_repository, args.current_repository)
verify_source_bytes(args.project_root, args.base_commit)
compare_public_jvm_signatures(args.baseline_repository, args.current_repository)
if __name__ == "__main__":
main()