diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index be0c73a..64fcc74 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,89 +1,12 @@ -FROM mcr.microsoft.com/devcontainers/universal -# FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 +FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 -RUN df -h +RUN apt-get update && apt-get -y install m4 build-essential autoconf opam libunwind-dev pkg-config -RUN rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL \ - /usr/local/php /usr/local/py-utils \ - /usr/local/python /usr/local/rvm /usr/local/sdkman /usr/local/share/nvm \ - /opt/conda /opt/dotnet \ - /home/codespace/.local/lib/python3.12 +USER vscode -RUN find / -type f -exec du -h {} + | sort -rh | head -100 || true - -RUN du -sh /usr/* -RUN du -sh /usr/local/* -RUN du -sh /opt/* -RUN ls -l / - -RUN df -h - -RUN cd /root && \ - curl -L -o autoconf.tar.gz https://ftp.gnu.org/gnu/autoconf/autoconf-2.71.tar.gz && \ - tar fxz autoconf.tar.gz && \ - cd autoconf-2.71 && \ - ./configure --prefix=/usr && \ - make && \ - make install && \ - cd .. && \ - rm -rf autoconf-2.71 autoconf.tar.gz - -ARG USERNAME=codespace -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -RUN curl -L -o /usr/bin/opam https://github.com/ocaml/opam/releases/download/2.3.0/opam-2.3.0-i686-linux && \ - chmod +x /usr/bin/opam && \ - apt-get update && \ - apt-get remove -y swig3.0 unixodbc-dev libpq-dev \ - default-libmysqlclient-dev libgdiplus jq python3-pip sqlite3 \ - libsqlite3-dev tk-dev uuid-dev clang lldb llvm gdb vim-doc xtail \ - libsecret-1-dev && \ - apt-get install -y imagemagick && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -RUN df -h - -USER ${USERNAME} - -# CR mshinwell/avsm: use a hash for --repos, but will need to restore the -# URL afterwards for any user opam update commands. RUN opam init -a --disable-sandboxing --yes --bare && \ opam update -a && \ opam switch create 5.2.0+ox --yes \ - --repos "ox=git+https://github.com/oxcaml/opam-repository.git,default" && \ - eval $(opam env --switch 5.2.0+ox) - -RUN df -h - -RUN eval $(opam env --switch 5.2.0+ox) && \ - opam install -j 1 --yes ocamlformat && \ - opam clean - -RUN df -h - -RUN eval $(opam env --switch 5.2.0+ox) && \ - opam install -j 1 --yes merlin && \ - opam clean - -RUN df -h - -RUN eval $(opam env --switch 5.2.0+ox) && \ - opam install -j 1 --yes async && \ - opam clean - -RUN df -h - -RUN eval $(opam env --switch 5.2.0+ox) && \ - opam install -j 1 --yes ocaml-lsp-server && \ - opam clean - -RUN df -h - -RUN eval $(opam env --switch 5.2.0+ox) && \ - opam install --yes utop && \ - opam install --yes parallel && \ - opam install --yes core_unix + --repos "ox=git+https://github.com/oxcaml/opam-repository.git,default" -USER root +RUN opam install -y ocamlformat merlin ocaml-lsp-server odoc utop core parallel && opam clean -a diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 802b85a..a646777 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,7 @@ { - "build": { - "dockerfile": "Dockerfile" - }, - //"image": "ghcr.io/oxcaml/playground@sha256:6d10fa7419881cec63d934956de1d662dbbf3d80bde47cc14a132506022db58c", + "image": "ghcr.io/avsm/playground:main", "name": "OxCaml Playground", + "userEnvProbe": "loginInteractiveShell", "customizations": { "vscode": { "extensions": [ @@ -12,8 +10,5 @@ "eamodio.gitlens" ] } - }, - "hostRequirements": { - "storage": "64gb" } } diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index e721992..0000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Docker - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - #schedule: - # - cron: '30 23 * * *' - push: - # branches: [ "main" ] - # Publish semver tags as releases. - tags: [ 'v*.*.*' ] - # pull_request: - # branches: [ "main" ] - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - - steps: - - run: df -h - - - name: Increase free disk space - run: | - sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL - sudo docker image prune --all --force - sudo docker builder prune -a - - - run: df -h - - - name: Checkout repository - uses: actions/checkout@v4 - - # Install the cosign tool except on PR - # https://github.com/sigstore/cosign-installer - - name: Install cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 - with: - cosign-release: 'v2.2.4' - - # Set up BuildKit Docker container builder to be able to build - # multi-platform images and export cache - # https://github.com/docker/setup-buildx-action - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - file: .devcontainer/Dockerfile - - # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. - # https://github.com/sigstore/cosign - - name: Sign the published Docker image - if: ${{ github.event_name != 'pull_request' }} - env: - # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - TAGS: ${{ steps.meta.outputs.tags }} - DIGEST: ${{ steps.build-and-push.outputs.digest }} - # This step uses the identity token to provision an ephemeral certificate - # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} diff --git a/.github/workflows/multi-build.yaml b/.github/workflows/multi-build.yaml new file mode 100644 index 0000000..66ecb54 --- /dev/null +++ b/.github/workflows/multi-build.yaml @@ -0,0 +1,279 @@ +# This workflow builds a multi-arch Docker image using GitHub Actions and separated Github Runners with native support for ARM64 and AMD64 architectures, without using QEMU emulation. +# It uses Docker Buildx to build and push the image to GitHub Container Registry (GHCR). +name: Build multi arch Docker Image with separate Github Runners + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'Dockerfile' + - '.devcontainer/Dockerfile' + - '.github/workflows/multi-build.yaml' +env: + # The name of the Docker image to be built and pushed to GHCR + # The image name is derived from the GitHub repository name and the GitHub Container Registry (GHCR) URL. + # The image name will be in the format: ghcr.io// + GHCR_IMAGE: ghcr.io/${{ github.repository }} + +permissions: + # Global permissions for the workflow, which can be overridden at the job level + contents: read + +concurrency: + # This concurrency group ensures that only one job in the group runs at a time. + # If a new job is triggered, the previous one will be canceled. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # The build job builds the Docker image for each platform specified in the matrix. + build: + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + # The matrix includes two platforms: linux/amd64 and linux/arm64. + # The build job will run for each platform in the matrix. + + permissions: + # Permissions for the build job, which can be overridden at the step level + # The permissions are set to allow the job to write to the GitHub Container Registry (GHCR) and read from the repository. + attestations: write + actions: read + checks: write + contents: write + deployments: none + id-token: write + issues: read + discussions: read + packages: write + pages: none + pull-requests: read + repository-projects: read + security-events: read + statuses: read + + runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} + # The job runs on different runners based on the platform. + # For linux/amd64, it runs on the latest Ubuntu runner. + # For linux/arm64, it runs on an Ubuntu 24.04 ARM runner. + # The runner is selected based on the platform specified in the matrix. + + name: Build Docker image for ${{ matrix.platform }} + + steps: + + - name: Reduce ASLR entropy + run: sudo sysctl -w vm.mmap_rnd_bits=28 + + - name: Prepare environment for current platform + # This step sets up the environment for the current platform being built. + # It replaces the '/' character in the platform name with '-' and sets it as an environment variable. + # This is useful for naming artifacts and other resources that cannot contain '/'. + # The environment variable PLATFORMS_PAIR will be used later in the workflow. + id: prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + # This step checks out the code from the repository. + # It uses the actions/checkout action to clone the repository into the runner's workspace. + + - name: Docker meta default + # This step generates metadata for the Docker image. + # It uses the docker/metadata-action to create metadata based on the repository information. + # The metadata includes information such as the image name, tags, and labels. + # The metadata will be used later in the workflow to build and push the Docker image. + id: meta + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + with: + images: ${{ env.GHCR_IMAGE }} + + - name: Set up Docker Context for Buildx + # This step sets up a Docker context for Buildx. + # It creates a new context named "builders" that will be used for building the Docker image. + # The context allows Buildx to use the Docker daemon for building images. + id: buildx-context + run: | + docker context create builders + + - name: Set up Docker Buildx + # This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit. + # It uses the docker/setup-buildx-action to configure Buildx with the specified context and platforms. + # The platforms are specified in the matrix and will be used for building the Docker image. + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + with: + endpoint: builders + platforms: ${{ matrix.platform }} + + - name: Login to GitHub Container Registry + # This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action. + # It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication. + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + + - name: Build and push by digest + # This step builds and pushes the Docker image using Buildx. + # It uses the docker/build-push-action to build the image with the specified context and platforms. + # The image is built with the labels and annotations generated in the previous steps. + # The outputs are configured to push the image by digest, which allows for better caching and versioning. + # The cache-from and cache-to options are used to enable caching for the build process. + # The cache is stored in GitHub Actions cache and is scoped to the repository, branch, and platform. + id: build + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + env: + DOCKER_BUILDKIT: 1 + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true + cache-from: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }} + cache-to: type=gha,scope=${{ github.repository }}-${{ github.ref_name }}-${{ matrix.platform }} + + + - name: Export digest + # This step exports the digest of the built image to a file. + # It creates a directory in /tmp/digests and saves the digest of the image to a file. + # The digest is obtained from the output of the build step. + # The digest is used to uniquely identify the built image and can be used for further processing or verification. + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + # This step uploads the digest file to the GitHub Actions artifact storage. + # It uses the actions/upload-artifact action to upload the file created in the previous step. + # The artifact is named digests-${{ env.PLATFORM_PAIR }}, where PLATFORM_PAIR is the platform name with '/' replaced by '-'. + # The artifact is retained for 1 day, and if no files are found, it will throw an error. + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + + merge: + # This job merges the Docker manifests for the different platforms built in the previous job. + name: Merge Docker manifests + runs-on: ubuntu-latest + permissions: + attestations: write + actions: read + checks: read + contents: read + deployments: none + id-token: write + issues: read + discussions: read + packages: write + pages: none + pull-requests: read + repository-projects: read + security-events: read + statuses: read + + needs: + - build + # This job depends on the build job to complete before it starts. + # It ensures that the Docker images for all platforms are built before merging the manifests. + steps: + - name: Download digests + # This step downloads the digest files uploaded in the build job. + # It uses the actions/download-artifact action to download the artifacts with the pattern digests-*. + # The downloaded files are merged into the /tmp/digests directory. + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + + - name: Docker meta + # This step generates metadata for the Docker image. + # It uses the docker/metadata-action to create metadata based on the repository information. + # The metadata includes information such as the image name, tags, and labels. + id: meta + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + with: + images: ${{ env.GHCR_IMAGE }} + annotations: | + type=org.opencontainers.image.description,value=${{ github.event.repository.description || 'No description provided' }} + tags: | + type=raw,value=main,enable=${{ github.ref_name == 'main' }} + type=raw,value=latest,enable=${{ github.ref_name == 'main' }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + # This step sets up Docker Buildx, which is a Docker CLI plugin for extended build capabilities with BuildKit. + with: + driver-opts: | + network=host + + - name: Login to GitHub Container Registry + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + # This step logs in to the GitHub Container Registry (GHCR) using the docker/login-action. + # It uses the GitHub actor's username and the GITHUB_TOKEN secret for authentication. + # The login is necessary to push the merged manifest list to GHCR. + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get execution timestamp with RFC3339 format + # This step gets the current execution timestamp in RFC3339 format. + # It uses the date command to get the current UTC time and formats it as a string. + # The timestamp is used for annotating the Docker manifest list. + id: timestamp + run: | + echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT + + - name: Create manifest list and pushs + # This step creates a manifest list for the Docker images built for different platforms. + # It uses the docker buildx imagetools create command to create the manifest list. + # The manifest list is annotated with metadata such as description, creation timestamp, and source URL. + # The annotations are obtained from the metadata generated in the previous steps. + # The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags. + working-directory: /tmp/digests + id: manifest-annotate + continue-on-error: true + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \ + --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \ + --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \ + --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + + - name: Create manifest list and push without annotations + # This step creates a manifest list for the Docker images built for different platforms. + # It uses the docker buildx imagetools create command to create the manifest list. + # The manifest list is created without annotations if the previous step fails. + # The manifest list is pushed to the GitHub Container Registry (GHCR) with the specified tags. + if: steps.manifest-annotate.outcome == 'failure' + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + + - name: Inspect image + # This step inspects the created manifest list to verify its contents. + # It uses the docker buildx imagetools inspect command to display information about the manifest list. + # The inspection output will show the platforms and tags associated with the manifest list. + id: inspect + run: | + docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}' diff --git a/Dockerfile b/Dockerfile new file mode 120000 index 0000000..5b7e9cd --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +.devcontainer/Dockerfile \ No newline at end of file diff --git a/README.md b/README.md index 4983184..e35a15c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # A playground for OxCaml -**(Disclaimer: currently in alpha)** +To make a playground, press the green "Code" button, then select "+" next to +"Codespaces". A new Codespace will open. It currently takes maybe 20 or 30 +minutes to initialize; please be patient. We'll work on improving this startup +time shortly. You can click the link in the status popup in the bottom-right +of the window to see current progress (although there are no spinners). -To make a playground, press the green "Code" button, then select "+" next to "Codespaces". A new Codespace will open. It currently takes maybe 20 or 30 minutes to initialize; please be patient. We'll work on improving this startup time shortly. -You can click the link in the status popup in the bottom-right of the window to see current progress (although there are no spinners). - -Once initialized you should have a full OPAM environment with the OxCaml compiler and dune on the path. VSCode will have the OCaml Platform plugin together with the LSP server and merlin, the editor assistant. +Once initialized you should have a full opam environment with the OxCaml +compiler and dune on the path. VSCode will have the OCaml Platform plugin +together with the LSP server and merlin, the editor assistant. ## Building your first OxCaml project