Skip to content
Merged
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
28 changes: 28 additions & 0 deletions .github/workflows/firebase-emulators-exec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ on:
required: false
type: boolean
default: false
artifact_path:
description: |
File or directory path, relative to path, to upload after the command runs.
The artifact name is inferred from the final path component.
required: false
type: string
default: ''
secrets:
GOOGLE_APPLICATION_CREDENTIALS_BASE64:
description: |
Expand Down Expand Up @@ -136,6 +143,27 @@ jobs:
else
firebase emulators:exec -c "$FIREBASE_JSON_PATH" "$COMMAND"
fi
- name: Resolve artifact name
id: artifact
if: ${{ (success() || failure()) && inputs.artifact_path != '' }}
env:
ARTIFACT_PATH: ${{ inputs.artifact_path }}
run: |
normalized_path="${ARTIFACT_PATH%/}"
artifact_name="$(basename "$normalized_path")"

if [ -z "$artifact_name" ] || [ "$artifact_name" = "." ]; then
artifact_name="artifact"
fi

artifact_name="$(printf '%s' "$artifact_name" | tr '/\\:*?"<>|' '-')"
echo "name=$artifact_name" >> "$GITHUB_OUTPUT"
- name: Upload artifact
if: ${{ (success() || failure()) && inputs.artifact_path != '' }}
uses: actions/upload-artifact@v7
with:
name: ${{ steps.artifact.outputs.name }}
path: ${{ format('{0}/{1}', inputs.path, inputs.artifact_path) }}
- name: Clean up Google application credentials
if: always()
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/markdown-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
JSON-based collection of labels indicating which type of GitHub runner should be chosen.
required: false
type: string
default: '["macOS", "self-hosted"]'
default: '["ubuntu-latest"]'

permissions:
contents: read
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ on:
types: [created]
workflow_dispatch:

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

jobs:
releasetag:
name: Tag Action Release
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/swift-package-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ jobs:
destination: ${{ matrix.platform.destination }}
buildConfig: ${{ matrix.config }}
resultBundle: ${{ format('{0}-{1}-{2}.xcresult', inputs.package_name, matrix.platform.name, matrix.config) }}
artifactname: ${{ format('{0}-{1}-{2}.xcresult', inputs.package_name, matrix.platform.name, matrix.config) }}

ui_tests:
name: UI ${{ matrix.platform.name }} (${{ matrix.config }})
Expand All @@ -86,7 +85,6 @@ jobs:
destination: ${{ matrix.platform.destination }}
buildConfig: ${{ matrix.config }}
resultBundle: ${{ format('{0}-{1}-{2}.xcresult', matrix.platform.scheme, matrix.platform.name, matrix.config) }}
artifactname: ${{ format('{0}-{1}-{2}.xcresult', matrix.platform.scheme, matrix.platform.name, matrix.config) }}

