Add release scripts

This commit is contained in:
Zubin Duggal 2022-04-23 16:17:58 +05:30
parent 5ce21d0a77
commit 9114c11907
No known key found for this signature in database
GPG Key ID: 7CCFC277A14C97A7
3 changed files with 237 additions and 0 deletions

7
release/README.md Normal file
View File

@ -0,0 +1,7 @@
# Making and uploading the Gitlab release to downloads.haskell.org
1. Run the gitlab release pipeline using https://gitlab.haskell.org/haskell/haskell-language-server/-/pipelines/new
2. Once the pipeline has completed, download the artifacts using `fetch_gitlab.py`
- For example for the `1.7.0.0` release: `python fetch_gitlab.py -p <pipeline_id> --output haskell-language-server-1.7.0.0 -r 1.7.0.0`
- Ensure all the artifacts in the output directory are accurate and add any missing/extra artifacts
3. `cd` to the output directory created in the previous step, and run `SIGNING_KEY=<your signing key> ../upload.sh`

76
release/fetch_gitlab.py Normal file
View File

@ -0,0 +1,76 @@
# adapted from https://gitlab.haskell.org/bgamari/ghc-utils/-/blob/master/rel-eng/fetch-gitlab-artifacts/fetch_gitlab.py
import logging
from pathlib import Path
import subprocess
import gitlab
logging.basicConfig(level=logging.INFO)
def strip_prefix(s, prefix):
if s.startswith(prefix):
return s[len(prefix):]
else:
return None
def fetch_artifacts(release: str, pipeline_id: int,
dest_dir: Path, gl: gitlab.Gitlab):
dest_dir.mkdir(exist_ok=True)
proj = gl.projects.get('haskell/haskell-language-server')
pipeline = proj.pipelines.get(pipeline_id)
tmpdir = Path("fetch-gitlab")
tmpdir.mkdir(exist_ok=True)
for pipeline_job in pipeline.jobs.list(all=True):
if len(pipeline_job.artifacts) == 0:
logging.info(f'job {pipeline_job.name} ({pipeline_job.id}) has no artifacts')
continue
job = proj.jobs.get(pipeline_job.id)
platform = strip_prefix(job.name, 'tar-')
if not platform:
logging.info(f'Skipping {job.name} (not a tar job)')
continue
try:
destdir = tmpdir / job.name
zip_name = Path(f"{tmpdir}/{job.name}.zip")
if not zip_name.exists() or zip_name.stat().st_size == 0:
logging.info(f'downloading archive {zip_name} for job {job.name} (job {job.id})...')
with open(zip_name, 'wb') as f:
job.artifacts(streamed=True, action=f.write)
if zip_name.stat().st_size == 0:
logging.info(f'artifact archive for job {job.name} (job {job.id}) is empty')
continue
dest = dest_dir / f'haskell-language-server-{release}-{platform}.tar.xz'
if dest.exists():
logging.info(f'bindist {dest} already exists')
continue
subprocess.run(['unzip', '-bo', zip_name, '-d', destdir])
bindist_files = list(destdir.glob('*/haskell-language-server*.tar.xz'))
if len(bindist_files) == 0:
logging.warn(f'Bindist does not exist')
continue
bindist = bindist_files[0]
logging.info(f'extracted {job.name} to {dest}')
bindist.replace(dest)
except Exception as e:
logging.error(f'Error fetching job {job.name}: {e}')
pass
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--pipeline', '-p', required=True, type=int, help="pipeline id")
parser.add_argument('--release', '-r', required=True, type=str, help="release name")
parser.add_argument('--output', '-o', type=Path, default=Path.cwd(), help="output directory")
parser.add_argument('--profile', '-P', default='haskell',
help='python-gitlab.cfg profile name')
args = parser.parse_args()
gl = gitlab.Gitlab.from_config(args.profile)
fetch_artifacts(args.release, args.pipeline,
dest_dir=args.output, gl=gl)
if __name__ == '__main__':
main()

154
release/upload.sh Executable file
View File

