Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/build-wheel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Build Python Wheel

on:
workflow_call:
inputs:
ref:
description: Git ref to check out
required: false
type: string
default: ""

jobs:
build-wheel:
name: Build Python wheel
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Build and test
run: ./gradlew clean build buildPythonWheel

- name: Collect wheel
run: |
mkdir -p dist
find . -path "*/build/install/*/dist/*.whl" -exec cp {} dist/ \;

- name: Upload Python wheel artifact
uses: actions/upload-artifact@v4
with:
name: python-wheel
path: dist/*.whl
if-no-files-found: error
retention-days: 14
31 changes: 7 additions & 24 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,18 @@ on:
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
build-wheel:
name: Build and test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Build and test
run: ./gradlew clean build
uses: ./.github/workflows/build-wheel.yml

dependency-submission:
name: Submit Gradle dependencies
needs: build
needs: build-wheel
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release

on:
release:
types:
- published

permissions:
contents: write
actions: read

concurrency:
group: release-${{ github.event.release.id }}
cancel-in-progress: false

jobs:
build-wheel:
name: Build wheel from release tag
uses: ./.github/workflows/build-wheel.yml
with:
ref: ${{ github.event.release.tag_name }}

publish-wheel:
name: Publish Python wheel to GitHub Release
needs: build-wheel
runs-on: ubuntu-latest

steps:
- name: Download Python wheel artifact
uses: actions/download-artifact@v4
with:
name: python-wheel
path: dist

- name: Generate checksums
run: |
cd dist
sha256sum *.whl > SHA256SUMS.txt

- name: Publish wheel to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.release.tag_name }}
files: |
dist/*.whl
dist/SHA256SUMS.txt
65 changes: 58 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,65 @@ plugins {
id "org.sonarqube" version "7.3.1.8318"
}

def versionLabel(gitInfo) {
def branch = gitInfo.branchName // all branches are snapshots, only tags get released
def tag = gitInfo.lastTag
// tag is returned as is. Branch may need cleanup
return branch == null ? tag : "99." + branch.replace("/","-") + "-SNAPSHOT"
def gitOutput(String... args) {
return providers.exec {
commandLine(['git'] + args.toList())
ignoreExitValue = true
}.standardOutput.asText.get().trim()
}

def normalizeReleaseTag(String tag) {
if (!tag) {
return null
}

return tag.startsWith('v') ? tag.substring(1) : tag
}

def pep440VersionFromLatestTag() {
def exactTag = gitOutput('describe', '--tags', '--exact-match')
if (exactTag) {
return normalizeReleaseTag(exactTag)
}

def tag = gitOutput('describe', '--tags', '--abbrev=0')
if (!tag) {
tag = '0.0.0'
}

def baseVersion = normalizeReleaseTag(tag)

def distanceText = tag == '0.0.0'
? gitOutput('rev-list', 'HEAD', '--count')
: gitOutput('rev-list', "${tag}..HEAD", '--count')
def distance = distanceText?.isInteger() ? distanceText.toInteger() : 0

def hash = gitOutput('rev-parse', '--short=7', 'HEAD')
def dirty = gitOutput('status', '--porcelain') ? true : false

if (distance == 0 && !dirty) {
return baseVersion
}

def version = "${baseVersion}.post${distance}"
def localParts = []

if (hash) {
localParts.add("g${hash}")
}

if (dirty) {
localParts.add("dirty")
}

if (!localParts.isEmpty()) {
version += "+" + localParts.join(".")
}

return version
}

allprojects {
group = 'mil.army.wmist.regi-headless'
version = versionLabel(versionDetails())
group = 'mil.army.wmist.regi-python'
version = pep440VersionFromLatestTag()
}
122 changes: 120 additions & 2 deletions regi-headless/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import com.pswidersk.gradle.python.VenvTask

plugins {
id 'regi-headless.deps-conventions'
id 'regi-headless.java-conventions'
id 'com.pswidersk.python-plugin' version '3.2.16'
}

dependencies {
Expand Down Expand Up @@ -45,6 +48,121 @@ jar {
}
}

test {
useJUnitPlatform()
pythonPlugin {
pythonVersion = "3.11.15"
condaVersion = "26.3.2-2"
condaInstaller = "Miniforge3"
installDir = file(layout.buildDirectory.dir("python"))
}

tasks.register('installPythonBuildTools', VenvTask) {
group = 'build setup'
description = 'Installs Python packages needed to build and test the wheel.'

venvExec = 'pip'
args = ['install', '--upgrade', 'pip', 'build', 'pytest']

outputs.file(layout.buildDirectory.file("python-build-tools/install.marker"))

doLast {
def markerFile = layout.buildDirectory.file("python-build-tools/install.marker").get().asFile
markerFile.parentFile.mkdirs()
markerFile.text = "pip build pytest installed\n"
}
}

tasks.register('bundlePython', Sync) {
description = 'Bundles Python scripts and creates the java_lib directory'

dependsOn jar

into layout.buildDirectory.dir("install/regi_python")

inputs.dir("src/main/python")
inputs.file(jar.archiveFile)
inputs.files(configurations.runtimeClasspath)
inputs.property("version", project.version.toString())

from('src/main/python') {
include 'pyproject.toml'
filter { line -> line.replaceAll('@VERSION@', project.version.toString()) }
}

from('src/main/python') {
exclude 'pyproject.toml'
}

into('regi_python/lib') {
from configurations.runtimeClasspath
from jar.archiveFile
exclude "**/*.nbm"
}
}

