diff --git a/.github/actions/disk-space/action.yml b/.github/actions/disk-space/action.yml index 8470ff68d..969cbf2cc 100644 --- a/.github/actions/disk-space/action.yml +++ b/.github/actions/disk-space/action.yml @@ -1,12 +1,12 @@ -name: 'Maximize build disk space' -description: 'Maximize build disk space' +name: 'Maximize build disk space for macOS' +description: 'Maximize build disk space on macOS' inputs: root-reserve-mb: description: 'Space to be left free on the root filesystem, in Megabytes.' required: false default: '1024' temp-reserve-mb: - description: 'Space to be left free on the temp filesystem (/mnt), in Megabytes.' + description: 'Space to be left free on the temp filesystem (/private/tmp), in Megabytes.' required: false default: '100' swap-size-mb: @@ -15,9 +15,7 @@ inputs: default: '4096' overprovision-lvm: description: | - Create the LVM disk images as sparse files, making the space required for the LVM image files *appear* unused on the - hosting volumes until actually allocated. Use with care, this can lead to surprising out-of-disk-space situations. - You should prefer adjusting root-reserve-mb/temp-reserve-mb over using this option. + (macOS does not support LVM in the same way as Linux, so this option is not applicable.) required: false default: 'false' build-mount-path: @@ -26,15 +24,15 @@ inputs: build-mount-path-ownership: description: 'Ownership of the mount point path, defaults to standard "runner" user and group.' required: false - default: 'runner:runner' + default: 'runner:staff' pv-loop-path: - description: 'Absolute file path for the LVM image created on the root filesystem, the default is usually fine.' + description: 'Absolute file path for the disk image created on the root filesystem.' required: false default: '/pv.img' tmp-pv-loop-path: - description: 'Absolute file path for the LVM image created on the temp filesystem, the default is usually fine. Must reside on /mnt' + description: 'Absolute file path for the disk image created on the temp filesystem.' required: false - default: '/mnt/tmp-pv.img' + default: '/private/tmp/tmp-pv.img' remove-dotnet: description: 'Removes .NET runtime and libraries. (frees ~17 GB)' required: false @@ -61,135 +59,125 @@ runs: - name: Disk space report before modification shell: bash run: | + echo "Memory and swap:" + vm_stat + echo + echo "Available storage:" - sudo df -h + df -h echo - name: Maximize build disk space shell: bash run: | - set -euo pipefail - - BUILD_MOUNT_PATH="${{ inputs.build-mount-path }}" - if [[ -z "${BUILD_MOUNT_PATH}" ]]; then - BUILD_MOUNT_PATH="${GITHUB_WORKSPACE}" - fi - - echo "Arguments:" - echo - echo " Root reserve: ${{ inputs.root-reserve-mb }} MiB" - echo " Temp reserve: ${{ inputs.temp-reserve-mb }} MiB" - echo " Swap space: ${{ inputs.swap-size-mb }} MiB" - echo " Overprovision LVM: ${{ inputs.overprovision-lvm }}" - echo " Mount path: ${BUILD_MOUNT_PATH}" - echo " Root PV loop path: ${{ inputs.pv-loop-path }}" - echo " Temp PV loop path: ${{ inputs.tmp-pv-loop-path }}" - echo -n " Removing: " - if [[ ${{ inputs.remove-dotnet }} == 'true' ]]; then - echo -n "dotnet " - fi - if [[ ${{ inputs.remove-android }} == 'true' ]]; then - echo -n "android " - fi - if [[ ${{ inputs.remove-haskell }} == 'true' ]]; then - echo -n "haskell " - fi - if [[ ${{ inputs.remove-codeql }} == 'true' ]]; then - echo -n "codeql " - fi - if [[ ${{ inputs.remove-docker-images }} == 'true' ]]; then - echo -n "docker " - fi - echo - - # store owner of $GITHUB_WORKSPACE in case the action deletes it - WORKSPACE_OWNER="$(stat -c '%U:%G' "${GITHUB_WORKSPACE}")" - - # ensure mount path exists before the action - sudo mkdir -p "${BUILD_MOUNT_PATH}" - sudo find "${BUILD_MOUNT_PATH}" -maxdepth 0 ! -empty -exec echo 'WARNING: directory [{}] is not empty, data loss might occur. Content:' \; -exec ls -al "{}" \; - - echo "Removing unwanted software... " - if [[ ${{ inputs.remove-dotnet }} == 'true' ]]; then - sudo rm -rf /usr/share/dotnet - fi - if [[ ${{ inputs.remove-android }} == 'true' ]]; then - sudo rm -rf /usr/local/lib/android - fi - if [[ ${{ inputs.remove-haskell }} == 'true' ]]; then - sudo rm -rf /opt/ghc - fi - if [[ ${{ inputs.remove-codeql }} == 'true' ]]; then - sudo rm -rf /opt/hostedtoolcache/CodeQL - fi - if [[ ${{ inputs.remove-docker-images }} == 'true' ]]; then - sudo docker image prune --all --force - fi - echo "... done" - - VG_NAME=buildvg - - # github runners have an active swap file in /mnt/swapfile - # we want to reuse the temp disk, so first unmount swap and clean the temp disk - echo "Unmounting and removing swap file." - sudo swapoff -a - sudo rm -f /mnt/swapfile - - echo "Creating LVM Volume." - echo " Creating LVM PV on root fs." - # create loop pv image on root fs - ROOT_RESERVE_KB=$(expr ${{ inputs.root-reserve-mb }} \* 1024) - ROOT_FREE_KB=$(df --block-size=1024 --output=avail / | tail -1) - ROOT_LVM_SIZE_KB=$(expr $ROOT_FREE_KB - $ROOT_RESERVE_KB) - ROOT_LVM_SIZE_BYTES=$(expr $ROOT_LVM_SIZE_KB \* 1024) - sudo touch "${{ inputs.pv-loop-path }}" && sudo fallocate -z -l "${ROOT_LVM_SIZE_BYTES}" "${{ inputs.pv-loop-path }}" - export ROOT_LOOP_DEV=$(sudo losetup --find --show "${{ inputs.pv-loop-path }}") - sudo pvcreate -f "${ROOT_LOOP_DEV}" - - # create pv on temp disk - echo " Creating LVM PV on temp fs." - TMP_RESERVE_KB=$(expr ${{ inputs.temp-reserve-mb }} \* 1024) - TMP_FREE_KB=$(df --block-size=1024 --output=avail /mnt | tail -1) - TMP_LVM_SIZE_KB=$(expr $TMP_FREE_KB - $TMP_RESERVE_KB) - TMP_LVM_SIZE_BYTES=$(expr $TMP_LVM_SIZE_KB \* 1024) - sudo touch "${{ inputs.tmp-pv-loop-path }}" && sudo fallocate -z -l "${TMP_LVM_SIZE_BYTES}" "${{ inputs.tmp-pv-loop-path }}" - export TMP_LOOP_DEV=$(sudo losetup --find --show "${{ inputs.tmp-pv-loop-path }}") - sudo pvcreate -f "${TMP_LOOP_DEV}" - - # create volume group from these pvs - sudo vgcreate "${VG_NAME}" "${TMP_LOOP_DEV}" "${ROOT_LOOP_DEV}" - - echo "Recreating swap" - # create and activate swap - sudo lvcreate -L "${{ inputs.swap-size-mb }}M" -n swap "${VG_NAME}" - sudo mkswap "/dev/mapper/${VG_NAME}-swap" - sudo swapon "/dev/mapper/${VG_NAME}-swap" - - echo "Creating build volume" - # create and mount build volume - sudo lvcreate -l 100%FREE -n buildlv "${VG_NAME}" - if [[ ${{ inputs.overprovision-lvm }} == 'true' ]]; then - sudo mkfs.ext4 -m0 "/dev/mapper/${VG_NAME}-buildlv" - else - sudo mkfs.ext4 -Enodiscard -m0 "/dev/mapper/${VG_NAME}-buildlv" - fi - sudo mount "/dev/mapper/${VG_NAME}-buildlv" "${BUILD_MOUNT_PATH}" - sudo chown -R "${{ inputs.build-mount-path-ownership }}" "${BUILD_MOUNT_PATH}" - - # if build mount path is a parent of $GITHUB_WORKSPACE, and has been deleted, recreate it - if [[ ! -d "${GITHUB_WORKSPACE}" ]]; then - sudo mkdir -p "${GITHUB_WORKSPACE}" - sudo chown -R "${WORKSPACE_OWNER}" "${GITHUB_WORKSPACE}" - fi + set -euo pipefail + + BUILD_MOUNT_PATH="${{ inputs.build-mount-path }}" + if [[ -z "${BUILD_MOUNT_PATH}" ]]; then + BUILD_MOUNT_PATH="${GITHUB_WORKSPACE}" + fi + + echo "Arguments:" + echo + echo " Root reserve: ${{ inputs.root-reserve-mb }} MiB" + echo " Temp reserve: ${{ inputs.temp-reserve-mb }} MiB" + echo " Swap space: ${{ inputs.swap-size-mb }} MiB" + echo " Overprovision LVM: ${{ inputs.overprovision-lvm }} (Not applicable on macOS)" + echo " Mount path: ${BUILD_MOUNT_PATH}" + echo " Root PV loop path: ${{ inputs.pv-loop-path }}" + echo " Temp PV loop path: ${{ inputs.tmp-pv-loop-path }}" + echo -n " Removing: " + if [[ ${{ inputs.remove-dotnet }} == 'true' ]]; then + echo -n "dotnet " + fi + if [[ ${{ inputs.remove-android }} == 'true' ]]; then + echo -n "android " + fi + if [[ ${{ inputs.remove-haskell }} == 'true' ]]; then + echo -n "haskell " + fi + if [[ ${{ inputs.remove-codeql }} == 'true' ]]; then + echo -n "codeql " + fi + if [[ ${{ inputs.remove-docker-images }} == 'true' ]]; then + echo -n "docker " + fi + echo + + # store owner of $GITHUB_WORKSPACE in case the action deletes it + WORKSPACE_OWNER="$(stat -f '%Su:%Sg' "${GITHUB_WORKSPACE}")" + + echo "Removing unwanted software... " + if [[ ${{ inputs.remove-dotnet }} == 'true' ]]; then + sudo rm -rf /usr/local/share/dotnet + fi + if [[ ${{ inputs.remove-android }} == 'true' ]]; then + sudo rm -rf /usr/local/lib/android + fi + if [[ ${{ inputs.remove-haskell }} == 'true' ]]; then + sudo rm -rf /usr/local/ghc + fi + if [[ ${{ inputs.remove-codeql }} == 'true' ]]; then + sudo rm -rf /usr/local/hostedtoolcache/CodeQL + fi + if [[ ${{ inputs.remove-docker-images }} == 'true' ]]; then + docker image prune --all --force + fi + echo "... done" + + # No LVM on macOS, use disk images instead + VG_NAME=buildvg + + echo "Creating disk images." + # Create a disk image for the root filesystem + ROOT_RESERVE_KB=$(expr ${{ inputs.root-reserve-mb }} \* 1024) + ROOT_FREE_KB=$(df -k / | tail -1 | awk '{print $4}') + ROOT_LVM_SIZE_KB=$(expr $ROOT_FREE_KB - $ROOT_RESERVE_KB) + hdiutil create -size ${ROOT_LVM_SIZE_KB}k -fs HFS+ -volname "${VG_NAME}-root" "${{ inputs.pv-loop-path }}" + + # Create a disk image for the temp filesystem + TMP_RESERVE_KB=$(expr ${{ inputs.temp-reserve-mb }} \* 1024) + TMP_FREE_KB=$(df -k /private/tmp | tail -1 | awk '{print $4}') + TMP_LVM_SIZE_KB=$(expr $TMP_FREE_KB - $TMP_RESERVE_KB) + hdiutil create -size ${TMP_LVM_SIZE_KB}k -fs HFS+ -volname "${VG_NAME}-tmp" "${{ inputs.tmp-pv-loop-path }}" + + echo "Attaching disk images." + # Attach and mount disk images + ROOT_DEV=$(hdiutil attach -nobrowse -nomount "${{ inputs.pv-loop-path }}" | awk '{print $1}') + TMP_DEV=$(hdiutil attach -nobrowse -nomount "${{ inputs.tmp-pv-loop-path }}" | awk '{print $1}') + sudo newfs_hfs -v "${VG_NAME}-root" ${ROOT_DEV} + sudo newfs_hfs -v "${VG_NAME}-tmp" ${TMP_DEV} + + echo "Creating build volume" + # Mount the root disk image to the build mount path + sudo mount -t hfs ${ROOT_DEV} "${BUILD_MOUNT_PATH}" + sudo chown -R "${{ inputs.build-mount-path-ownership }}" "${BUILD_MOUNT_PATH}" + + echo "Recreating swap" + # Create and activate swap (macOS-specific approach) + sudo rm -f /private/var/vm/swapfile + sudo dd if=/dev/zero of=/private/var/vm/swapfile bs=1024k count=${{ inputs.swap-size-mb }} + sudo chmod 600 /private/var/vm/swapfile + sudo /usr/sbin/diskutil secureErase freespace 0 /private/var/vm/swapfile + sudo /usr/bin/env fs_usage | grep swap + + # if build mount path is a parent of $GITHUB_WORKSPACE, and has been deleted, recreate it + if [[ ! -d "${GITHUB_WORKSPACE}" ]]; then + sudo mkdir -p "${GITHUB_WORKSPACE}" + sudo chown -R "${WORKSPACE_OWNER}" "${GITHUB_WORKSPACE}" + fi + + # Unmount and detach the disk images at the end + sudo umount "${BUILD_MOUNT_PATH}" + hdiutil detach ${ROOT_DEV} + hdiutil detach ${TMP_DEV} - name: Disk space report after modification shell: bash run: | echo "Memory and swap:" - sudo free - echo - sudo swapon --show + vm_stat echo echo "Available storage:" - sudo df -h \ No newline at end of file + df -h