How to run a Kotlin script on GitHub Actions? - kotlin

I want to run Kotlin scripts in CI without relying on a Gradle project, so I can easily do operations that would be hard to program using shell/bash/batch, and so that I can use libraries if needed.
Having the Kotlin script run only on Ubuntu/Linux is fine, though ideally, there's a way to make it run on Windows and macOS targets as well for platform specific projects.

UPDATE: Kotlin is now pre-installed on GitHub Actions runners, no need to install it beforehand anymore.
First, ensure that you have a proper Kotlin script, ending in .kts, or better, .main.kts as that latter one will be recognized better by the IDE (e.g. IntelliJ IDEA, Android Studio), especially when it comes to autocompletion and type analysis.
Second, ensure that its first line is the shebang pointing to the right place:
#!/usr/bin/env kotlin
That will be helpful to test the script locally before running in CI, as the IDE will show a run button in the gutter, next to the shebang.
If you add the execute permission to the file (chmod +x YouScript.main.kts on Linux/macOS), you'll also be able to run it just like any other script, without having to type kotlinc -script before, and that will apply on GitHub Actions as well.
Finally, here's an example manual GitHub Action (aka. workflow file) that will take an input and pass it to your Kotlin script (usable in the args property/parameter) after it installed Kotlin:
name: Run Kotlin script
on:
workflow_dispatch:
inputs:
awesome-input:
description: 'Awesome parameter'
default: 'You'
required: true
jobs:
awesome-action:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
- name: Run Kotlin script
run: kotlinc -script ./YourScript.main.kts ${{ github.event.inputs.awesome-input }}
Note that if the script has the execute (x) permission, as I told previously, you can remove the kotlinc -script part and it will still run.
Bonus: it is possible to have Kotlin code directly in the workflow file (though I'd not recommend doing it), by using kotlin as a shell.
See this YouTrack comment to see how: https://youtrack.jetbrains.com/issue/KT-43534#focus=Comments-27-4640716.0-0

Kotlin runner is now pre-installed on GitHub Actions environments (GitHub issue, YouTube video).
Refer to the GitHub Actions runner images1 to see all the installed software.
So, you can easily run your .main.kts scripts like this:
name: Example
on:
push:
branches:
- main
jobs:
example-action:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v3
- name: Run the script
run: kotlin /path/in/repo/to/my-script.main.kts
And here is an example my-script.main.kts file:
#file:JvmName("MyScript")
#file:CompilerOptions("-jvm-target", "11")
#file:Repository("https://repo.maven.apache.org/maven2")
// #file:DependsOn("com.example:library:1.2.3")
import java.io.File
val input = File("README.md") // Assuming you ran checkout before
val output = File("result.txt")
val readmeFirstLine = input.readLines().first()
output.writeText(readmeFirstLine)
There is also a GitHub action called setup-kotlin that lets you install your desired version of Kotlin and also provides some more features. Check out this issue.
...
- uses: actions/checkout#v3
- uses: fwilhe2/setup-kotlin#main
with:
version: 1.7.0
- name: Run the script
run: kotlin /path/in/repo/to/my-script.main.kts
It was previously called virtual environments

Related

Gitlab CI sequence of instructions causing circular dependency

I have a CICD configuration that looks something like this:
.rule_template: &rule_configuration
rules:
- changes:
- file/dev/script1.txt
variables:
DESTINATION_HOST: somehost1
RUNNER_TAG: somerunner1
- changes:
- file/test/script1.txt
variables:
DESTINATION_HOST: somehost2
RUNNER_TAG: somerunner2
default:
tags:
- scripts
stages:
- lint
deploy scripts 1/6:
<<: *rule_configuration
tags:
- $RUNNER_TAG
stage: lint
script: |
echo "Add linting here!"
....
In short, which runner to choose depends on which file was changed, hence the runner tag has to be conditionally decided. However, these jobs never execute and the value of never gets assigned as I always get:
This job is stuck because you don't have any active runners online or available with any of these tags assigned to them: $RUNNER_TAG
I believe it is because the rules blocks isn't executed and hence the $RUNNER_TAG variable not resolved to its actual value at the point when job/workflow is being initialized and runner being searched.
If my doubt is correct, then probably it's a circular dependency that job initialization requires $RUNNER_TAG but the resolution of $RUNNER_TAG requires job initialization.
If the above is correct, what is the right way to handle it and what stage can I conditionally decide and assign $RUNNER_TAG its value so it doesn’t hinder job/workflow initialization?
gitlab-runner --version
Version: 14.7.0
Git revision: 98daeee0
Git branch: 14-7-stable
GO version: go1.17.5
Built: 2022-01-19T17:11:48+0000
OS/Arch: linux/amd64
I think what you are doing is over complicating what you need to do.
Instead of trying to abstract the tag and dynamically create some variable, simply make each job responsible for registering itself within a pipeline run based on if a particular file path changed.
It might feel like code duplication but it actually keeps your CI a lot simpler and easier to understand.
Job1:
Run when file changes
Tag : some tag
Job2:
Run when some other file changes
Tag: sometag2
Job 3:
Run when a third different file changes
Tag: sometag3

Serverless.yml - Epilogue

One magical day I found a reference to an 'epilogue' key to be used in the Serverless.yml file. It's the best. We use it to cleanup after testing that occurs inside our CI/CD pipeline.
- name: Test Integration
dependencies:
- Deploy Dev
task:
jobs:
- name: Test endpoints
commands:
- cache restore
- checkout
- sem-version python 3.8
- cd integration_tests
- pip install -r requirements.txt
- // our various testing scripts...
epilogue:
always: // This runs, no matter what. There are other options!!
commands:
- python3 99_cleanup.py
secrets:
- name: secret_things_go_here
Today, I don't want epilogue: always: , but rather epilogue: when it doesn't fail: . I cannot find one shred of documentation about this option. Nothing to even explain how I got here in the first place.
Oh, internet: How do I run something only when my tests have passed?
WOO!
I was barking up the wrong tree. The solution is within SemaphoreCI, not Serverless.
https://docs.semaphoreci.com/reference/pipeline-yaml-reference/#the-epilogue-property
Options include: on_pass and on_fail.
Whew.

What Gitlab tool used for code coverage reports?

Instead of using JaCoCo, I was told, that there would be an internal Gitlab tool, where I can create test coverage reports?
I do not want to use JaCoCo.
I am not interessted in any vizualization plugin within Gitlab.
I would like to generate a xml/html file(s) with e.g. bar graphs, what can be emailed and opened externally.
I couldn't find anything in the Gitlab dashboard menu. The project is a Android App Kotlin project.
the question is what part of Coverage you want to see/have:
just a number within the MR - therefore GitLab parses the logoutput of the Jobs
coverage visualization within MR - therefore you need to provide a report.
Coverage in Overview
For the coverage in the Overview and just to get a percentage, you need to configure your job with an regex how it can be parsed like
job1:
# ....
coverage: '/Code coverage: \d+\.\d+/'
https://docs.gitlab.com/ee/ci/yaml/#coverage
Visualization
We are actually using JaCoCo, but to make the coverage visible and to have the information in Merge Requests you have to convert everything into Cobertura Reports.
There are different approaches to achieve this:
with a gradle-plugin like https://github.com/kageiit/gradle-jacobo-plugin
the configuration is pretty neat, and if you do have already a gradle build it is easy to integrate
with an own step within the CI Pipeline - see https://docs.gitlab.com/ee/user/project/merge_requests/test_coverage_visualization.html
test-jdk11:
stage: test
image: gradle:6.6.1-jdk11
script:
- 'gradle test jacocoTestReport' # jacoco must be configured to create an xml report
artifacts:
paths:
- build/jacoco/jacoco.xml
coverage-jdk11:
# Must be in a stage later than test-jdk11's stage.
# The `visualize` stage does not exist by default.
# Please define it first, or chose an existing stage like `deploy`.
stage: visualize
image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.7
script:
# convert report from jacoco to cobertura, using relative project path
- python /opt/cover2cover.py build/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > build/cobertura.xml
needs: ["test-jdk11"]
artifacts:
reports:
cobertura: build/cobertura.xml
important to note is that you always will have to tell GitLab CI your path to the artifact for cobertura with
job:
#...
artifacts:
reports:
cobertura: build/cobertura.xml
Our approach is the following.
have to tell Gitlab where your coverage report is, for example we have this setup for a java unit test report "jacoco.xml":
Unit Test:
stage: pruebas
script:
- echo "Iniciar Pruebas"
- mvn $MAVEN_CLI_OPTS test
artifacts:
when: always
reports:
junit:
- target/surefire-reports/*Test.xml
- target/failsafe-reports/*Test.xml
cobertura: target/site/jacoco/jacoco.xml
Our summary in Gitlab :
Unit Test Detaills:
The key is your "jacoco.xml".

Passing variables between Tekton Steps

I am trying to implement a basic Tekton CI pipeline. All the pipeline does is 1) get the source code 2) build an image with a new version and push it to an image registry.
The image version is generated by a Tekton Step. Images are built by another Tekton step that uses Kaniko as described here.
I am aware of using workspaces to pass variables between Tekton steps. Meaning I can write the version to a file in the workspace. But cant figure out a way to read this version from the file in the Kaniko build step below:
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:latest
# specifying DOCKER_CONFIG is required to allow kaniko to detect docker credential
env:
- name: "DOCKER_CONFIG"
value: "/tekton/home/.docker/"
command:
- /kaniko/executor
args:
- --dockerfile=$(params.pathToDockerFile)
- --destination=$(resources.outputs.builtImage.url):<IMAGE-VERSION-NEEDED-HERE>
- --context=$(params.pathToContext)
- --build-arg=BASE=alpine:3
There should be a common pattern to resolve this but I am not sure if I am looking at the right places in Tekton documentation for this.
Can anyone offer some pointers?
This is to confirm that I managed to resolve the issue by redesigning the steps to tasks as suggested by #Jonas.
Tekton Tasks can have outputs which can be referred in other tasks. At the time of writing this Tekton steps don't seem to have this feature.
For more details refer the links in #Jonas comments above.
All steps in a Task share the same Pod and thus as access to a shared workspace implemented as an emptyDir volume:
Volumes:
tekton-internal-workspace:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
A common way to share data between steps is to a file in the /workspace and read it in the next step.
Alternatively, as suggested by #Jonas, if you use different Tasks you can write a result in the first Task and feed it into a parameter of the second Task in the Pipeline definition.
Using results this way implicitly creates a dependency between the two Tasks, so the Tekton controller won't schedule the second Task until the first one has successfully completed and results are available.
You can use the gcr.io/kaniko-project/executor:debug image that has shell at /busybox/sh.
And create something like this (pass kaniko commands via script):
steps:
- name: write-to-workspace
image: ubuntu
script: |
#!/usr/bin/env bash
echo "IMAGE_VERSION" > /workspace/FOO
- name: read-from-workspace
image: gcr.io/kaniko-project/executor:debug
script: |
#!/busybox/sh
export IMAGE_VERSION=$(cat /workspace/FOO)
echo "$IMAGE_VERSION"
/kaniko/executor \
--dockerfile=$(params.pathToDockerFile) \
--destination=$(resources.outputs.builtImage.url):"${IMAGE_VERSION}" \
--context=$(params.pathToContext) \
--build-arg=BASE=alpine:3
You can refer to this discussion: https://github.com/tektoncd/pipeline/issues/1476

Flutter Integration Tests with Travis CI

1. The Summary of the Problem
I would like a Travis CI setup that would let me run flutter driver tests inside an Android and an iOS environments. For this to happen, I expect I somehow have to install Flutter, Android and iOS in different environments.
2. What I Have So Far
Most of the posts I've been able to find on this topic are terribly outdated or feature incredibly complicated setups. Some of those that keep on appearing in my searches are:
Test Flutter apps on Travis, by Yegor Jbanov. This one covers unit and widget testing (flutter test), but not integration tests.
It's from early 2017 and Travis CI has maybe simplified its API, because I've managed to make it work with only this:
language: dart
dart:
- stable
dart_task:
- dartfmt
install:
- git clone https://github.com/flutter/flutter.git -b stable
script:
- ./flutter/bin/flutter doctor
- ./flutter/bin/flutter test
One resource that I've found very useful is the .travis.yml in the Flutter samples repo. The setup there seems very complicated to me though.
The closest I could get to what I wanted is similar to Maurice McCabe's Flutter unit, widget and integration testing with IOS and Android emulators on Travis-CI.
Again, this seems overcomplicated and outdated.
3. The Sketch of What I Have in Mind
The script and install steps in the example I mentioned previously could be replaced by jobs with stages. In this way, each stage would represent one sort of step. Unit and Widget stages in one, integration tests on Android and iOS in two others, which is similar to what Maurice McCabe and Flutter samples show. For example:
jobs:
include:
- stage: Flutter Test
language: dart
os: linux
install: git clone $FLUTTER_GITHUB -b stable
before_script:
- ./flutter/bin/flutter doctor
script:
- ./flutter/bin/flutter test
- stage: Integration Test on Android
os: linux
dist: trusty
language: android
android: # the things here are what probably needs to be fixed
components:
- build-tools-28.0.3
- android-28
install: git clone $FLUTTER_GITHUB -b stable
before_script:
- ./flutter/bin/flutter doctor
script:
- ./flutter/bin/flutter drive --target=test_driver/app.dart
If I could create a stage for the dartfmt task that would also be nice in terms of organization.
1. The Overview
Kudos to #MirceaMatei and Maurice McCabe for helping out in this.
I haven't yet been able to make Android Integration Tests work (the last stage of the code below), but at least iOS is working.
Android is much harder to get right because of different versions and licenses, something Apple does much better.
The code you will find below is part of a custom mono repo setup I'm working on.
The folder structure consists of an app and a packages folders.
I haven't yet segmented code coverage by those two folders either, so this setup is currently overwriting code coverage from one folder to another.
Using a top level install section makes Travis repeat that setup for every stage, so that avoids having to do that repeatedly for every stage.
I'm sharing my current — albeit incomplete — setup below, but continuous improvement will happen through this Github Gist, which is a much better way of interacting with the community to improve code than StackOverflow — it's not its purpose.
All in all, I think Travis CI is really dropping the ball when it comes to Flutter — and maybe Android in general. An easier alternative that has been hyped by many practitioners I've come into contact recently is Codemagic, which also offers code signing and automated deployment to the iOS and Android app stores.
2. Useful Resources
I didn't really like Travis documentation for the integration tests. People creating pure Android apps filed similar issues.
Anyway, here are some useful resources I've found during my searches:
Issue Comment on "Error: Target id is not valid. Use 'android list targets' to get the target ids."
“Invalid --abi armeabi-v7a for the selected target” with Google APIs
Is there a way to start android emulator in Travis CI build?
Travis-CI Android 28 licenses have not been accepted
Flutter Samples Travis Setup
#MirceaMatei's Travis Setup Recommendation
Travis Building Android Projects Documentation
Maurice McCabe's Unit and Integration Tests with Travis Article on Medium
3. The Code
Please, do not post code improvement suggestions here, but in the the Github Gist. I'll be updating the yaml code below myself every time a useful iteration arrives.
language: dart
env:
global:
- DARTSDK=./flutter/bin/cache/dart-sdk/bin
- DARTFMT=$DARTSDK/dartfmt
- FLUTTER=./flutter/bin/flutter
- FLUTTER_UP=../flutter/bin/flutter
- FLUTTER_GITHUB=https://github.com/flutter/flutter.git
- CODECOV=https://codecov.io/bash
- PACKAGES=packages
- APP=app
- ANDROID_API=28
install:
- git clone $FLUTTER_GITHUB -b stable --depth 1
- $FLUTTER doctor
- $FLUTTER pub get
jobs:
include:
- stage: Formatting
script:
- $DARTFMT -n $PACKAGES --set-exit-if-changed
- stage: Packages Flutter Test
script:
- $FLUTTER test --coverage $PACKAGES
after_success:
- bash <(curl -s $CODECOV)
- stage: App Flutter Test
script:
- cd $APP
- $FLUTTER_UP test --coverage
after_success:
- bash <(curl -s $CODECOV)
- stage: iOS Integration Tests
os: osx
osx_image: xcode11
before_script:
- open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app
- export HOMEBREW_NO_AUTO_UPDATE=1
- brew install libimobiledevice
- brew install ideviceinstaller
- brew install ios-deploy
- brew install cocoapods || echo 'ignore exit(1)'
- brew link --overwrite cocoapods
script:
- cd $APP
- $FLUTTER_UP driver --target=test_driver/app.dart77
- stage: Android Integration Tests
language: android
dist: trusty
android:
components:
- tools
- platform-tools
- build-tools-25.0.3
- android-24
- android-22
- extra-google-google_play_services
- extra-google-m2repository
- extra-android-m2repository
- sys-img-armeabi-v7a-android-22
licenses:
- 'android-sdk-preview-license-52d11cd2'
- 'android-sdk-license-.+'
- 'google-gdk-license-.+'
before_install:
- yes | sdkmanager "platforms;android-28"
- echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a -c 32M
- emulator -avd test -no-audio -no-window &
- android-wait-for-emulator
- adb devices
- adb shell input keyevent 82 &
script:
- $FLUTTER --version
- $FLUTTER doctor
- cd $APP
- $FLUTTER_UP devices
- $FLUTTER_UP driver --target=test_driver/app.dart
You can take a look at this project
Sqlite Scaffolding Generator # Dart Framework ORM M8
I think it has all the components you are looking for. It's a Dart package that has, in the solution, an example integration project for Flutter.
The CI pipeline is built for Travis CI (based mostly on Maurice McCabe's article). It's possible that the travis.yml file has all the sections you need.