Add new Kotlin Multiplatform project to gui.
No files matched your search
@@ -0,0 +1,11 @@
|
|||||||
|
*.iml
|
||||||
|
.kotlin
|
||||||
|
build/
|
||||||
|
.DS_Store
|
||||||
|
captures
|
||||||
|
**/module.xcodeproj/*
|
||||||
|
!**/module.xcodeproj/project.pbxproj
|
||||||
|
!**/module.xcodeproj/xcshareddata/
|
||||||
|
!**/module.xcodeproj/project.xcworkspace/
|
||||||
|
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||||
|
xcuserdata
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Nomadic Weather
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="KotlinToolchainAndroidProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AmperProjectManager">
|
||||||
|
<option name="amperProject" value="true" />
|
||||||
|
<option name="projectRoot" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/NomadicWeather.iml" filepath="$PROJECT_DIR$/.idea/NomadicWeather.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
This is a Kotlin Multiplatform project targeting Android, Desktop (JVM), built with the [Kotlin Toolchain](https://kotlin-toolchain.org/dev/).
|
||||||
|
|
||||||
|
- [/androidApp](./androidApp) contains the Android application.
|
||||||
|
- [/desktopApp](./desktopApp) contains the desktop (JVM) application.
|
||||||
|
- [/shared](./shared) holds the code shared across your applications — Compose UI, business logic, and platform-specific implementations. [src](./shared/src) is for common code; the sibling `src@<platform>` folders (for example `src@android`) hold code compiled only for the platform named in the folder.
|
||||||
|
|
||||||
|
The `kotlin` (macOS/Linux) and `kotlin.bat` (Windows) scripts in the project root are self-bootstrapping wrappers for the Kotlin Toolchain: they download the pinned toolchain version on first use, so no separate installation is required. Build the whole project with `./kotlin build`.
|
||||||
|
|
||||||
|
### Running
|
||||||
|
|
||||||
|
Use the run configurations provided by the run widget in your IDE's toolbar. You can also run each module from the command line:
|
||||||
|
|
||||||
|
- Android app: `./kotlin run -m androidApp`
|
||||||
|
- Desktop app: `./kotlin run -m desktopApp`
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Run the project's tests with `./kotlin test`, or `./kotlin test -m <module>` for a single module.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html), [Kotlin Toolchain](https://kotlin-toolchain.org/dev/), [Compose Multiplatform](https://kotlinlang.org/compose-multiplatform/).
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
product: android/app
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- //shared
|
||||||
|
- $libs.androidx.activity.compose
|
||||||
|
- $compose.preview
|
||||||
|
- $compose.uiTooling
|
||||||
|
|
||||||
|
settings:
|
||||||
|
compose: enabled
|
||||||
|
android:
|
||||||
|
namespace: com.hyperling.weather
|
||||||
|
applicationId: com.hyperling.weather
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">NomadicWeather</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
|
<activity
|
||||||
|
android:exported="true"
|
||||||
|
android:name=".MainActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContent {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AppAndroidPreview() {
|
||||||
|
App()
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
product: jvm/app
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- //shared
|
||||||
|
- $compose.desktop.currentOs
|
||||||
|
- $compose.preview
|
||||||
|
- $libs.kotlinx.coroutines.swing
|
||||||
|
- $libs.logback.classic
|
||||||
|
|
||||||
|
settings:
|
||||||
|
compose: enabled
|
||||||
|
jvm:
|
||||||
|
mainClass: com.hyperling.weather.MainKt
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import androidx.compose.ui.window.Window
|
||||||
|
import androidx.compose.ui.window.application
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
Window(onCloseRequest = ::exitApplication, title = "NomadicWeather") {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
#!/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
|
||||||
|
# Establish chain of trust from here by specifying exact checksum of Kotlin Toolchain (and CLI) distribution to be run
|
||||||
|
kotlin_cli_sha256=fa82b9872dcfa04d26c8f800f33d597c99fbffd747a0e9bbce3a9e4424b3a588
|
||||||
|
|
||||||
|
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"
|
||||||
|
temp_file="$cache_dir/download-${short_sha}.$$.bin"
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# An extraction left half-done must never keep the .flag file, which marks $extract_dir as complete: subsequent runs
|
||||||
|
# would skip the extraction and use the broken directory.
|
||||||
|
cleanup_incomplete_provisioning() {
|
||||||
|
rm -f "$download_lock_file" "$temp_file"
|
||||||
|
rm -rf "$extract_dir"
|
||||||
|
}
|
||||||
|
trap cleanup_incomplete_provisioning 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..."
|
||||||
|
|
||||||
|
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" "$@"
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
@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
|
||||||
|
@rem Establish chain of trust from here by specifying the exact checksum of the Kotlin Toolchain distribution to be run
|
||||||
|
set kotlin_cli_sha256=fa82b9872dcfa04d26c8f800f33d597c99fbffd747a0e9bbce3a9e4424b3a588
|
||||||
|
|
||||||
|
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 - native commands like tar don't raise PowerShell errors on failure, so we have to check $LASTEXITCODE.
|
||||||
|
@rem The extraction must never be left half-done with the .flag file in place, because the .flag file marks the
|
||||||
|
@rem target dir as complete, and subsequent runs would then skip the extraction and use the broken directory.
|
||||||
|
@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'; ^
|
||||||
|
^
|
||||||
|
$temp_file = $null; ^
|
||||||
|
$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; ^
|
||||||
|
} ^
|
||||||
|
try { ^
|
||||||
|
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%'; ^
|
||||||
|
if ($LASTEXITCODE -ne 0) { ^
|
||||||
|
throw \"tar failed to extract %moniker% (exit code $LASTEXITCODE)\"; ^
|
||||||
|
} ^
|
||||||
|
} ^
|
||||||
|
} catch { ^
|
||||||
|
Remove-Item '%flag_file%' -Force -ErrorAction Ignore; ^
|
||||||
|
Remove-Item '%target_dir%' -Recurse -Force -ErrorAction Ignore; ^
|
||||||
|
throw; ^
|
||||||
|
} ^
|
||||||
|
^
|
||||||
|
Set-Content '%flag_file%' -Value '%sha%'; ^
|
||||||
|
Write-Host 'Download complete.'; ^
|
||||||
|
Write-Host ''; ^
|
||||||
|
} ^
|
||||||
|
} ^
|
||||||
|
finally { ^
|
||||||
|
if ($temp_file) { ^
|
||||||
|
Remove-Item $temp_file -Force -ErrorAction Ignore; ^
|
||||||
|
} ^
|
||||||
|
$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%
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[versions]
|
||||||
|
logback = "1.6.2"
|
||||||
|
androidx-activity-compose = "1.13.0"
|
||||||
|
androidx-lifecycle = "2.11.0"
|
||||||
|
kotlinx-coroutines = "1.11.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||||
|
androidx-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
||||||
|
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
modules:
|
||||||
|
- androidApp
|
||||||
|
- desktopApp
|
||||||
|
- shared
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="450dp"
|
||||||
|
android:height="450dp"
|
||||||
|
android:viewportWidth="64"
|
||||||
|
android:viewportHeight="64">
|
||||||
|
<path
|
||||||
|
android:pathData="M56.25,18V46L32,60 7.75,46V18L32,4Z"
|
||||||
|
android:fillColor="#6075f2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m41.5,26.5v11L32,43V60L56.25,46V18Z"
|
||||||
|
android:fillColor="#6b57ff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m32,43 l-9.5,-5.5v-11L7.75,18V46L32,60Z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:centerX="23.131"
|
||||||
|
android:centerY="18.441"
|
||||||
|
android:gradientRadius="42.132"
|
||||||
|
android:type="radial">
|
||||||
|
<item android:offset="0" android:color="#FF5383EC"/>
|
||||||
|
<item android:offset="0.867" android:color="#FF7F52FF"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:pathData="M22.5,26.5 L32,21 41.5,26.5 56.25,18 32,4 7.75,18Z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:startX="44.172"
|
||||||
|
android:startY="4.377"
|
||||||
|
android:endX="17.973"
|
||||||
|
android:endY="34.035"
|
||||||
|
android:type="linear">
|
||||||
|
<item android:offset="0" android:color="#FF33C3FF"/>
|
||||||
|
<item android:offset="0.878" android:color="#FF5383EC"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:pathData="m32,21 l9.526,5.5v11L32,43 22.474,37.5v-11z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
product:
|
||||||
|
type: kmp/lib
|
||||||
|
platforms: [ jvm, android ]
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- $compose.runtime
|
||||||
|
- $compose.foundation
|
||||||
|
- $compose.material3
|
||||||
|
- $compose.ui
|
||||||
|
- $compose.components.resources
|
||||||
|
- $compose.preview
|
||||||
|
- $compose.hotReload.runtimeApi
|
||||||
|
- $libs.androidx.lifecycle.viewmodel.compose
|
||||||
|
- $libs.androidx.lifecycle.runtime.compose
|
||||||
|
|
||||||
|
dependencies@android:
|
||||||
|
- $compose.uiTooling
|
||||||
|
|
||||||
|
settings:
|
||||||
|
compose:
|
||||||
|
enabled: true
|
||||||
|
resources:
|
||||||
|
packageName: com.hyperling.weather.resources
|
||||||
|
# Generate public accessors so other modules (and previews) can use Res.*
|
||||||
|
exposedAccessors: true
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.safeContentPadding
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import org.jetbrains.compose.reload.DevelopmentEntryPoint
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
|
||||||
|
import com.hyperling.weather.resources.Res
|
||||||
|
import com.hyperling.weather.resources.compose_multiplatform
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
@DevelopmentEntryPoint
|
||||||
|
fun App() {
|
||||||
|
MaterialTheme {
|
||||||
|
var showContent by remember { mutableStateOf(false) }
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||||
|
.safeContentPadding()
|
||||||
|
.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Button(onClick = { showContent = !showContent }) {
|
||||||
|
Text("Click me!")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(showContent) {
|
||||||
|
val greeting = remember { Greeting().greet() }
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||||
|
Text("Compose: $greeting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
class Greeting {
|
||||||
|
private val platform = getPlatform()
|
||||||
|
|
||||||
|
fun greet(): String {
|
||||||
|
return "Hello, ${platform.name}!"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
interface Platform {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
expect fun getPlatform(): Platform
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
class AndroidPlatform : Platform {
|
||||||
|
override val name: String = "Android ${Build.VERSION.SDK_INT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getPlatform(): Platform = AndroidPlatform()
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
class JVMPlatform: Platform {
|
||||||
|
override val name: String = "Java ${System.getProperty("java.version")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getPlatform(): Platform = JVMPlatform()
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class PlatformTest {
|
||||||
|
@Test
|
||||||
|
fun `platform name is reported`() {
|
||||||
|
assertTrue(getPlatform().name.isNotBlank())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `greeting is built from the platform name`() {
|
||||||
|
assertEquals("Hello, ${getPlatform().name}!", Greeting().greet())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class PlatformAndroidTest {
|
||||||
|
@Test
|
||||||
|
fun `platform name is reported`() {
|
||||||
|
assertTrue(getPlatform().name.isNotBlank())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.hyperling.weather
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class PlatformJvmTest {
|
||||||
|
@Test
|
||||||
|
fun `platform name is reported`() {
|
||||||
|
assertTrue(getPlatform().name.isNotBlank())
|
||||||
|
}
|
||||||
|
}
|
||||||