@ -0,0 +1,154 @@
#!/usr/bin/env bash
set -e
# This is a script for preparing and uploading a release of Haskell Language Server.
# Adapted from https://gitlab.haskell.org/bgamari/ghc-utils/-/commits/master/rel-eng/upload.sh
#
# Usage,
# 1. Set $SIGNING_KEY to your key id (prefixed with '=')
# 2. Create a directory called haskell-langauge-server-<release_number> and place the binary tarballs there
# 4. Run this script from that directory
#
# You can also invoke the script with an argument to perform only
# a subset of the usual release,
#
# upload.sh gen_hashes generate signed hashes of the release
# tarballs
# upload.sh sign generate signed hashes of the release
# tarballs
# upload.sh upload upload the tarballs and documentation
# to downloads.haskell.org
#
# Prerequisites: moreutils
# Infer release name from directory name
if [ -z "$rel_name" ]; then
rel_name="$(basename $(pwd))"
fi
# Infer version from tarball names
if [ -z "$ver" ]; then
ver="$(ls haskell-language-server-*.tar.* | sed -ne 's/haskell-language-server-\([0-9]\+\.[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).\+/\1/p' | head -n1)"
if [ -z "$ver" ]; then echo "Failed to infer \$ver"; exit 1; fi
fi
echo HLS version $ver
host="webhost.haskell.org"
usage() {
echo "Usage: [rel_name=<name>] SIGNING_KEY=<key> $0 <action>"
echo
echo "where,"
echo " rel_name gives the release name (e.g. 1.7.0.0)"
echo "and <action> is one of,"
echo " [nothing] do everything below"
echo " gen_hashes generated hashes of the release tarballs"
echo " sign sign hashes of the release tarballs"
echo " upload upload the tarballs and documentation to downloads.haskell.org"
echo " purge_all purge entire release from the CDN"
echo " purge_file file purge a given file from the CDN"
echo " verify verify the signatures in this directory"
echo
}
if [ -z "$ver" ]; then
usage
exit 1
fi
if [ -z "$rel_name" ]; then
rel_name="$ver"
fi
# returns the set of files that must have hashes generated.
function hash_files() {
echo $(find -maxdepth 1 \
-iname '*.xz' \
-o -iname '*.lz' \
-o -iname '*.bz2' \
-o -iname '*.zip' \
)
echo $(find -maxdepth 1 -iname '*.patch')
}
function gen_hashes() {
echo -n "Hashing..."
sha1sum $(hash_files) >| SHA1SUMS &
sha256sum $(hash_files) >| SHA256SUMS &
wait
echo "done"
}
function sign() {
# Kill DISPLAY lest pinentry won't work
DISPLAY=
eval "$(gpg-agent --daemon --sh --pinentry-program $(which pinentry))"
for i in $(hash_files) SHA1SUMS SHA256SUMS; do
if [ -e $i -a -e $i.sig -a $i.sig -nt $i ]; then
echo "Skipping signing of $i"
continue
elif [ -e $i.sig ] && gpg2 --verify $i.sig; then
# Don't resign if current signature is valid
touch $i.sig
continue
fi
echo "Signing $i"
rm -f $i.sig
gpg2 --use-agent --detach-sign --local-user="$SIGNING_KEY" $i
done
}
function verify() {
if [ $(find -iname '*.sig' | wc -l) -eq 0 ]; then
echo "No signatures to verify"
return
fi
for i in *.sig; do
echo
echo Verifying $i
gpg2 --verify $i $(basename $i .sig)
done
}
function upload() {
verify
chmod ugo+r,o-w -R .
dir=$(echo $rel_name | sed s/-release//)
lftp -c " \
open -u hls-downloads: sftp://$host && \
mirror -P20 -c --reverse --exclude=fetch-gitlab --exclude=out . hls/$dir && \
wait all;"
chmod ugo-w $(ls *.xz *.bz2 *.zip)
}
function purge_all() {
# Purge CDN cache
curl -X PURGE http://downloads.haskell.org/hls/
curl -X PURGE http://downloads.haskell.org/~hls/
curl -X PURGE http://downloads.haskell.org/hls/$dir
curl -X PURGE http://downloads.haskell.org/hls/$dir/
curl -X PURGE http://downloads.haskell.org/~hls/$dir
curl -X PURGE http://downloads.haskell.org/~hls/$dir/
for i in *; do
purge_file $i
done
}
function purge_file() {
curl -X PURGE http://downloads.haskell.org/~hls/$rel_name/$i
curl -X PURGE http://downloads.haskell.org/~hls/$rel_name/$i/
curl -X PURGE http://downloads.haskell.org/hls/$rel_name/$i
curl -X PURGE http://downloads.haskell.org/hls/$rel_name/$i/
}
if [ "x$1" == "x" ]; then
gen_hashes
sign
upload
purge_all
else
$@
fi