tasks.register('buildPythonWheel', VenvTask) {
group = "distribution"
description = "Builds a Python .whl file using the Gradle-managed Python environment."

dependsOn bundlePython
dependsOn installPythonBuildTools

workingDir = layout.buildDirectory.dir("install/regi_python").get().asFile
venvExec = "python"
args = ["-m", "build", "--wheel"]

inputs.files(fileTree(layout.buildDirectory.dir("install/regi_python")) {
exclude "dist/**"
exclude "build/**"
exclude "*.egg-info/**"
})
inputs.property("version", project.version.toString())
outputs.dir(layout.buildDirectory.dir("install/regi_python/dist"))

doFirst {
delete layout.buildDirectory.dir("install/regi_python/dist")
}

doLast {
println "Python Wheel built in: ${workingDir}/dist"
}
}

tasks.register('installPythonWheelForSmokeTest', VenvTask) {
group = 'verification'
description = 'Installs the built Python wheel into the Gradle-managed Python environment.'

dependsOn buildPythonWheel

venvExec = 'pip'

inputs.files(fileTree(dir: "${buildDir}/install/regi_python/dist", include: '*.whl'))
outputs.file(layout.buildDirectory.file("test-python-wheel/install.marker"))

doFirst {
def wheelFile = fileTree(dir: "${buildDir}/install/regi_python/dist", include: '*.whl').singleFile
args = ['install', '--force-reinstall', wheelFile.absolutePath]
}

doLast {
def wheelFile = fileTree(dir: "${buildDir}/install/regi_python/dist", include: '*.whl').singleFile
def markerFile = layout.buildDirectory.file("test-python-wheel/install.marker").get().asFile
markerFile.parentFile.mkdirs()
markerFile.text = "${wheelFile.name}\n${wheelFile.length()} bytes\n"
}
}
tasks.register('testPythonWheel', VenvTask) {
group = 'verification'
description = 'Runs pytest against the installed Python wheel.'

dependsOn installPythonWheelForSmokeTest

venvExec = 'python'
args = ['-m', 'pytest', 'src/test/python']

inputs.files(fileTree(dir: 'src/test/python', include: '**/*.py'))
outputs.upToDateWhen { false }
}

check {
dependsOn testPythonWheel
}
Loading
Loading