Skip to content

Commit

Permalink
chore: updating binary distribution to support homebrew (#432)
Browse files Browse the repository at this point in the history
* chore: codesign for Mac

* chore: fix tap GitHub repository

* chore: add metadata to the brew tar file

* chore: add metadat file before sign

* chore: testing ci

* chore: testing ci

---------

Co-authored-by: Negar Abbasi <[email protected]>
Co-authored-by: Altynbek Orumbayev <[email protected]>
  • Loading branch information
3 people authored Sep 19, 2024
1 parent bfc8744 commit 7162a62
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 55 deletions.
48 changes: 45 additions & 3 deletions .github/actions/build-binaries/macos/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,66 @@ inputs:
required: true
version:
description: "The version to use for this artifact"
apple_team_id:
description: "The Apple Team ID"
required: true
apple_bundle_id:
description: "The bundle ID to be used for packaging and notarisation"
required: true
apple_cert_id:
description: "The Apple Developer ID certificate ID"
required: true
apple_notary_user:
description: "The Apple user to notarise the package"
require: true
apple_notary_password:
description: "The Apple password to notarise the package"
require: true

runs:
using: "composite"
steps:
- name: Build binary
shell: bash
run: |
poetry run poe package_unix
export APPLE_CERT_ID="${{ inputs.apple_cert_id }}"
export APPLE_BUNDLE_ID="${{ inputs.apple_bundle_id }}"
poetry run poe package_mac
env:
APPLE_CERT_ID: ${{ inputs.apple_cert_id }}
APPLE_BUNDLE_ID: ${{ inputs.apple_bundle_id }}

- name: Add metadata to binary
shell: bash
run: |
echo brew > ${{ github.workspace }}/dist/algokit/_internal/algokit/resources/distribution-method
# Workaround an issue with PyInstaller where Python.framework was incorrectly signed during the build
- name: Codesign python.framework
shell: bash
run: |
codesign --force --sign "${{ inputs.apple_cert_id }}" --timestamp "${{ github.workspace }}/dist/algokit/_internal/Python.framework"
- name: Notarize
uses: lando/notarize-action@v2
with:
appstore-connect-team-id: ${{ inputs.apple_team_id }}
appstore-connect-username: ${{ inputs.apple_notary_user }}
appstore-connect-password: ${{ inputs.apple_notary_password }}
primary-bundle-id: ${{ inputs.apple_bundle_id }}
product-path: "${{ github.workspace }}/dist/algokit"
tool: notarytool
verbose: true

- name: Package binary artifact
shell: bash
run: |
cd dist/algokit/
tar -zcf ${{ inputs.artifacts_dir }}/${{ inputs.package_name }}.tar.gz *
tar -zcf ${{ inputs.artifacts_dir }}/${{ inputs.package_name }}-brew.tar.gz *
cd ../..
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.package_name }}
path: ${{ inputs.artifacts_dir }}/${{ inputs.package_name }}.tar.gz
path: ${{ inputs.artifacts_dir }}/${{ inputs.package_name }}-brew.tar.gz
40 changes: 40 additions & 0 deletions .github/actions/install-apple-dev-id-cert/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: "Install Apple Developer ID certificate"
description: "Install Apple Developer ID certificate to macos-build keychain"
inputs:
cert_data:
description: "Base64 string represents the Apple developer ID certificate"
required: true
cert_password:
description: "The password to unlock the Apple developer ID certificate"
required: true

runs:
using: "composite"
steps:
- name: Install cert
shell: bash
env:
APPLE_CERT_DATA: ${{ inputs.cert_data }}
APPLE_CERT_PASSWORD: ${{ inputs.cert_password }}
run: |
# Export certs
echo "$APPLE_CERT_DATA" | base64 --decode > /tmp/certs.p12
# Create keychain
security create-keychain -p actions macos-build.keychain
security default-keychain -s macos-build.keychain
security unlock-keychain -p actions macos-build.keychain
security set-keychain-settings -t 3600 -u macos-build.keychain
echo "Keychain created"
# Import certs to keychain
security import /tmp/certs.p12 -k ~/Library/Keychains/macos-build.keychain -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign
echo "Cert imported"
# Key signing
security set-key-partition-list -S apple-tool:,apple: -s -k actions macos-build.keychain
echo "Key signed"
# Delete temp file
rm /tmp/certs.p12
echo "Done"
12 changes: 12 additions & 0 deletions .github/workflows/build-binaries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,25 @@ jobs:
version: ${{ inputs.release_version }}
artifacts_dir: ${{ env.ARTIFACTS_DIR }}

- name: Install Apple Developer Id Cert
if: runner.os == 'macOS'
uses: ./.github/actions/install-apple-dev-id-cert
with:
cert_data: ${{ secrets.APPLE_CERT_DATA }}
cert_password: ${{ secrets.APPLE_CERT_PASSWORD }}

