Adds run-external ability for amalgam/hse #1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Reusable WF - Set Build Metadata | |
on: | |
workflow_call: | |
inputs: | |
build-type: | |
required: false | |
type: string | |
optional-release-tag: | |
required: false | |
type: string | |
amalgam-build: | |
required: false | |
type: string | |
howso-engine-build: | |
required: false | |
type: string | |
amalgam-lang-py-build: | |
required: false | |
type: string | |
howso-engine-py-build: | |
required: false | |
type: string | |
howso-synthesizer-py-build: | |
required: false | |
type: string | |
howso-validator-py-build: | |
required: false | |
type: string | |
howso-validator-enterprise-py-build: | |
required: false | |
type: string | |
allow-version-prefix: | |
required: false | |
type: boolean | |
description: Whether to search for previous release tags that include the "version-" prefix | |
default: false | |
skip-version-check: | |
required: false | |
type: boolean | |
description: Whether to skip creating a new (pre)release version | |
default: false | |
checkout-repos: | |
description: Whether to checkout each repo when getting dependency details (e.g., for use with the Git client) | |
type: boolean | |
required: false | |
default: false | |
build-and-embed: | |
description: If a build for Amalgam and/or Engine are provided, automatically build a corresponding amalgam-lang-py and howso-engine-py to be used in the client workflow. | |
type: boolean | |
required: false | |
default: false | |
outputs: | |
upstream-details: | |
value: ${{ jobs.set-metadata.outputs.upstream-details }} | |
version: | |
value: ${{ jobs.set-version.outputs.version }} | |
jobs: | |
validate-event-details: | |
runs-on: ubuntu-latest | |
env: | |
GH_TOKEN: ${{ github.token }} | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Validate PR Title | |
if: inputs.build-type == 'PR' | |
run: | | |
if [[ "${{ github.head_ref }}" =~ "rerun-recipes" || "${{ github.head_ref }}" =~ "gen-requirements" || "${{ github.head_ref }}" =~ "update-devcontainers" ]]; then | |
echo "PR title checks disabled on automated recipe or requirements re-run" | |
exit 0 | |
fi | |
PR_NUMBER="${{ github.event.number }}" | |
PR_TITLE=$(gh pr view $PR_NUMBER --json 'title' | jq -r '.title' ) | |
echo "Found PR title: $PR_TITLE" | |
TITLE_REGEX='^[0-9]+: .+$' | |
if ! [[ $PR_TITLE =~ $TITLE_REGEX ]]; then | |
echo "❌ - PR title does not comply with required format:" | |
echo "<ticket_num>: <title>" | |
exit 1 | |
fi | |
echo "✔ - PR title is formatted correctly" | |
construct-payload: | |
needs: ['validate-event-details'] | |
runs-on: ubuntu-latest | |
outputs: | |
payload: ${{ steps.construct-payload.outputs.payload }} | |
steps: | |
- name: Construct payload | |
id: construct-payload | |
run: | | |
echo "Constructing JSON payload from raw inputs" | |
# Map inputs to repo names in a JSON object | |
builds_json=$(cat <<EOF | |
{ | |
"amalgam": "${{ inputs.amalgam-build }}", | |
"howso-engine": "${{ inputs.howso-engine-build }}", | |
"amalgam-lang-py": "${{ inputs.amalgam-lang-py-build }}", | |
"howso-engine-py": "${{ inputs.howso-engine-py-build }}", | |
"howso-synthesizer-py": "${{ inputs.howso-synthesizer-py-build }}", | |
"howso-validator-py": "${{ inputs.howso-validator-py-build }}", | |
"howso-validator-enterprise-py": "${{ inputs.howso-validator-enterprise-py-build }}" | |
} | |
EOF | |
) | |
# Initialize an empty final payload | |
payload="{}" | |
# If the provided build is a URL, extract the run ID. Else, if the build exists, add it to the payload JSON. | |
while read -r repo; do | |
build=$(echo "$builds_json" | jq --raw-output --arg repo "$repo" '.[$repo]') | |
echo "Discovered build $build for repo $repo" | |
if [[ "$build" =~ runs/([0-9]+) ]]; then | |
echo "Adding build ID ${BASH_REMATCH[1]} to $repo" | |
payload=$(echo "$payload" | jq --arg run_id "${BASH_REMATCH[1]}" --arg repo "$repo" '. + {($repo): ($run_id)}') | |
elif [[ -n $build && $build != "\"\"" ]]; then | |
echo "Adding build ID $build to $repo" | |
payload=$(printf "%s" "$payload" | jq --arg build "$build" --arg repo "$repo" '. + {($repo): ($build)}') | |
fi | |
done < <(echo "$builds_json" | jq -rc 'keys[]') | |
fmtd_payload=$(echo "$payload" | jq -c) | |
echo "Payload: $fmtd_payload" | |
echo "payload=$fmtd_payload" >> $GITHUB_OUTPUT | |
get-howso-synthesizer-py-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "howso-synthesizer-py" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
skip-version-json-check: true | |
checkout-repo: ${{ inputs.checkout-repos }} | |
get-howso-validator-enterprise-py-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "howso-validator-enterprise-py" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
skip-version-json-check: true | |
checkout-repo: ${{ inputs.checkout-repos }} | |
get-howso-validator-py-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "howso-validator-py" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
skip-version-json-check: true | |
checkout-repo: ${{ inputs.checkout-repos }} | |
get-howso-engine-py-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "howso-engine-py" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
skip-version-json-check: true | |
checkout-repo: ${{ inputs.checkout-repos }} | |
get-amalgam-lang-py-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "amalgam-lang-py" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
skip-version-json-check: true | |
checkout-repo: ${{ inputs.checkout-repos }} | |
get-howso-engine-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "howso-engine" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
checkout-repo: ${{ inputs.checkout-repos }} | |
get-amalgam-details: | |
needs: ['construct-payload'] | |
uses: "./.github/workflows/get-dependency-details.yml" | |
secrets: inherit | |
with: | |
owner: "howsoai" | |
repo: "amalgam" | |
payload: "${{ needs.construct-payload.outputs.payload }}" | |
checkout-repo: ${{ inputs.checkout-repos }} | |
set-metadata: | |
needs: | |
- get-howso-engine-details | |
- get-amalgam-details | |
- get-amalgam-lang-py-details | |
- get-howso-engine-py-details | |
- get-howso-validator-py-details | |
- get-howso-validator-enterprise-py-details | |
- get-howso-synthesizer-py-details | |
runs-on: ubuntu-latest | |
outputs: | |
upstream-details: ${{ steps.output-upstream-details.outputs.final-upstream-details }} | |
steps: | |
- name: Set Metadata Details | |
id: set-metadata-details | |
run: | | |
# Construct a JSON object with all detail outputs, and then remove the empty ones | |
details=$(cat <<EOF | |
{ | |
"amalgam": { | |
"run_id": "${{ needs.get-amalgam-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-amalgam-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-amalgam-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-amalgam-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-amalgam-details.outputs.head-sha }}", | |
"url": "${{ needs.get-amalgam-details.outputs.url }}" | |
}, | |
"howso-engine": { | |
"run_id": "${{ needs.get-howso-engine-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-howso-engine-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-howso-engine-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-howso-engine-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-howso-engine-details.outputs.head-sha }}", | |
"url": "${{ needs.get-howso-engine-details.outputs.url }}" | |
}, | |
"amalgam-lang-py": { | |
"run_id": "${{ needs.get-amalgam-lang-py-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-amalgam-lang-py-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-amalgam-lang-py-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-amalgam-lang-py-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-amalgam-lang-py-details.outputs.head-sha }}", | |
"url": "${{ needs.get-amalgam-lang-py-details.outputs.url }}" | |
}, | |
"howso-engine-py": { | |
"run_id": "${{ needs.get-howso-engine-py-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-howso-engine-py-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-howso-engine-py-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-howso-engine-py-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-howso-engine-py-details.outputs.head-sha }}", | |
"url": "${{ needs.get-howso-engine-py-details.outputs.url }}" | |
}, | |
"howso-synthesizer-py": { | |
"run_id": "${{ needs.get-howso-synthesizer-py-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-howso-synthesizer-py-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-howso-synthesizer-py-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-howso-synthesizer-py-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-howso-synthesizer-py-details.outputs.head-sha }}", | |
"url": "${{ needs.get-howso-synthesizer-py-details.outputs.url }}" | |
}, | |
"howso-validator-py": { | |
"run_id": "${{ needs.get-howso-validator-py-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-howso-validator-py-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-howso-validator-py-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-howso-validator-py-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-howso-validator-py-details.outputs.head-sha }}", | |
"url": "${{ needs.get-howso-validator-py-details.outputs.url }}" | |
}, | |
"howso-validator-enterprise-py": { | |
"run_id": "${{ needs.get-howso-validator-enterprise-py-details.outputs.run-id }}", | |
"run_type": "${{ needs.get-howso-validator-enterprise-py-details.outputs.run-type }}", | |
"build_date": "${{ needs.get-howso-validator-enterprise-py-details.outputs.build-date }}", | |
"build_title": "${{ needs.get-howso-validator-enterprise-py-details.outputs.build-title }}", | |
"head_sha": "${{ needs.get-howso-validator-enterprise-py-details.outputs.head-sha }}", | |
"url": "${{ needs.get-howso-validator-enterprise-py-details.outputs.url }}" | |
} | |
} | |
EOF | |
) | |
filtered_details="{}" | |
# Iterate through the full details JSON, and add those with run IDs present to the filtered JSON | |
while read -r repo; do | |
run_id=$(echo "$details" | jq --arg repo "$repo" '.[$repo].run_id') | |
echo "Checking $repo" | |
echo "Discovered run_id $run_id for repo $repo" | |
all_details=$(echo "$details" | jq --arg repo "$repo" '.[$repo]') | |
# Skip the repo if the run ID isn't present, or if an amalgam or howso-engine build are set and will be overwritten later anyways | |
if [[ -n "$run_id" && "$run_id" != "\"\"" ]] && ! { [[ "$repo" == "amalgam-lang-py" && -n "${{ inputs.amalgam-build }}" ]] || [[ "$repo" == "howso-engine-py" && -n "${{ inputs.howso-engine-build }}" ]]; }; then | |
filtered_details=$(echo "$filtered_details" | jq --arg repo "$repo" --argjson all_details "$all_details" '. + { ($repo): $all_details }') | |
else | |
echo "Skipping (empty run_id)" | |
fi | |
done < <(echo "$details" | jq -rc 'keys[]') | |
echo "Filtered upstream details: $filtered_details" | |
echo "upstream-details=$(echo "$filtered_details" | jq -c)" >> $GITHUB_OUTPUT | |
- name: Config external run | |
id: config-external | |
if: inputs.build-and-embed | |
run: | | |
while read -r repo; do | |
echo "Getting details for $repo" | |
# Remove carriage return character (Windows issue) | |
repo=$(echo $repo | tr -d '\r') | |
run_type=$(printf "%s" '${{ steps.set-metadata-details.outputs.upstream-details }}' | jq -r --arg repo "$repo" '.[$repo]."run_type"') | |
run_id=$(printf "%s" '${{ steps.set-metadata-details.outputs.upstream-details }}' | jq -r --arg repo "$repo" '.[$repo]."run_id"') | |
if [[ "$repo" == "amalgam" ]]; then | |
echo "amalgam-run-id=$run_id" >> $GITHUB_OUTPUT | |
elif [[ "$repo" == "howso-engine" ]]; then | |
echo "howso-engine-run-id=$run_id" >> $GITHUB_OUTPUT | |
fi | |
done < <(printf "%s" '${{ steps.set-metadata-details.outputs.upstream-details }}' | jq -rc 'keys[]') | |
- name: Build custom amalgam-lang-py | |
id: build-amalgam-lang-py | |
if: inputs.build-and-embed && inputs.amalgam-build != '' | |
uses: "howsoai/.github/.github/workflows/run-external.yml@main" | |
with: | |
repo: amalgam-lang-py | |
workflow-name: build.yml | |
payload: '{"amalgam-build": "${{ steps.config-external.outputs.amalgam-run-id }}"}' | |
- name: Build custom howso-engine-py | |
id: build-howso-engine-py | |
if: inputs.build-and-embed && inputs.howso-engine-build != '' | |
uses: "howsoai/.github/.github/workflows/run-external.yml@main" | |
with: | |
repo: howso-engine-py | |
workflow-name: build.yml | |
payload: '{"amalgam-build": "${{ steps.config-external.outputs.amalgam-run-id }}", "howso-engine-build": "${{ steps.config-external.outputs.howso-engine-run-id }}"}' | |
- name: Output upstream-details | |
id: output-upstream-details | |
run: | | |
final_details=$(printf "%s" '${{ steps.set-metadata-details.outputs.upstream-details }}') | |
if [[ -n "${{ steps.build-amalgam-lang-py.outputs.run-id }}" ]]; then | |
final_details=$(printf "%s" '$final_details' | jq --arg run_id "${{ steps.build-amalgam-lang-py.outputs.run-id }}" --arg repo "amalgam-lang-py" '. + {($repo): ($run_id)}') | |
elif [[ -n "${{ steps.build-howso-engine-py.outputs.run-id }}" ]]; then | |
final_details=$(printf "%s" '$final_details' | jq --arg run_id "${{ steps.build-howso-engine-py.outputs.run-id }}" --arg repo "howso-engine-py" '. + {($repo): ($run_id)}') | |
fi | |
echo "Final upstream details: $final_details" | |
echo "final-upstream-details=$(echo "$final_details" | jq -c)" >> $GITHUB_OUTPUT | |
set-version: | |
needs: ['set-metadata'] | |
if: inputs.skip-version-check == false | |
runs-on: ubuntu-latest | |
outputs: | |
version: ${{ steps.choose-version.outputs.version }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: Get previous git tag | |
id: previous-tag | |
run: | | |
if [[ ${{ inputs.allow-version-prefix }} == true ]]; then | |
prefixed_tag=$(git for-each-ref --sort=-creatordate --count 5 --format="%(refname:short)" "refs/tags/" | grep -E "^[a-z]*-*[0-9]{1,3}\.[0-9]+\.[0-9]+" | head -n 1) | |
tag=${prefixed_tag/#version-} | |
else | |
tag=$(git for-each-ref --sort=-creatordate --count 10 --format="%(refname:short)" "refs/tags/" | grep -E "^[0-9]{1,3}\.[0-9]+\.[0-9]+" | head -n 1) | |
fi | |
echo "Found tag: $tag" | |
echo "tag=$(echo $tag)" >> $GITHUB_OUTPUT | |
- name: Get next semver from previous tag | |
id: next-semvers | |
uses: WyriHaximus/github-action-next-semvers@v1 | |
with: | |
version: ${{ steps.previous-tag.outputs.tag }} | |
- name: Set custom release version | |
if: inputs.build-type == 'release-custom' | |
id: set-custom-release-version | |
run: | | |
echo "version=$(echo ${{ steps.next-semvers.outputs.patch }}-alpha+PR.${{ github.run_attempt }}.${{ github.run_number }})" >> $GITHUB_OUTPUT | |
- name: Set PR version | |
if: inputs.build-type == 'PR' || inputs.build-type == 'release-custom' | |
id: set-pr-version | |
run: | | |
PR_NUMBER=${{ github.event.pull_request.number }} | |
PR_ITERATION=${{ github.run_attempt }}.${{ github.run_number }} | |
echo "version=$(echo ${{ steps.next-semvers.outputs.patch }}-alpha+PR.${PR_NUMBER}.${PR_ITERATION})" >> $GITHUB_OUTPUT | |
- name: Set Branch version | |
if: inputs.build-type == '' | |
id: set-branch-version | |
run: | | |
BRANCH_ITERATION=${{ github.run_attempt }}.${{ github.run_number }} | |
echo "version=$(echo ${{ steps.next-semvers.outputs.patch }}-alpha+BR.${{ github.ref_name }}.${BRANCH_ITERATION})" >> $GITHUB_OUTPUT | |
- name: Format version string | |
if: steps.set-pr-version.outputs.version != '' || steps.set-branch-version.outputs.version != '' | |
id: format-version | |
run: | | |
if [[ -n "${{ steps.set-pr-version.outputs.version }}" ]]; then | |
echo "Found PR version..." | |
semver=${{ steps.set-pr-version.outputs.version }} | |
else | |
echo "Found branch version..." | |
semver=${{ steps.set-branch-version.outputs.version }} | |
fi | |
# If this is not a Python repo, do nothing | |
if ! [[ -e "pyproject.toml" || -e "setup.py" ]]; then | |
echo "Current repository is not a Python project, nothing to do." | |
echo "version=$semver" >> $GITHUB_OUTPUT | |
exit 0 | |
fi | |
# Per PEP 440, the full scope of traditional semantic versioning is not valid in Python. | |
int_only_br=$(echo "${{ github.ref_name }}" | sed 's/[^0-9]*//g') | |
metadata=".dev$int_only_br" | |
pepified_version=$(echo "$semver" | sed -r -e 's/-alpha/a0/g' -e 's/-beta/b0/g' -e 's/\+PR./\.dev/g' -e "s|\+BR.${{ github.ref_name }}.|$metadata|g" -e 's/([0-9]+\.[0-9]+\.[0-9]+)([a-b0-9]+)?([\.dev]+)?(([0-9]+)\.([0-9]+)(\.([0-9]+))?)?/\1\2\3\5\6\8/g') | |
echo "Tagged version converted to PEP 440 standard: $pepified_version" | |
echo "version=$pepified_version" >> $GITHUB_OUTPUT | |
- name: Construct release tag | |
if: inputs.build-type == 'release' | |
id: set-release-version | |
run: | | |
RELEASE_TAG="" | |
# If tag not given by user, deduce from commit titles since last tag: | |
if test -z "${{ inputs.optional-release-tag }}"; then | |
echo "Autoincrementing version based on previous commit titles since last tag" | |
# Search previous commits for MAJOR/MINOR text tokens: | |
IS_MAJOR_BUMP=false | |
IS_MINOR_BUMP=false | |
echo "Checking commit titles since last tag: '${{ steps.previous-tag.outputs.tag }}'..." | |
if [[ ${{ inputs.allow-version-prefix }} == true ]]; then | |
COMMITS=$(git log --pretty=format:%s version-${{ steps.previous-tag.outputs.tag }}..@) | |
else | |
COMMITS=$(git log --pretty=format:%s ${{ steps.previous-tag.outputs.tag }}..@) | |
fi | |
while read commit | |
do | |
if [[ $commit == *"MAJOR"* ]]; then | |
echo -e "\tMajor: title='$commit'" | |
IS_MAJOR_BUMP=true | |
elif [[ $commit == *"MINOR"* ]]; then | |
echo -e "\tMinor: title='$commit'" | |
IS_MINOR_BUMP=true | |
else | |
echo -e "\tPatch: title='$commit'" | |
fi | |
done <<< "$COMMITS" | |
# Set version according to what was found in commit titles: | |
if [ "$IS_MAJOR_BUMP" = true ]; then | |
echo "Bumping major version" | |
RELEASE_TAG=${{ steps.next-semvers.outputs.major }} | |
elif [ "$IS_MINOR_BUMP" = true ]; then | |
echo "Bumping minor version" | |
RELEASE_TAG=${{ steps.next-semvers.outputs.minor }} | |
else | |
# If no major/minor found, treat as patch: | |
echo "Bumping patch version" | |
RELEASE_TAG=${{ steps.next-semvers.outputs.patch }} | |
fi | |
else | |
echo "Using user provided release tag" | |
RELEASE_TAG=${{ inputs.optional-release-tag }} | |
fi | |
# Check if valid semver: | |
regex='^([0-9]+\.){2}(\*|[0-9]+)(-.*)?$' | |
if [[ ! "$RELEASE_TAG" =~ $regex ]]; then | |
echo "❌ - Release tag is not a valid semver: $RELEASE_TAG" | |
exit 1 | |
fi | |
echo "✔ - Release tag is a valid semver" | |
# Check if tag already exists: | |
if git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then | |
echo "❌ - Release tag already exists: $RELEASE_TAG" | |
exit 1 | |
fi | |
echo "✔ - Release tag does not exist" | |
echo "Release tag: $RELEASE_TAG" | |
echo "version=$(echo $RELEASE_TAG)" >> $GITHUB_OUTPUT | |
- name: Choose Version | |
id: choose-version | |
run: | | |
if [[ -n "${{ steps.format-version.outputs.version }}" ]]; then | |
echo "Setting PR/branch version" | |
echo "version=${{ steps.format-version.outputs.version }}" >> $GITHUB_OUTPUT | |
elif [[ -n "${{ steps.set-release-version.outputs.version }}" ]]; then | |
echo "Setting release version" | |
echo "version=${{ steps.set-release-version.outputs.version }}" >> $GITHUB_OUTPUT | |
elif [[ -n "${{ steps.set-custom-release-version.outputs.version }}" ]]; then | |
echo "Setting custom release version" | |
echo "version=${{ steps.set-custom-release-version.outputs.version }}" >> $GITHUB_OUTPUT | |
else | |
echo "Version could not be determined" | |
echo "version=0.0.0" >> $GITHUB_OUTPUT | |
fi |