diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2113cd9..838aca5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,4 @@ name: MAVSDK-Java - on: push: branches: @@ -9,14 +8,40 @@ on: - '**' release: types: [created] - jobs: - main: + build: + if: github.event_name != 'release' runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: submodules: recursive + - name: Build and prepare mavsdk + working-directory: ./sdk + run: | + set -o pipefail + python3 -m venv venv + source ./venv/bin/activate + pip install protoc-gen-mavsdk + ./gradlew build + - name: Build and prepare mavsdk-server + working-directory: ./mavsdk_server + run: ./gradlew build + + release: + if: github.event_name == 'release' && github.event.action == 'created' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Validate version + id: version + run: | + set -o pipefail + tag_name="${{ github.ref_name }}" + python ./tools/version_validator.py "$tag_name" + echo "tag_name=$tag_name" >> $GITHUB_OUTPUT - name: Prepare tokens keystore run: | echo "${{ secrets.TOKENS_KEYSTORE }}" > /tmp/keystore.properties.b64 @@ -25,6 +50,30 @@ jobs: cp /tmp/keystore.properties mavsdk_server - name: Prepare GPG key run: echo "${{ secrets.SIGNING_PGP_KEY }}" | gpg --batch --import + - name: Extract version and sync proto submodule + run: | + set -o pipefail + tag_name="${{ steps.version.outputs.tag_name }}" + + # Extract version number + extracted_version=$(python ./tools/version_validator.py -e "$tag_name") + + # Clone MAVSDK repo with minimal depth to get proto submodule hash + git clone --depth 1 --branch "${extracted_version}" https://github.com/mavlink/mavsdk.git /tmp/mavsdk + + # Get the proto submodule commit hash from the MAVSDK repo + cd /tmp/mavsdk + proto_commit_hash=$(git ls-tree HEAD proto | awk '{print $3}') + + # Update our local proto submodule to match + cd $GITHUB_WORKSPACE/sdk/proto + git fetch origin + git checkout $proto_commit_hash + + # Cleanup + rm -rf /tmp/mavsdk + + echo "Updated proto submodule to commit: $proto_commit_hash for MAVSDK version: $extracted_version" - name: Build and prepare mavsdk working-directory: ./sdk run: | @@ -32,20 +81,18 @@ jobs: python3 -m venv venv source ./venv/bin/activate pip install protoc-gen-mavsdk - ./gradlew build - ./gradlew publish + ./gradlew build -PVERSION=${{ steps.version.outputs.tag_name }} + ./gradlew publish -PVERSION=${{ steps.version.outputs.tag_name }} - name: Build and prepare mavsdk-server working-directory: ./mavsdk_server run: | set -o pipefail - ./gradlew build - ./gradlew publish + ./gradlew build -PVERSION=${{ steps.version.outputs.tag_name }} + ./gradlew publish -PVERSION=${{ steps.version.outputs.tag_name }} - name: Deploy mavsdk - if: github.event_name == 'release' && github.event.action == 'created' working-directory: ./sdk - run: ./gradlew jreleaserDeploy + run: ./gradlew jreleaserDeploy -PVERSION=${{ steps.version.outputs.tag_name }} - name: Deploy mavsdk-server - if: github.event_name == 'release' && github.event.action == 'created' working-directory: ./mavsdk_server - run: ./gradlew jreleaserDeploy + run: ./gradlew jreleaserDeploy -PVERSION=${{ steps.version.outputs.tag_name }} diff --git a/mavsdk_server/build.gradle.kts b/mavsdk_server/build.gradle.kts index 70ff74e..1c25d86 100644 --- a/mavsdk_server/build.gradle.kts +++ b/mavsdk_server/build.gradle.kts @@ -22,7 +22,17 @@ try { } allprojects { - val mavsdk_server_release = "v3.6.0" + // We fetch the mavsdk_server binary that corresponds to the mavsdk-java + // version. Say we set this package to be 3.6.0-2-SNAPSHOT, it means that it + // corresponds to mavsdk_server 3.6.0. + val mavsdk_server_release = if (!project.hasProperty("VERSION")) { + "v3.6.0" + } else { + val versionString = project.property("VERSION").toString() + val regex = Regex("v?(\\d+\\.\\d+\\.\\d+)") + val version = regex.find(versionString)?.groupValues?.get(1) + "v$version" + } tasks { register("extractMavsdkServer") { @@ -87,8 +97,13 @@ android { minSdk = 21 group = "io.mavsdk" - version = "3.6.0" + // The version must be of the form "X.Y.Z-b[-SNAPSHOT]", where "X.Y.Z" + // is the MAVSDK-C++ version, "b" is the build number of this + // MAVSDK-Java package and "SNAPSHOT" optionally sets it as a SNAPSHOT. + version = + if (project.hasProperty("VERSION")) project.property("VERSION").toString() + else "3.6.0-SNAPSHOT" ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64") diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index 92e6c15..0a2dc3a 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -22,7 +22,15 @@ try { } group = "io.mavsdk" -version = "3.6.0" + +// The version must be of the form "X.Y.Z-b[-SNAPSHOT]", where "X.Y.Z" +// is the MAVSDK-C++ version, "b" is the build number of this +// MAVSDK-Java package and "SNAPSHOT" optionally sets it as a SNAPSHOT. +// For instance, if the version is 3.6.0-2, it should be built with the same +// version of the proto files as MAVSDK-C++ v3.6.0. +version = + if (project.hasProperty("VERSION")) project.property("VERSION").toString() + else "3.6.0-SNAPSHOT" val grpcVersion = "1.61.1" diff --git a/tools/version_validator.py b/tools/version_validator.py new file mode 100644 index 0000000..79ac27f --- /dev/null +++ b/tools/version_validator.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +""" +Version Number Validator + +This script validates version numbers and extracts major.minor.patch format. + +Version format rules: +- Can start with 'v' (optional): v2.3.1 or 2.3.1 +- Must have major.minor.patch: 1.2.3 (minimum required) +- May have build number: 1.2.3-1 or 1.2.3-2 +- May end with -SNAPSHOT: 3.4.5-1-SNAPSHOT or 3.4.5-SNAPSHOT + +Usage: + python version_validator.py + python version_validator.py --extract +""" + +import re +import sys +import argparse + +def extract_major_minor_patch(version_string): + """ + Extract major.minor.patch from a valid version string in vX.Y.Z format. + + Args: + version_string (str): The version string to extract from + + Returns: + str: The extracted version in vX.Y.Z format, or None if invalid + """ + # Define the regex pattern for valid version strings + pattern = r'^v?(\d+)\.(\d+)\.(\d+)(?:-(\d+))?(?:-SNAPSHOT)?$' + match = re.match(pattern, version_string) + + if match: + major, minor, patch = match.groups()[:3] + return f"v{major}.{minor}.{patch}" + + return None + +def validate_version(version_string): + """ + Validate a version string according to the specified rules. + + Args: + version_string (str): The version string to validate + + Returns: + bool: True if valid, False otherwise + """ + return extract_major_minor_patch(version_string) is not None + +def main(): + parser = argparse.ArgumentParser( + description="Validate version number and extract major.minor.patch format", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python version_validator.py v2.3.1 # Validate version + python version_validator.py 1.2.3-1 # Validate version with build + python version_validator.py --extract v2.3.1-SNAPSHOT # Extract v2.3.1 + python version_validator.py -e 3.4.5-2-SNAPSHOT # Extract v3.4.5 + +Valid version formats: + v1.2.3, 1.2.3, v1.2.3-1, 1.2.3-1, v1.2.3-SNAPSHOT, 1.2.3-1-SNAPSHOT + """ + ) + + parser.add_argument( + 'version', + help='Version string to validate/extract' + ) + parser.add_argument( + '-e', '--extract', + action='store_true', + help='Extract major.minor.patch in vX.Y.Z format' + ) + + args = parser.parse_args() + + if args.extract: + # Extract mode + result = extract_major_minor_patch(args.version) + if result: + print(result) + sys.exit(0) + else: + print(f"Error: '{args.version}' is not a valid version string", file=sys.stderr) + sys.exit(1) + else: + # Validation mode + if validate_version(args.version): + print(f"'{args.version}' is a valid version string") + sys.exit(0) + else: + print(f"Error: '{args.version}' is not a valid version string", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + # If no arguments provided, run some test cases + if len(sys.argv) == 1: + print("Running test cases...") + + test_cases = [ + # Valid cases + ("v2.3.1", True), + ("2.3.1", True), + ("1.2.3-1", True), + ("v1.2.3-1", True), + ("3.4.5-SNAPSHOT", True), + ("v3.4.5-SNAPSHOT", True), + ("1.2.3-1-SNAPSHOT", True), + ("v1.2.3-1-SNAPSHOT", True), + ("10.20.30", True), + ("v0.0.1", True), + + # Invalid cases + ("1.2", False), + ("v1.2", False), + ("1.2.3.4", False), + ("v1.2.3.4", False), + ("1.2.3-", False), + ("1.2.3-SNAPSHOT-1", False), + ("1.2.3-a", False), + ("v1.2.3-a", False), + ("av1.2.3", False), + ("a1.2.3", False), + (" v1.2.3", False), + ("V1.2.3-a", False), + ("1.2.x", False), + ("", False), + ("v", False), + ("1.2.3-1-SNAPSHOT-extra", False), + ] + + print("\nValidation Tests:") + for version, expected in test_cases: + result = validate_version(version) + status = "✓" if result == expected else "✗" + print(f"{status} {version:<25} -> {result} (expected {expected})") + + print("\nExtraction Tests:") + extraction_tests = [ + ("v2.3.1", "v2.3.1"), + ("2.3.1", "v2.3.1"), + ("1.2.3-1", "v1.2.3"), + ("v1.2.3-1-SNAPSHOT", "v1.2.3"), + ("3.4.5-SNAPSHOT", "v3.4.5"), + ("invalid", None), + ] + + for version, expected in extraction_tests: + result = extract_major_minor_patch(version) + status = "✓" if result == expected else "✗" + print(f"{status} {version:<25} -> {result} (expected {expected})") + else: + main()