- name: Build macOS binary
if: ${{ runner.os == 'macOS' }}
uses: ./.github/actions/build-binaries/macos
with:
package_name: ${{ env.PACKAGE_NAME }}
version: ${{ inputs.release_version }}
artifacts_dir: ${{ env.ARTIFACTS_DIR }}
apple_team_id: ${{ secrets.APPLE_TEAM_ID }}
apple_bundle_id: ${{ inputs.production_release == 'true' && vars.APPLE_BUNDLE_ID || format('beta.{0}', vars.APPLE_BUNDLE_ID) }}
apple_cert_id: ${{ secrets.APPLE_CERT_ID }}
apple_notary_user: ${{ secrets.APPLE_NOTARY_USER }}
apple_notary_password: ${{ secrets.APPLE_NOTARY_PASSWORD }}

- name: Add binary to path
run: |
Expand Down
26 changes: 21 additions & 5 deletions .github/workflows/publish-release-packages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Publish packages to public repositories
on:
workflow_call:
inputs:
artifactName:
wheelArtifactName:
required: true
type: string
description: "The github artifact holding the wheel file which will be published"
Expand Down Expand Up @@ -67,24 +67,40 @@ jobs:
uses: actions/checkout@v4

# Download either via release or provided artifact
- name: Download release
- name: Download wheel from release
if: ${{ github.event_name == 'workflow_dispatch' }}
run: gh release download v${{ inputs.release_version }} --pattern "*.whl" --dir dist
env:
GH_TOKEN: ${{ github.token }}

- name: Download artifact
- name: Download wheel from artifact
if: ${{ github.event_name == 'workflow_call' }}
uses: actions/download-artifact@v4
with:
name: ${{ inputs.artifactName }}
name: ${{ inputs.wheelArtifactName }}
path: dist

- name: Download macOS binary from release
if: ${{ github.event_name == 'workflow_dispatch' }}
run: gh release download ${{ inputs.release }} --pattern "*-brew.tar.gz" --dir dist
env:
GH_TOKEN: ${{ github.token }}

- name: Download macOS binary from artifact
uses: actions/download-artifact@v4
if: ${{ github.event_name == 'workflow_call' }}
with:
name: ${{ inputs.binaryArtifactName }}
path: dist

- name: Set Git user as GitHub actions
run: git config --global user.email "[email protected]" && git config --global user.name "github-actions"

- name: ls dist folder
run: ls -la dist

- name: Update homebrew cask
run: scripts/update-brew-cask.sh "dist/algokit*-py3-none-any.whl" "algorandfoundation/homebrew-tap"
run: scripts/update-brew-cask.sh "dist/algokit*-py3-none-any.whl" "dist/algokit*-macos_arm64-brew.tar.gz" "dist/algokit*-macos_x64-brew.tar.gz" "algorandfoundation/homebrew-tap"
env:
TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}