package_tests_linux:
name: Linux (${{ matrix.config }})
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ on:
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
reuse_action:
name: Check REUSE Compliance
Expand All @@ -28,8 +32,6 @@ jobs:
markdown_link_check:
name: Check Markdown Links
uses: ./.github/workflows/markdown-links.yml
with:
runs_on_labels: '["ubuntu-latest"]'
yamllint:
name: Lint YAML
runs-on: ubuntu-latest
Expand Down
143 changes: 125 additions & 18 deletions .github/workflows/xcodebuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ on:
scheme:
description: |
The scheme in the Xcode project.
required: true
If omitted, the workflow infers the Swift package scheme from Package.swift or the only shared Xcode scheme.
required: false
type: string
default: ''
buildConfig:
description: |
The build configuration parameter that should be passed to xcodebuild.
Expand All @@ -59,7 +61,7 @@ on:
resultBundle:
description: |
The name of the Xcode result bundle that is passed to xcodebuild.
If not defined, the name of the scheme + .xcresult is used.
If not defined, Swift packages use the package name + .xcresult and Xcode projects use the resolved scheme + .xcresult.
required: false
type: string
default: ''
Expand All @@ -72,6 +74,7 @@ on:
test:
description: |
A flag indicating if the tests of the Xcode project scheme should run when using xcodebuild.
When CodeQL runs, the workflow builds without running tests regardless of this value.
required: false
type: boolean
default: true
Expand All @@ -89,6 +92,7 @@ on:
artifactname:
description: |
The name and path of the artifact that should be uploaded at the end of the build.
If not defined, test runs upload the resolved result bundle using its file name as the artifact name.
required: false
type: string
default: ''
Expand Down Expand Up @@ -157,14 +161,116 @@ jobs:
cp "$XCODE_PATH"/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/*.dylib "$XCODE_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib"
sudo mkdir -p /usr/local/lib
sudo cp "$XCODE_PATH"/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/*.dylib /usr/local/lib
- name: Resolve xcodebuild inputs
id: xcodebuild_inputs
env:
ARTIFACT_INPUT: ${{ inputs.artifactname }}
CODEQL_ENABLED: ${{ runner.environment == 'github-hosted' && inputs.codeql }}
RESULT_BUNDLE_INPUT: ${{ inputs.resultBundle }}
SCHEME_INPUT: ${{ inputs.scheme }}
TEST_ENABLED: ${{ inputs.test }}
run: |
package_name=""

if [ -f Package.swift ]; then
package_dump="$(swift package dump-package)"
package_name="$(echo "$package_dump" | jq -r '.name')"
fi

if [ -n "$SCHEME_INPUT" ]; then
scheme="$SCHEME_INPUT"
elif [ -n "$package_name" ]; then
library_products="$(echo "$package_dump" | jq -c '[.products[] | select(.type | tostring | test("library")) | .name]')"
library_product_count="$(echo "$library_products" | jq 'length')"
if [ "$library_product_count" -gt 1 ]; then
scheme="${package_name}-Package"
else
scheme="$package_name"
fi
else
workspaces=()
while IFS= read -r workspace; do
workspaces+=("$workspace")
done < <(find . -maxdepth 1 -name "*.xcworkspace" -type d | sort)

projects=()
while IFS= read -r project; do
projects+=("$project")
done < <(find . -maxdepth 1 -name "*.xcodeproj" -type d | sort)

if [ "${#workspaces[@]}" -eq 1 ]; then
container_args=(-workspace "${workspaces[0]#./}")
elif [ "${#workspaces[@]}" -gt 1 ]; then
echo "::error::Multiple Xcode workspaces found. Set the scheme input explicitly."
exit 1
elif [ "${#projects[@]}" -eq 1 ]; then
container_args=(-project "${projects[0]#./}")
elif [ "${#projects[@]}" -gt 1 ]; then
echo "::error::Multiple Xcode projects found. Set the scheme input explicitly."
exit 1
else
echo "::error::No Package.swift, .xcworkspace, or .xcodeproj found. Set path to the project directory."
exit 1
fi

schemes="$(xcodebuild -list -json "${container_args[@]}" | ruby -rjson -e '
data = JSON.parse(STDIN.read)
puts(data.dig("workspace", "schemes") || data.dig("project", "schemes") || [])
')"
scheme_count="$(printf '%s\n' "$schemes" | sed '/^$/d' | wc -l | tr -d ' ')"
if [ "$scheme_count" -ne 1 ]; then
echo "::error::Expected exactly one shared Xcode scheme, found $scheme_count. Set the scheme input explicitly."
printf '%s\n' "$schemes"
exit 1
fi

scheme="$schemes"
fi

if [ -z "$RESULT_BUNDLE_INPUT" ]; then
if [ -n "$package_name" ]; then
result_bundle="${package_name}.xcresult"
else
result_bundle="${scheme}.xcresult"
fi
else
result_bundle="$RESULT_BUNDLE_INPUT"
fi

if [ -n "$ARTIFACT_INPUT" ]; then
artifact_path="$ARTIFACT_INPUT"
elif [ "$CODEQL_ENABLED" != "true" ] && [ "$TEST_ENABLED" = "true" ]; then
artifact_path="$result_bundle"
else
artifact_path=""
fi

if [ -n "$artifact_path" ]; then
normalized_artifact_path="${artifact_path%/}"
artifact_name="$(basename "$normalized_artifact_path")"
if [ -z "$artifact_name" ] || [ "$artifact_name" = "." ]; then
artifact_name="artifact"
fi

artifact_name="$(printf '%s' "$artifact_name" | tr '/\\:*?"<>|' '-')"
else
artifact_name=""
fi

{
echo "scheme=$scheme"
echo "result_bundle=$result_bundle"
echo "artifact_path=$artifact_path"
echo "artifact_name=$artifact_name"
} >> "$GITHUB_OUTPUT"
- name: Check available simulators
env:
SCHEME: ${{ inputs.scheme }}
SCHEME: ${{ steps.xcodebuild_inputs.outputs.scheme }}
run: |
xcrun xcodebuild -scheme "$SCHEME" -showdestinations
- name: Resolve dependencies
env:
SCHEME: ${{ inputs.scheme }}
SCHEME: ${{ steps.xcodebuild_inputs.outputs.scheme }}
SPM_DISABLE_PREBUILTS: ${{ inputs.spm-disable-prebuilts }}
run: |
prebuilt_args=()
Expand All @@ -182,28 +288,29 @@ jobs:
- name: Build and test
env:
BUILD_CONFIG: ${{ inputs.buildConfig }}
CODEQL_ENABLED: ${{ runner.environment == 'github-hosted' && inputs.codeql }}
DESTINATION: ${{ inputs.destination }}
RESULT_BUNDLE_INPUT: ${{ inputs.resultBundle }}
SCHEME: ${{ inputs.scheme }}
RESULT_BUNDLE: ${{ steps.xcodebuild_inputs.outputs.result_bundle }}
SCHEME: ${{ steps.xcodebuild_inputs.outputs.scheme }}
SPM_DISABLE_PREBUILTS: ${{ inputs.spm-disable-prebuilts }}
SWIFT_VERSION: ${{ inputs.swiftVersion }}
TEST_ENABLED: ${{ inputs.test }}
TESTPLAN: ${{ inputs.testplan }}
run: |
if [ "$TEST_ENABLED" = "true" ]; then
if [ "$CODEQL_ENABLED" = "true" ]; then
effective_test_enabled=false
else
effective_test_enabled="$TEST_ENABLED"
fi

if [ "$effective_test_enabled" = "true" ]; then
xcode_command="test"
code_coverage_args=(-enableCodeCoverage YES)
else
xcode_command="build"
code_coverage_args=()
fi

if [ -z "$RESULT_BUNDLE_INPUT" ]; then
result_bundle="$SCHEME.xcresult"
else
result_bundle="$RESULT_BUNDLE_INPUT"
fi

if [ "$BUILD_CONFIG" = "Release" ]; then
enable_testing_flag="-enable-testing"
else
Expand All @@ -218,7 +325,7 @@ jobs:
other_swift_flags="$other_swift_flags -swift-version $SWIFT_VERSION"
fi

if [ -n "$TESTPLAN" ]; then
if [ "$effective_test_enabled" = "true" ] && [ -n "$TESTPLAN" ]; then
testplan_args=(-testPlan "$TESTPLAN")
else
testplan_args=()
Expand All @@ -238,7 +345,7 @@ jobs:
"${testplan_args[@]}" \
"${code_coverage_args[@]}" \
-derivedDataPath ".derivedData" \
-resultBundlePath "$result_bundle" \
-resultBundlePath "$RESULT_BUNDLE" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY="" \
OTHER_SWIFT_FLAGS="$other_swift_flags" \
Expand All @@ -250,8 +357,8 @@ jobs:
if: ${{ runner.environment == 'github-hosted' && inputs.codeql }}
uses: github/codeql-action/analyze@v4
- name: Upload artifact
if: ${{ (success() || failure()) && inputs.artifactname != '' }}
if: ${{ (success() || failure()) && steps.xcodebuild_inputs.outputs.artifact_path != '' }}
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.artifactname }}
path: ${{ inputs.path }}/${{ inputs.artifactname }}
name: ${{ steps.xcodebuild_inputs.outputs.artifact_name }}
path: ${{ inputs.path }}/${{ steps.xcodebuild_inputs.outputs.artifact_path }}
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ jobs:
markdown-links:
name: Check Markdown Links
uses: SchmiedmayerLab/.github/.github/workflows/markdown-links.yml@v0.2
with:
runs_on_labels: '["ubuntu-latest"]'
```

##### Run Periphery
Expand Down Expand Up @@ -305,24 +303,33 @@ jobs:
##### Build and Test with xcodebuild

Use [`xcodebuild.yml`](.github/workflows/xcodebuild.yml) for Apple projects that need direct xcodebuild tests or builds.
When `scheme` is omitted, the workflow infers the Swift package scheme from `Package.swift` or the only shared Xcode scheme in the selected `path`.
Swift packages use the package name as the scheme, or `PackageName-Package` when the package defines multiple library products.
Swift package result bundles use `PackageName.xcresult` in both cases.
Test runs upload the resolved `.xcresult` bundle automatically; set `resultBundle` only when you need a custom bundle name.
The workflow intentionally does not declare its own `permissions` block because its CodeQL path is optional.
Callers that set `codeql: true` must grant `security-events: write`; normal build and test jobs can omit that permission.

```yml
jobs:
build-and-test:
name: Build and Test Swift Package
app-tests:
name: Build and Test App
permissions:
contents: read
uses: SchmiedmayerLab/.github/.github/workflows/xcodebuild.yml@v0.2
with:
artifactname: TemplatePackage.xcresult
runsonlabels: '["macOS", "self-hosted"]'
scheme: TemplatePackage
package-tests:
name: Build and Test Swift Package
uses: SchmiedmayerLab/.github/.github/workflows/xcodebuild.yml@v0.2
with:
path: ExamplePackage
runsonlabels: '["macOS", "self-hosted"]'
```

CodeQL analysis uses the same workflow with `codeql: true`.
Because GitHub sets unspecified token scopes to `none` when any explicit permission is declared, grant both `contents: read` and `security-events: write` on the calling job.
When CodeQL runs on a GitHub-hosted runner, the workflow builds the project without running tests even though `test` defaults to `true`.

```yml
jobs:
Expand All @@ -341,6 +348,7 @@ jobs:

Use [`firebase-emulators-exec.yml`](.github/workflows/firebase-emulators-exec.yml) for test or validation commands that must run while Firebase emulators are active.
The `command` input is executed through `firebase emulators:exec` and can be any trusted shell command.
Use `artifact_path` to upload command output such as an `.xcresult` bundle; the artifact name is inferred from the path.

```yml
jobs:
Expand All @@ -349,6 +357,7 @@ jobs:
uses: SchmiedmayerLab/.github/.github/workflows/firebase-emulators-exec.yml@v0.2
with:
command: bundle exec fastlane uitest
artifact_path: fastlane/test_output/UITests.xcresult
firebase_emulator_import: ./firebase-export
secrets:
GOOGLE_APPLICATION_CREDENTIALS_BASE64: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_BASE64 }}
Expand Down
Loading