|
| 1 | +# Copyright (c) godot-rust; Bromeon and contributors. |
| 2 | +# This Source Code Form is subject to the terms of the Mozilla Public |
| 3 | +# License, v. 2.0. If a copy of the MPL was not distributed with this |
| 4 | +# file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 5 | + |
| 6 | +name: godot |
| 7 | +description: "Run Godot integration tests" |
| 8 | + |
| 9 | +inputs: |
| 10 | + artifact-name: |
| 11 | + required: true |
| 12 | + description: "Name of the compiled Godot artifact to download" |
| 13 | + |
| 14 | + godot-binary: |
| 15 | + required: true |
| 16 | + description: "Filename of the Godot executable" |
| 17 | + |
| 18 | + godot-args: |
| 19 | + required: false |
| 20 | + default: '' |
| 21 | + description: "Command-line arguments passed to Godot" |
| 22 | + |
| 23 | + # Currently unused; could be removed entirely. |
| 24 | + godot-check-header: |
| 25 | + required: false |
| 26 | + default: 'false' |
| 27 | + description: "Should the job check against latest gdextension_interface.h, and warn on difference" |
| 28 | + |
| 29 | + godot-prebuilt-patch: |
| 30 | + required: false |
| 31 | + default: '' |
| 32 | + description: "If specified, sets the branch name of the godot4-prebuilt crate to this value" |
| 33 | + |
| 34 | + rust-toolchain: |
| 35 | + required: false |
| 36 | + default: 'stable' |
| 37 | + description: "Rust toolchain specifier (e.g. 'nightly')" |
| 38 | + |
| 39 | + rust-extra-args: |
| 40 | + required: false |
| 41 | + default: '' |
| 42 | + description: "Extra command line arguments for 'cargo build', e.g. features" |
| 43 | + |
| 44 | + rust-env-rustflags: |
| 45 | + required: false |
| 46 | + default: '' |
| 47 | + description: "Extra values for the RUSTFLAGS env var" |
| 48 | + |
| 49 | + rust-target: |
| 50 | + required: false |
| 51 | + default: '' |
| 52 | + description: "If provided, acts as an argument for '--target', and results in output files written to ./target/{target}" |
| 53 | + |
| 54 | + rust-cache-key: |
| 55 | + required: false |
| 56 | + default: '' |
| 57 | + description: "Extra key to resolve Rust cache" |
| 58 | + |
| 59 | + with-llvm: |
| 60 | + required: false |
| 61 | + default: 'false' |
| 62 | + description: "Set to 'true' if LLVM should be installed" |
| 63 | + |
| 64 | + |
| 65 | +runs: |
| 66 | + using: "composite" |
| 67 | + steps: |
| 68 | + # Do not check out here, as this would overwrite (clean) the current directory and is already done by the parent workflow. |
| 69 | + |
| 70 | + - name: "Install Godot" |
| 71 | + uses: ./.github/composite/godot-install |
| 72 | + with: |
| 73 | + artifact-name: ${{ inputs.artifact-name }} |
| 74 | + godot-binary: ${{ inputs.godot-binary }} |
| 75 | + |
| 76 | + # The chmod seems still necessary, although applied before uploading artifact. Possibly modes are not preserved. |
| 77 | + # The `| xargs` pattern trims the output, since printed version may contain extra newline, which causes problems in env vars. |
| 78 | + - name: "Inspect Godot version" |
| 79 | + run: | |
| 80 | + godotVer=$($GODOT4_BIN --version | xargs) |
| 81 | + gitSha=$(echo $godotVer | sed -E "s/.+custom_build\.//") |
| 82 | + echo "GODOT_BUILT_FROM=_Built from [\`$godotVer\`](https://github.com/godotengine/godot/commit/$gitSha)._" >> $GITHUB_ENV |
| 83 | + shell: bash |
| 84 | + |
| 85 | + - name: "Install Rust" |
| 86 | + uses: ./.github/composite/rust |
| 87 | + with: |
| 88 | + rust: ${{ inputs.rust-toolchain }} |
| 89 | + with-llvm: ${{ inputs.with-llvm }} |
| 90 | + cache-key: ${{ inputs.rust-cache-key }} |
| 91 | + |
| 92 | + - name: "Patch prebuilt version ({{ inputs.godot-prebuilt-patch }})" |
| 93 | + if: inputs.godot-prebuilt-patch != '' |
| 94 | + env: |
| 95 | + PATCHED_VERSION: ${{ inputs.godot-prebuilt-patch }} |
| 96 | + # sed -i'' needed for macOS compatibility, see https://stackoverflow.com/q/4247068 |
| 97 | + run: | |
| 98 | + # Specify `api-*` feature for godot crate if needed. |
| 99 | + .github/other/patch-prebuilt.sh "$PATCHED_VERSION" |
| 100 | + |
| 101 | + # Reduce versions to "major.minor" format. |
| 102 | + apiVersion=$(echo "$PATCHED_VERSION" | sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+/\1/') |
| 103 | + apiDefaultVersion=$(echo "$defaultVersion" | sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+/\1/') |
| 104 | + |
| 105 | + # For newer versions, update 'compatibility_minimum' in .gdextension files to the respective version. |
| 106 | + # Nothing needs to be done for 4.0.x, as compatibility_minimum didn't exist back then (we do it due to easier code, anyway). |
| 107 | + if [[ "$apiVersion" == "$apiDefaultVersion" ]]; then |
| 108 | + echo "Already has version $version; no need to change compatibility_minimum." |
| 109 | + |
| 110 | + else |
| 111 | + echo "Update compatibility_minimum in .gdextension files to '$apiVersion'..." |
| 112 | + dirs=("itest" "examples") |
| 113 | + |
| 114 | + # Note that this is still hardcoded to 4.1, the start of GDExtension's compatibility promise. This makes it easier for users |
| 115 | + # to use gdext with older Godot versions. There is anyway a runtime check in gdext that checks compatibility again. |
| 116 | + for dir in "${dirs[@]}"; do |
| 117 | + find "$dir" -type f -name "*.gdextension" -exec sed -i'.bak' "s/compatibility_minimum = 4\.1/compatibility_minimum = $apiVersion/" {} + |
| 118 | + done |
| 119 | + |
| 120 | + echo "Example output: itest/godot/itest.gdextension" |
| 121 | + echo "----------------------------------------------------" |
| 122 | + cat itest/godot/itest.gdextension |
| 123 | + echo "----------------------------------------------------" |
| 124 | + fi |
| 125 | +
|
| 126 | + shell: bash |
| 127 | + |
| 128 | + # else |
| 129 | + - name: "No patch selected" |
| 130 | + if: inputs.godot-prebuilt-patch == '' |
| 131 | + run: | |
| 132 | + echo "No patch selected; use default godot4-prebuilt version." |
| 133 | + shell: bash |
| 134 | + |
| 135 | + - name: "Build gdext (itest)" |
| 136 | + env: |
| 137 | + RUSTFLAGS: ${{ inputs.rust-env-rustflags }} |
| 138 | + TARGET: ${{ inputs.rust-target }} |
| 139 | + run: | |
| 140 | + targetArgs="" |
| 141 | + if [[ -n "$TARGET" ]]; then |
| 142 | + targetArgs="--target $TARGET" |
| 143 | + fi |
| 144 | + |
| 145 | + cargo build -p itest --no-default-features ${{ inputs.rust-extra-args }} $targetArgs |
| 146 | + |
| 147 | + # Instead of modifying .gdextension, rename the output directory |
| 148 | + if [[ -n "$TARGET" ]]; then |
| 149 | + rm -rf target/debug |
| 150 | + mv target/$TARGET/debug target |
| 151 | + fi |
| 152 | + shell: bash |
| 153 | + |
| 154 | + - name: "Run Godot integration tests" |
| 155 | + # Aborts immediately if Godot outputs certain keywords (would otherwise stall until CI runner times out). |
| 156 | + # Explanation: |
| 157 | + # * tee: still output logs while scanning for errors |
| 158 | + # * grep -q: no output, use exit code 0 if found -> thus also && |
| 159 | + # * pkill: stop Godot execution (since it hangs in headless mode); simple 'head -1' did not work as expected |
| 160 | + # * exit: the terminated process would return 143, but this is more explicit and future-proof |
| 161 | + # |
| 162 | + # --disallow-focus: fail if #[itest(focus)] is encountered, to prevent running only a few tests for full CI |
| 163 | + run: | |
| 164 | + cd itest/godot |
| 165 | + echo "OUTCOME=itest" >> $GITHUB_ENV |
| 166 | + $GODOT4_BIN --headless -- --disallow-focus ${{ inputs.godot-args }} 2>&1 \ |
| 167 | + | tee "${{ runner.temp }}/log.txt" \ |
| 168 | + | tee >(grep -E "SCRIPT ERROR:|Can't open dynamic library" -q && { |
| 169 | + printf "\n::error::godot-itest: unrecoverable Godot error, abort...\n"; |
| 170 | + pkill godot |
| 171 | + echo "OUTCOME=godot-runtime" >> $GITHUB_ENV |
| 172 | + exit 2 |
| 173 | + }) |
| 174 | + |
| 175 | + echo "OUTCOME=success" >> $GITHUB_ENV |
| 176 | + shell: bash |
| 177 | + |
| 178 | + - name: "Check for memory leaks" |
| 179 | + run: | |
| 180 | + if grep -q "ObjectDB instances leaked at exit" "${{ runner.temp }}/log.txt"; then |
| 181 | + echo "OUTCOME=godot-leak" >> $GITHUB_ENV |
| 182 | + exit 3 |
| 183 | + fi |
| 184 | + shell: bash |
| 185 | + |
| 186 | + - name: "Conclusion" |
| 187 | + if: always() |
| 188 | + run: | |
| 189 | + echo "Evaluate conclusion: $OUTCOME" |
| 190 | + |
| 191 | + case $OUTCOME in |
| 192 | + "success") |
| 193 | + # Do not output success for now, to keep summary focused on warnings/errors |
| 194 | + #echo "### :heavy_check_mark: Godot integration tests passed" > $GITHUB_STEP_SUMMARY |
| 195 | + #echo "$GODOT_BUILT_FROM" >> $GITHUB_STEP_SUMMARY |
| 196 | + ;; |
| 197 | + |
| 198 | + "godot-runtime") |
| 199 | + echo "### :x: Godot runtime error" > $GITHUB_STEP_SUMMARY |
| 200 | + echo "$GODOT_BUILT_FROM" >> $GITHUB_STEP_SUMMARY |
| 201 | + echo "Aborted due to an error during Godot execution." >> $GITHUB_STEP_SUMMARY |
| 202 | + exit 2 |
| 203 | + ;; |
| 204 | + |
| 205 | + "godot-leak") |
| 206 | + echo "### :x: Memory leak" > $GITHUB_STEP_SUMMARY |
| 207 | + echo "$GODOT_BUILT_FROM" >> $GITHUB_STEP_SUMMARY |
| 208 | + echo "Integration tests cause memory leaks." >> $GITHUB_STEP_SUMMARY |
| 209 | + exit 3 |
| 210 | + ;; |
| 211 | + |
| 212 | + "itest") |
| 213 | + echo "### :x: Godot integration tests failed" > $GITHUB_STEP_SUMMARY |
| 214 | + echo "$GODOT_BUILT_FROM" >> $GITHUB_STEP_SUMMARY |
| 215 | + exit 4 |
| 216 | + ;; |
| 217 | + |
| 218 | + "header-diff") |
| 219 | + # already written |
| 220 | + ;; |
| 221 | +
|
| 222 | + *) |
| 223 | + echo "### :x: Unknown error occurred" > $GITHUB_STEP_SUMMARY |
| 224 | + echo "$GODOT_BUILT_FROM" >> $GITHUB_STEP_SUMMARY |
| 225 | + exit 5 |
| 226 | + ;; |
| 227 | + esac |
| 228 | + shell: bash |
0 commit comments