Skip to content

Adds run-external ability for amalgam/hse #1

Adds run-external ability for amalgam/hse

Adds run-external ability for amalgam/hse #1

Workflow file for this run

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