我对我的C项目使用以下Github操作工作流。工作流大约在40秒内完成,但其中一半以上的时间用于安装valgrind
软件包及其依赖项。
我相信缓存可以帮助我加快工作流程。我不介意多等几秒钟,但这似乎是对GitHub资源的毫无意义的浪费。
name: C Workflow
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: make
run: make
- name: valgrind
run: |
sudo apt-get install -y valgrind
valgrind -v --leak-check=full --show-leak-kinds=all ./bin
运行sudo apt get install-y valgrind
安装以下软件包:
gdb
gdbserver
libbabeltrace1
libc6-dbg
libipt1
valgrind
我知道Actions支持缓存特定的目录(已经有一些回答了SO问题和文章),但是我不确定apt安装的所有不同的包最终会在哪里。我假设/bin/
或/usr/bin/
不是受安装包影响的唯一目录。
是否有一种优雅的方法来缓存已安装的系统包,以便将来运行工作流?
这个答案的目的是展示如何使用github操作进行缓存。不一定要展示如何缓存valgrind
,它确实展示了这一点,但更多的是展示并非所有东西都可以/应该缓存,缓存和恢复缓存与重新安装依赖关系的权衡需要考虑在内。
您将使用操作/cache
操作来执行此操作。
添加它作为一个步骤(在您需要使用valgrind之前):
- name: Cache valgrind
uses: actions/cache@v2
id: cache-valgrind
with:
path: "~/valgrind"
key: ${{secrets.VALGRIND_VERSION}}
下一步应尝试安装缓存版本(如果有),或从存储库安装:
- name: Install valgrind
env:
CACHE_HIT: ${{steps.cache-valgrind.outputs.cache-hit}}
VALGRIND_VERSION: ${{secrets.VALGRIND_VERSION}}
run: |
if [[ "$CACHE_HIT" == 'true' ]]; then
sudo cp --verbose --force --recursive ~/valgrind/* /
else
sudo apt-get install --yes valgrind="$VALGRIND_VERSION"
mkdir -p ~/valgrind
sudo dpkg -L valgrind | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/valgrind/
fi
设置VALGRIND_VERSION
秘密为输出:
apt-cache policy valgrind | grep -oP '(?<=Candidate:\s)(.+)'
这将允许您在发布新版本时仅通过更改secret的值使缓存无效。
dpkg-L valgrind
用于列出使用sudo apt get install valgrind
时安装的所有文件。
我们现在可以使用这个命令将所有依赖项复制到缓存文件夹中:
dpkg -L valgrind | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/valgrind/
除了复制valgrind
的所有组件之外,可能还需要复制依赖项(例如在本例中的libc
),但是我不建议继续沿着这条路走,因为依赖链只是从那里生长出来的。准确地说,复制最终有一个适合valgrind运行的环境所需的依赖关系如下:
要复制所有这些依赖项,可以使用与上面相同的语法:
for dep in libc6 libgcc1 gcc-8-base; do
dpkg -L $dep | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/valgrind/
done
当安装valgrind
首先需要简单地运行sudo apt-get安装valgrind
时,所有这些工作真的值得麻烦吗?如果您的目标是加快构建过程,那么您还必须考虑恢复(下载和提取)缓存所需的时间,而不是简单地再次运行命令来安装valgrind
。
最后要恢复缓存,假设它存储在/tmp/valgrind
,您可以使用以下命令:
cp --force --recursive /tmp/valgrind/* /
它基本上将所有文件从缓存复制到根分区。
除了上面的过程之外,我还有一个“缓存valgrind”的示例,通过从源代码安装和编译它。现在缓存的大小约为63MB(压缩),仍然需要单独安装libc
,这样做有违目的。
注意:这个问题的另一个答案是,通过使用一个依赖于预先安装的依赖项的容器,我认为可以更安全地缓存依赖关系。最好的部分是,您可以使用操作使这些容器保持最新。
参考文献:
您可以使用预先安装的valgrind
创建docker映像,并在该映像上运行您的工作流。
创建一个Dockerfile
,内容如下:
FROM ubuntu
RUN apt-get install -y valgrind
构建它并将其推送到dockerhub:
docker build -t natiiix/valgrind .
docker push natiiix/valgrind
然后将以下内容用作您的工作流程:
name: C Workflow
on: [push, pull_request]
jobs:
build:
container: natiiix/valgrind
steps:
- uses: actions/checkout@v1
- name: make
run: make
- name: valgrind
run: valgrind -v --leak-check=full --show-leak-kinds=all ./bin
完全未经测试,但你明白了。
更新:我创建了一个GitHub操作,作为这个解决方案,代码更少,优化更好。缓存新内容
这个解决方案类似于大多数投票。我尝试了建议的解决方案,但它不适合我,因为我正在安装tex live-latex
和pandoc
,它有许多依赖关系和子依赖关系。
我创建了一个可以帮助很多人的解决方案。一种情况是当你安装了几个包(apt安装
),另一种解决方案是当你制作了一个程序,并且需要一段时间。
解决方案:
find
创建容器中所有文件的列表李> make
programs,无论您想缓存什么李> find
创建容器中所有文件的列表李> diff
获取新创建的文件李> 操作/cache@v2
李>/
李>什么时候用这个?
实施:
>
我的操作的登录页:工作流。
释放yml
name: CI - Release books
on:
release:
types: [ released ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: cache-packages
with:
path: ${{ runner.temp }}/cache-linux
key: ${{ runner.os }}-cache-packages-v2.1
- name: Install packages
if: steps.cache-packages.outputs.cache-hit != 'true'
env:
SOURCE: ${{ runner.temp }}/cache-linux
run: |
set +xv
echo "# --------------------------------------------------------"
echo "# Action environment variables"
echo "github.workspace: ${{ github.workspace }}"
echo "runner.workspace: ${{ runner.workspace }}"
echo "runner.os: ${{ runner.os }}"
echo "runner.temp: ${{ runner.temp }}"
echo "# --------------------------------------------------------"
echo "# Where am I?"
pwd
echo "SOURCE: ${SOURCE}"
ls -lha /
sudo du -h -d 1 / 2> /dev/null || true
echo "# --------------------------------------------------------"
echo "# APT update"
sudo apt update
echo "# --------------------------------------------------------"
echo "# Set up snapshot"
mkdir -p "${{ runner.temp }}"/snapshots/
echo "# --------------------------------------------------------"
echo "# Install tools"
sudo rm -f /var/lib/apt/lists/lock
#sudo apt install -y vim bash-completion
echo "# --------------------------------------------------------"
echo "# Take first snapshot"
sudo find / \
-type f,l \
-not \( -path "/sys*" -prune \) \
-not \( -path "/proc*" -prune \) \
-not \( -path "/mnt*" -prune \) \
-not \( -path "/dev*" -prune \) \
-not \( -path "/run*" -prune \) \
-not \( -path "/etc/mtab*" -prune \) \
-not \( -path "/var/cache/apt/archives*" -prune \) \
-not \( -path "/tmp*" -prune \) \
-not \( -path "/var/tmp*" -prune \) \
-not \( -path "/var/backups*" \) \
-not \( -path "/boot*" -prune \) \
-not \( -path "/vmlinuz*" -prune \) \
> "${{ runner.temp }}"/snapshots/snapshot_01.txt 2> /dev/null \
|| true
echo "# --------------------------------------------------------"
echo "# Install pandoc and dependencies"
sudo apt install -y texlive-latex-extra wget
wget -q https://github.com/jgm/pandoc/releases/download/2.11.2/pandoc-2.11.2-1-amd64.deb
sudo dpkg -i pandoc-2.11.2-1-amd64.deb
rm -f pandoc-2.11.2-1-amd64.deb
echo "# --------------------------------------------------------"
echo "# Take second snapshot"
sudo find / \
-type f,l \
-not \( -path "/sys*" -prune \) \
-not \( -path "/proc*" -prune \) \
-not \( -path "/mnt*" -prune \) \
-not \( -path "/dev*" -prune \) \
-not \( -path "/run*" -prune \) \
-not \( -path "/etc/mtab*" -prune \) \
-not \( -path "/var/cache/apt/archives*" -prune \) \
-not \( -path "/tmp*" -prune \) \
-not \( -path "/var/tmp*" -prune \) \
-not \( -path "/var/backups*" \) \
-not \( -path "/boot*" -prune \) \
-not \( -path "/vmlinuz*" -prune \) \
> "${{ runner.temp }}"/snapshots/snapshot_02.txt 2> /dev/null \
|| true
echo "# --------------------------------------------------------"
echo "# Filter new files"
diff -C 1 \
--color=always \
"${{ runner.temp }}"/snapshots/snapshot_01.txt \
"${{ runner.temp }}"/snapshots/snapshot_02.txt \
| grep -E "^\+" \
| sed -E s/..// \
> "${{ runner.temp }}"/snapshots/snapshot_new_files.txt
< "${{ runner.temp }}"/snapshots/snapshot_new_files.txt wc -l
ls -lha "${{ runner.temp }}"/snapshots/
echo "# --------------------------------------------------------"
echo "# Make cache directory"
rm -fR "${SOURCE}"
mkdir -p "${SOURCE}"
while IFS= read -r LINE
do
sudo cp -a --parent "${LINE}" "${SOURCE}"
done < "${{ runner.temp }}"/snapshots/snapshot_new_files.txt
ls -lha "${SOURCE}"
echo ""
sudo du -sh "${SOURCE}" || true
echo "# --------------------------------------------------------"
- name: Copy cached packages
if: steps.cache-packages.outputs.cache-hit == 'true'
env:
SOURCE: ${{ runner.temp }}/cache-linux
run: |
echo "# --------------------------------------------------------"
echo "# Using Cached packages"
ls -lha "${SOURCE}"
sudo cp --force --recursive "${SOURCE}"/. /
echo "# --------------------------------------------------------"
- name: Generate release files and commit in GitHub
run: |
echo "# --------------------------------------------------------"
echo "# Generating release files"
git fetch --all
git pull --rebase origin main
git checkout main
cd ./src/programming-from-the-ground-up
./make.sh
cd ../../
ls -lha release/
git config --global user.name 'Israel Roldan'
git config --global user.email 'israel.alberto.rv@gmail.com'
git add .
git status
git commit -m "Automated Release."
git push
git status
echo "# --------------------------------------------------------"
解释部分代码:
这里是操作缓存,指示一个键
,它将生成一次,并在以后的执行中进行比较。路径是生成缓存压缩文件的文件所在的目录。
- uses: actions/cache@v2
id: cache-packages
with:
path: ${{ runner.temp }}/cache-linux
key: ${{ runner.os }}-cache-packages-v2.1
如果退出缓存命中
则对键
缓存的有条件搜索为“true”。
if: steps.cache-packages.outputs.cache-hit != 'true'
if: steps.cache-packages.outputs.cache-hit == 'true'
这并不重要,但是当第一次执行du
命令时,Linux索引所有的文件(5~8分钟),然后当我们使用fine
时,只需要大约50秒就可以得到所有的文件文件。如果你愿意,你可以删除这一行。
带后缀的命令| | true
防止出现2
sudo du -h -d 1 / 2> /dev/null || true
这是神奇的部分,使用查找
生成实际文件的列表,排除一些目录来优化缓存文件夹。它也将在安装和make
程序后执行。在下一个快照中,文件名应该是不同的snapshot_02.txt
。
sudo find / \
-type f,l \
-not \( -path "/sys*" -prune \) \
-not \( -path "/proc*" -prune \) \
-not \( -path "/mnt*" -prune \) \
-not \( -path "/dev*" -prune \) \
-not \( -path "/run*" -prune \) \
-not \( -path "/etc/mtab*" -prune \) \
-not \( -path "/var/cache/apt/archives*" -prune \) \
-not \( -path "/tmp*" -prune \) \
-not \( -path "/var/tmp*" -prune \) \
-not \( -path "/var/backups*" \) \
-not \( -path "/boot*" -prune \) \
-not \( -path "/vmlinuz*" -prune \) \
> "${{ runner.temp }}"/snapshots/snapshot_01.txt 2> /dev/null \
|| true
安装一些软件包并pandoc
。
sudo apt install -y texlive-latex-extra wget
wget -q https://github.com/jgm/pandoc/releases/download/2.11.2/pandoc-2.11.2-1-amd64.deb
sudo dpkg -i pandoc-2.11.2-1-amd64.deb
rm -f pandoc-2.11.2-1-amd64.deb
使用添加的新文件生成文本文件,这些文件也可以是符号文件。
diff -C 1 \
"${{ runner.temp }}"/snapshots/snapshot_01.txt \
"${{ runner.temp }}"/snapshots/snapshot_02.txt \
| grep -E "^\+" \
| sed -E s/..// \
> "${{ runner.temp }}"/snapshots/snapshot_new_files.txt
最后,将所有文件复制到缓存目录中,作为存档保存原始信息。
while IFS= read -r LINE
do
sudo cp -a --parent "${LINE}" "${SOURCE}"
done < "${{ runner.temp }}"/snapshots/snapshot_new_files.txt
步骤将所有缓存文件复制到主路径/
。
- name: Copy cached packages
if: steps.cache-packages.outputs.cache-hit == 'true'
env:
SOURCE: ${{ runner.temp }}/cache-linux
run: |
echo "# --------------------------------------------------------"
echo "# Using Cached packages"
ls -lha "${SOURCE}"
sudo cp --force --recursive "${SOURCE}"/. /
echo "# --------------------------------------------------------"
这一步就是我使用缓存生成的安装包的地方,/制作sh
脚本使用pandoc
进行一些转换。正如我提到的,您可以创建使用缓存好处的其他步骤,也可以创建不使用缓存的其他步骤。
- name: Generate release files and commit in GitHub
run: |
echo "# --------------------------------------------------------"
echo "# Generating release files"
cd ./src/programming-from-the-ground-up
./make.sh