diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml deleted file mode 100644 index 479dde0..0000000 --- a/.github/workflows/build_and_test.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Test the Docker image on every pull request. -# -# The steps are: -# 1. Build docker image using cached data. -# 2. Start the docker container with local folder mounted to it. - -name: build-and-test-image-from-pull-request - -on: - [pull_request] - -jobs: - - build-and-test: - - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - - uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Build image locally - uses: docker/build-push-action@v2 - with: - load: true - push: false - tags: build-machine:newly-baked - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4400131 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,184 @@ +--- +name: Build images and run tests and publish + +on: + pull_request: + push: + branches: + - main + tags: + - "v*" + workflow_dispatch: + +env: + BUILDKIT_PROGRESS: plain + FORCE_COLOR: 1 + +# https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # only cancel in-progress jobs or runs for the current workflow - matches against branch & tags + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + build: + + runs-on: ubuntu-latest + timeout-minutes: 30 + + outputs: + image: ${{ steps.bake_metadata.outputs.image }} + + steps: + - name: Checkout Repo ⚡️ + uses: actions/checkout@v4 + + - name: Set up QEMU + if: ${{ inputs.platforms != 'linux/amd64' }} + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry 🔑 + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + + - uses: crazy-max/ghaction-github-runtime@v3 + - name: Build and upload to ghcr.io 📤 + id: build-upload + uses: docker/bake-action@v4 + with: + push: true + # Using provenance to disable default attestation so it will build only desired images: + # https://github.com/orgs/community/discussions/45969 + provenance: false + set: | + *.platform=linux/amd64 + *.output=type=registry,name-canonical=true,push-by-digest=true + *.cache-from=type=gha + *.cache-to=type=gha,mode=max + + files: | + docker-bake.hcl + build.json + .github/workflows/env.hcl + + - name: Set output variables + id: bake_metadata + run: | + .github/workflows/extract-image-name.sh | tee -a "${GITHUB_OUTPUT}" + env: + BAKE_METADATA: ${{ steps.build-upload.outputs.metadata }} + + test: + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: build + + steps: + + - name: Checkout Repo ⚡️ + uses: actions/checkout@v4 + + - name: Login to GitHub Container Registry 🔑 + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run container checking libraries exist in the container + run: | + docker run --rm ${{ needs.build.outputs.image }} /bin/bash -c "ls -l /usr/local" > /tmp/ls-l.txt + if grep -q libxc /tmp/ls-l.txt; then + echo "libxc found" + else + echo "libxc not found" + exit 1 + fi + + if grep -q lapack /tmp/ls-l.txt; then + echo "lapack found" + else + echo "lapack not found" + exit 1 + fi + + publish: + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: [build, test] + strategy: + matrix: + registry: ["docker.io", "ghcr.io"] + if: >- + github.repository == 'pspgen/build-machine' + && (github.ref_type == 'tag' || github.ref_name == 'main') + + steps: + - uses: actions/checkout@v4 + + - name: Login to GitHub Container Registry 🔑 + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to DockerHub 🔑 + uses: docker/login-action@v3 + if: inputs.registry == 'docker.io' + with: + registry: docker.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Read build variables + id: build_vars + run: | + vars=$(cat build.json | jq -c '[.variable | to_entries[] | {"key": .key, "value": .value.default}] | from_entries') + echo "vars=$vars" | tee -a "${GITHUB_OUTPUT}" + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + env: ${{ fromJSON(steps.build_vars.outputs.vars) }} + with: + images: ${{ matrix.registry }}/${{ github.repository_owner }}/build-machine + tags: | + type=edge,enable={{is_default_branch}} + type=raw,value={{tag}},enable=${{ github.ref_type == 'tag' && ! startsWith(github.ref_name, 'v') }} + type=raw,value=gnu-compiler-${{ env.GNU_COMPILER_VERSION }},enable=${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} + type=raw,value=libxc-${{ env.LIBXC_VERSION }},enable=${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} + type=raw,value=lapack-${{ env.LAPACK_VERSION }},enable=${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} + type=match,pattern=v(\d{4}\.\d{4}(-.+)?),group=1 + + - name: Determine source image + id: images + run: | + src=$(echo '${{ inputs.images }}'| jq -cr '.[("${{ matrix.target }}"|ascii_upcase|sub("-"; "_"; "g")) + "_IMAGE"]') + echo "src=$src" | tee -a "${GITHUB_OUTPUT}" + + - name: Push image + uses: akhilerm/tag-push-action@v2.2.0 + with: + src: ${{ needs.build.outputs.image }} + dst: ${{ steps.meta.outputs.tags }} + + - name: Docker Hub Description + if: inputs.registry == 'docker.io' + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: pspgen/build-machine + short-description: ${{ github.event.repository.description }} \ No newline at end of file diff --git a/.github/workflows/env.hcl b/.github/workflows/env.hcl new file mode 100644 index 0000000..c5b87d5 --- /dev/null +++ b/.github/workflows/env.hcl @@ -0,0 +1,2 @@ +# env.hcl +REGISTRY = "ghcr.io" \ No newline at end of file diff --git a/.github/workflows/extract-image-name.sh b/.github/workflows/extract-image-name.sh new file mode 100755 index 0000000..24b6ab9 --- /dev/null +++ b/.github/workflows/extract-image-name.sh @@ -0,0 +1,34 @@ +# Extract image names together with their sha256 digests +# from the docker/bake-action metadata output. +# These together uniquely identify newly built images. + +# The input to this script is a JSON string passed via BAKE_METADATA env variable +# Here's example input (trimmed to relevant bits): +# BAKE_METADATA: { +# "base": { +# "containerimage.descriptor": { +# "mediaType": "application/vnd.docker.distribution.manifest.v2+json", +# "digest": "sha256:8e57a52b924b67567314b8ed3c968859cad99ea13521e60bbef40457e16f391d", +# "size": 6170, +# }, +# "containerimage.digest": "sha256:8e57a52b924b67567314b8ed3c968859cad99ea13521e60bbef40457e16f391d", +# "image.name": "ghcr.io/pspgen/build-machine" +# } +# } +# +# Example output (real output is on one line): +# +# image="ghcr.io/pspgen/build-machine@sha256:79a0f984b9e03b733304fda809ad3e8eec8416992ff334052d75da00cadb8f12" +# } +# +# This json output is later turned to environment variables using fromJson() GHA builtin +# (e.g. BUILD_MACHINE_IMAGE=ghcr.io/pspgen/build-machine@sha256:8e57a52b...) +# and these are in turn read in the docker-compose..yml files for tests. + +if [[ -z ${BAKE_METADATA-} ]];then + echo "ERROR: Environment variable BAKE_METADATA is not set!" + exit 1 +fi + +image=$(echo "${BAKE_METADATA}" | jq -c '. as $base | to_entries[] | [(.value."image.name"|split(",")[0]),(.value."containerimage.digest")]|join("@")') +echo "image=$image" \ No newline at end of file diff --git a/.github/workflows/push_to_dockerhub.yml b/.github/workflows/push_to_dockerhub.yml deleted file mode 100644 index 25e88d7..0000000 --- a/.github/workflows/push_to_dockerhub.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Build the new Docker image on every commit to main branch and on every new tag. -# No caching is involved for the image build. The new image is then pushed to the Docker Hub. - -name: build-and-push-to-dockerhub - -on: - push: - branches: - - main - tags: - - 'v*' - -jobs: - - build-and-push: - - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - - uses: actions/checkout@v2 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ github.repository }} - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ steps.meta.outputs.tags }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 793f5a8..72d5bd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,42 @@ -FROM ubuntu:focal +# syntax=docker/dockerfile:1 +FROM base-image +# Tool chain for building RUN apt-get update && apt-get install -y \ build-essential \ automake \ autoconf \ libtool \ wget \ - gfortran-7 + gfortran-7 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* -RUN update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-7 7 && \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 7 +ARG GNU_COMPILER_VERSION + +RUN update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-${GNU_COMPILER_VERSION} 2 && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-${GNU_COMPILER_VERSION} 2 WORKDIR /build -# compile lapack-3.10.1 -RUN wget -c -O lapack-3.10.1.tar.gz https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v3.10.1.tar.gz && \ - tar xf lapack-3.10.1.tar.gz && \ - cd lapack-3.10.1 && \ +ARG LAPACK_VERSION + +RUN wget -c -O lapack.tar.gz https://github.com/Reference-LAPACK/lapack/archive/refs/tags/v${LAPACK_VERSION}.tar.gz && \ + mkdir -p lapack && \ + tar xf lapack.tar.gz -C lapack --strip-components=1 && \ + cd lapack && \ cp INSTALL/make.inc.gfortran make.inc && \ make lapacklib blaslib && \ mkdir -p /usr/local/lapack/lib && \ cp *.a /usr/local/lapack/lib -# Compile libxc-4.3.4 -RUN wget -c -O libxc-4.3.4.tar.gz http://www.tddft.org/programs/libxc/down.php?file=4.3.4/libxc-4.3.4.tar.gz && \ - tar xf libxc-4.3.4.tar.gz && \ - cd libxc-4.3.4 && \ +ARG LIBXC_VERSION +RUN wget -c -O libxc.tar.gz https://gitlab.com/libxc/libxc/-/archive/4.3.4/libxc-4.3.4.tar.gz && \ + mkdir -p libxc && \ + tar xf libxc.tar.gz -C libxc --strip-components=1 && \ + cd libxc && \ autoreconf -i && \ ./configure --prefix=/usr/local/libxc && \ make && make install -WORKDIR / RUN rm -rf /build +WORKDIR / diff --git a/README.md b/README.md index 94c786d..ab913dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # build-machine image for building the images of pseudopotential generation tools +The image page is [here](https://hub.docker.com/r/pspgen/build-machine). + This is the base image for build the images of pseudopotential generation tool containers. In the image, it installs and compiles the following tools: @@ -8,4 +10,23 @@ In the image, it installs and compiles the following tools: - lapack and blas libraries ~3.10 - libxc ~= 4.3.4 -The reason that we use these version is that pseudo generation codes are written in old fashion and usually not compatible with the latest version of compilers and libraries. \ No newline at end of file +The reason that we use these version is that pseudo generation codes are written in old fashion and usually not compatible with the latest version of compilers and libraries. + +## How to build the image locally + +First, clone the repository: + +```bash +git clone https://github.com/pspgen/build-machine.git +``` + +Then, build the image: + +```bash +cd build-machine +docker buildx bake -f docker-bake.hcl -f build.json --load +``` + +You'll see the image `pspgen/build-machine:newly-baked` in your local docker images by running `docker images`. + +The versions of libraries and compilers are defined in the `build.json`. \ No newline at end of file diff --git a/build.json b/build.json new file mode 100644 index 0000000..b418694 --- /dev/null +++ b/build.json @@ -0,0 +1,16 @@ +{ + "variable": { + "BASE_IMAGE": { + "default": "ubuntu:focal" + }, + "LIBXC_VERSION": { + "default": "4.3.4" + }, + "GNU_COMPILER_VERSION": { + "default": "7" + }, + "LAPACK_VERSION": { + "default": "3.10.1" + } + } +} \ No newline at end of file diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 0000000..ab08b11 --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,46 @@ +group "default" { + targets = ["base"] +} + +variable "ORGANIZATION" { + default = "pspgen" +} + +variable "BASE_IMAGE" { +} + +variable "LIBXC_VERSION" { +} + +variable "GNU_COMPILER_VERSION" { +} + +variable "LAPACK_VERSION" { +} + +variable "REGISTRY" { +} + +function "tags" { + params = [image] + result = [ + "${REGISTRY}/${ORGANIZATION}/${image}", + ] +} + +target "base-meta" { + tags = tags("build-machine") +} + +target "base" { + inherits = ["base-meta"] + context = "." + contexts = { + base-image = "docker-image://${BASE_IMAGE}" + } + args = { + "LIBXC_VERSION" = "${LIBXC_VERSION}" + "GNU_COMPILER_VERSION" = "${GNU_COMPILER_VERSION}" + "LAPACK_VERSION" = "${LAPACK_VERSION}" + } +} \ No newline at end of file