Skip to content

Commit

Permalink
Merge pull request #8 from multiversx/reproducible-builds-24
Browse files Browse the repository at this point in the history
Add reusable workflow for reproducible builds
  • Loading branch information
andreibancioiu authored Jan 27, 2023
2 parents 9c19d1a + a112475 commit 4eea9ad
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 670 deletions.
170 changes: 170 additions & 0 deletions .github/workflows/reproducible-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
name: Build contracts deterministically

on:
workflow_call:
inputs:
image_tag:
type: string
required: true
description: Image multiversx/sdk-rust-contract-builder
contract_name:
type: string
required: false
description: A specific contract to be built
create_release:
type: boolean
description: Whether to create a new release
attach_to_existing_release:
type: boolean
description: Whether to attach output to existing release

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Handle input
run: |
python3 - << "EOF"
import logging
import os
import sys
logging.basicConfig(level=logging.INFO)
image_tag = os.getenv("image_tag", "")
contract_name = os.getenv("contract_name", "")
create_release = os.getenv("create_release", "").lower() == "true"
attach_to_existing_release = os.getenv("attach_to_existing_release", "").lower() == "true"
logging.info(f"Image tag: {image_tag}")
logging.info(f"Contract name: {contract_name or '*'}")
logging.info(f"Create new release: {create_release}")
logging.info(f"Attach output to existing release: {attach_to_existing_release}")
if image_tag == "next":
logging.warning("You've chosen to build the contracts with the image 'multiversx/sdk-rust-contract-builder:next', which is experimental and not recommended for production!")
if contract_name:
logging.info(f"One contract will be built: {contract_name}.")
if create_release and attach_to_existing_release:
sys.exit("ERROR: Choose either to create a new release, or to attach the build output to an existing release. Can't pick both. Feel free to pick none, though.")
EOF
env:
image_tag: ${{ inputs.image_tag }}
contract_name: ${{ inputs.contract_name }}
create_release: ${{ inputs.create_release }}
attach_to_existing_release: ${{ inputs.attach_to_existing_release }}

- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ env.GITHUB_REF_NAME }}
fetch-depth: 0
repository: ${{ env.GITHUB_REPOSITORY }}

- name: Preliminary checks
run: |
python3 - << "EOF"
import logging
from pathlib import Path
import sys
logging.basicConfig(level=logging.INFO)
logging.info("Check for missing 'wasm/Cargo.lock' files ...")
missing_cargo_lock = False
for cargo_toml in Path(".").rglob("wasm/Cargo.toml"):
cargo_lock = cargo_toml.with_suffix(".lock")
if cargo_lock.exists():
logging.info(f"Found wasm/Cargo.lock: {cargo_lock}")
else:
logging.error(f"wasm/Cargo.lock file not found: {cargo_lock}")
missing_cargo_lock = True
if missing_cargo_lock:
sys.exit(f"ERROR: One or more 'wasm/Cargo.lock' files are missing. They are essential for reproducible builds.")
EOF
- name: Download build script
run: |
wget https://raw.githubusercontent.com/multiversx/mx-sdk-rust-contract-builder/${{ inputs.image_tag }}/build_with_docker.py
- name: Build contracts
run: |
python3 ./build_with_docker.py --no-docker-tty --image=multiversx/sdk-rust-contract-builder:${{ inputs.image_tag }} --project=. --contract=${{ inputs.contract_name }} --output=/home/runner/work/output-from-docker
- name: Save artifacts
uses: actions/upload-artifact@v3
with:
name: build-output
path: |
/home/runner/work/output-from-docker/**/*.*
if-no-files-found: error

- name: Create new release (if applicable)
if: ${{ inputs.create_release == true }}
run: |
gh release create ${{ github.ref_name }} --target=$GITHUB_SHA --prerelease --title="New release: ${{ github.ref_name }}" --generate-notes
# Give some time for processing the request
sleep 10
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
image_tag: ${{ inputs.image_tag }}

- name: Create release notes
if: ${{ inputs.attach_to_existing_release == true || inputs.create_release == true }}
run: |
python3 - << "EOF"
import logging
from hashlib import blake2b
from pathlib import Path
import os
import json
import urllib.request
logging.basicConfig(level=logging.INFO)
repository = os.getenv("GITHUB_REPOSITORY")
ref_name = os.getenv("GITHUB_REF_NAME")
image_tag = os.getenv("image_tag", "")
try:
logging.info(f"Fetching existing release notes, if any, for repository = {repository}, tag = {ref_name} ...")
request = urllib.request.urlopen(f"https://api.github.com/repos/{repository}/releases/tags/{ref_name}")
data = json.loads(request.read())
existing_notes = data["body"]
except Exception as err:
logging.info("An error ocurred while fetching existing release notes (perhaps there is no release yet).")
existing_notes = ""
notes = existing_notes + "\n"
notes += "Built using Docker image: **multiversx/sdk-rust-contract-builder:${{ inputs.image_tag }}**.\n"
notes += "\n"
notes+= "## Codehashes (blake2b):\n"
for wasm_file in Path("/home/runner/work/output-from-docker").rglob("*.wasm"):
code = wasm_file.read_bytes()
h = blake2b(digest_size=32)
h.update(code)
notes += f"**{wasm_file.name}**: `{h.hexdigest()}`"
Path("release_notes.txt").write_text(notes)
EOF
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
image_tag: ${{ inputs.image_tag }}

- name: Upload artifacts to release, edit release notes
if: ${{ inputs.attach_to_existing_release == true || inputs.create_release == true }}
run: |
gh release edit ${{ github.ref_name }} --notes-file=release_notes.txt
gh release upload ${{ github.ref_name }} $(find /home/runner/work/output-from-docker/**/*.wasm -type f)
gh release upload ${{ github.ref_name }} $(find /home/runner/work/output-from-docker/**/*.source.json -type f)
gh release upload ${{ github.ref_name }} $(find /home/runner/work/output-from-docker/artifacts.json -type f)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading

0 comments on commit 4eea9ad

Please sign in to comment.