Expand Down
10 changes: 10 additions & 0 deletions entitlements.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ docs_title = {shell = "(echo \"# AlgoKit CLI Reference Documentation\\n\\n\"; ca
docs = ["docs_generate", "docs_toc", "docs_title"]
package_unix = "pyinstaller --clean --onedir --hidden-import jinja2_ansible_filters --hidden-import multiformats_config --copy-metadata algokit --name algokit --noconfirm src/algokit/__main__.py --add-data './misc/multiformats_config:multiformats_config/' --add-data './src/algokit/resources:algokit/resources/'"
package_windows = { cmd = "scripts/package_windows.bat" }
package_mac = "pyinstaller --clean --onedir --hidden-import jinja2_ansible_filters --hidden-import multiformats_config --copy-metadata algokit --name algokit --noconfirm src/algokit/__main__.py --add-data './misc/multiformats_config/multibase-table.json:multiformats_config/' --add-data './misc/multiformats_config/multicodec-table.json:multiformats_config/' --add-data './src/algokit/resources:algokit/resources/' --osx-bundle-identifier \"$APPLE_BUNDLE_ID\" --codesign-identity \"$APPLE_CERT_ID\" --osx-entitlements-file './entitlements.xml'"

[tool.ruff]
line-length = 120
lint.select = [
Expand Down
102 changes: 55 additions & 47 deletions scripts/update-brew-cask.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
#script arguments
wheel_files=( $1 )
wheel_file=${wheel_files[0]}
homebrew_tap_repo=$2
arm_artifacts=( $2 )
arm_artifact=${arm_artifacts[0]}
intel_artifacts=( $3 )
intel_artifact=${intel_artifacts[0]}
homebrew_tap_repo=$4

#globals
command=algokit

#error codes
MISSING_WHEEL=1
CASK_GENERATION_FAILED=2
PR_CREATION_FAILED=3
MISSING_EXECUTABLE=2
CASK_GENERATION_FAILED=3
PR_CREATION_FAILED=4

if [[ ! -f $wheel_file ]]; then
>&2 echo "$wheel_file not found. 🚫"
Expand All @@ -20,6 +25,21 @@ else
echo "Found $wheel_file 🎉"
fi

if [[ ! -f $arm_artifact ]]; then
>&2 echo "$arm_artifact not found. 🚫"
exit $MISSING_EXECUTABLE
else
echo "Found $arm_artifact 🎉"
fi

if [[ ! -f $intel_artifact ]]; then
>&2 echo "$intel_artifact not found. 🚫"
exit $MISSING_EXECUTABLE
else
echo "Found $intel_artifact 🎉"
fi


get_metadata() {
local field=$1
grep "^$field:" $metadata | cut -f 2 -d : | xargs
Expand All @@ -29,10 +49,10 @@ create_cask() {
repo="https://github.com/${GITHUB_REPOSITORY}"
homepage="$repo"

wheel=`basename $wheel_file`
echo "Creating brew cask from $wheel_file"
echo "Creating brew cask"

#determine package_name, version and release tag from .whl
# determine package_name, version and release tag from .whl
wheel=`basename $wheel_file`
package_name=`echo $wheel | cut -d- -f1`

version=None
Expand All @@ -50,78 +70,66 @@ create_cask() {
echo Version: $version
echo Release Tag: $release_tag

url="$repo/releases/download/$release_tag/$wheel"
#get other metadata from wheel
# get other metadata from wheel
unzip -o $wheel_file -d . >/dev/null 2>&1
metadata=`echo $wheel | cut -f 1,2 -d "-"`.dist-info/METADATA

desc=`get_metadata Summary`
license=`get_metadata License`

echo "Calculating sha256 of $url..."
sha256=`curl -s -L $url | sha256sum | cut -f 1 -d ' '`
arm_binary_url="$repo/releases/download/$release_tag/$(basename $arm_artifact)"
echo "Calculating sha256 of $arm_binary_url..."
arm_sha256=`curl -s -L $arm_binary_url | sha256sum | cut -f 1 -d ' '`

ruby=${command}.rb

echo "Outputting $ruby..."
intel_binary_url="$repo/releases/download/$release_tag/$(basename $intel_artifact)"
echo "Calculating sha256 of $intel_binary_url..."
intel_sha256=`curl -s -L $intel_binary_url | sha256sum | cut -f 1 -d ' '`

cat << EOF > $ruby
# typed: false
# frozen_string_literal: true
cask_file=${command}.rb
echo "Outputting $cask_file..."

cask "$command" do
cat << EOF > $cask_file
cask "$package_name" do
arch arm: "arm64", intel: "x64"
version "$version"
sha256 "$sha256"
sha256 arm: "$arm_sha256", intel: "$intel_sha256"
url "$repo/releases/download/v#{version}/algokit-#{version}-py3-none-any.whl"
name "$command"
url "$repo/releases/download/v#{version}/algokit-#{version}-macos_#{arch}.tar.gz"
name "$package_name"
desc "$desc"
homepage "$homepage"
depends_on formula: "pipx"
container type: :naked
installer script: {
executable: "pipx",
args: ["install", "--force", "#{staged_path}/algokit-#{version}-py3-none-any.whl"],
print_stderr: false,
}
installer script: {
executable: "pipx",
args: ["ensurepath"],
}
installer script: {
executable: "bash",
args: ["-c", "echo \$(which pipx) uninstall $package_name >#{staged_path}/uninstall.sh"],
}
uninstall script: {
executable: "bash",
args: ["#{staged_path}/uninstall.sh"],
}
binary "#{staged_path}/#{token}"
postflight do
set_permissions "#{staged_path}/#{token}", "0755"
end
uninstall delete: "/usr/local/bin/#{token}"
end
EOF

if [[ ! -f $ruby ]]; then
>&2 echo "Failed to generate $ruby 🚫"
if [[ ! -f $cask_file ]]; then
>&2 echo "Failed to generate $cask_file 🚫"
exit $CASK_GENERATION_FAILED
else
echo "Created $ruby 🎉"
echo "Created $cask_file 🎉"
fi
}

create_pr() {
local full_ruby=`realpath $ruby`
local full_cask_filepath=`realpath $cask_file`
echo "Cloning $homebrew_tap_repo..."
clone_dir=`mktemp -d`
git clone "https://oauth2:${TAP_GITHUB_TOKEN}@github.com/${homebrew_tap_repo}.git" $clone_dir

echo "Commiting Casks/$ruby..."
echo "Commiting Casks/$cask_file..."
pushd $clone_dir
dest_branch="$command-update-$version"
git checkout -b $dest_branch
mkdir -p $clone_dir/Casks
cp $full_ruby $clone_dir/Casks
cp $full_cask_filepath $clone_dir/Casks
message="Updating $command to $version"
git add .
git commit --message "$message"
Expand Down

0 comments on commit 7162a62

Please sign in to comment.