diff --git a/.devcontainer/.gitignore b/.devcontainer/.gitignore new file mode 100644 index 0000000000..b2b871603f --- /dev/null +++ b/.devcontainer/.gitignore @@ -0,0 +1,31 @@ +* + +!/assets/ +!/assets/cradles/ +!/assets/cradles/cabal/ +!/assets/cradles/stack/ +!/assets/screenshots/ +!/bind-mounts/ +!/conf/ +!/conf/etc/ +!/conf/etc/stack/ +!/ghc-*/ +!/scripts/ +!/scripts/usr/ +!/scripts/usr/local/ +!/scripts/usr/local/bin/ + +!/assets/cradles/cabal/hie.yaml +!/assets/cradles/stack/hie.yaml +!/assets/screenshots/manageHLS.png +!/conf/etc/stack/config.yaml +!/ghc-*/devcontainer.json +!/scripts/usr/local/bin/*.sh + +!/devcontainer.json +!/GHC.Dockerfile +!/LICENSE +!/README.md + +!.gitignore +!.keep diff --git a/.devcontainer/GHC.Dockerfile b/.devcontainer/GHC.Dockerfile new file mode 100644 index 0000000000..3dbe9c627f --- /dev/null +++ b/.devcontainer/GHC.Dockerfile @@ -0,0 +1,116 @@ +ARG BUILD_ON_IMAGE=glcr.b-data.ch/ghc/ghc-musl +ARG GHC_VERSION=latest +ARG HLS_VERSION +ARG STACK_VERSION + +ARG HLS_GHC_VERSION=${HLS_VERSION:+$GHC_VERSION} +ARG HLS_SFX=/${HLS_GHC_VERSION:-all}/hls:${HLS_VERSION:-none} + +ARG STACK_VERSION_OVERRIDE=${STACK_VERSION:-none} + +FROM ${BUILD_ON_IMAGE}:${GHC_VERSION} as files + +RUN mkdir /files + +COPY conf /files +COPY scripts /files + +## Ensure file modes are correct +RUN find /files -type d -exec chmod 755 {} \; \ + && find /files -type f -exec chmod 644 {} \; \ + && find /files/usr/local/bin -type f -exec chmod 755 {} \; + +FROM glcr.b-data.ch/commercialhaskell/ssi:${STACK_VERSION_OVERRIDE} as ssi + +FROM ${BUILD_ON_IMAGE}${HLS_SFX} as hls + +FROM glcr.b-data.ch/ndmitchell/hlsi:latest as hlsi + +FROM docker.io/koalaman/shellcheck:stable as sci + +FROM ${BUILD_ON_IMAGE}:${GHC_VERSION} + +COPY --from=files /files / + +RUN sysArch="$(uname -m)" \ + ## Ensure that common CA certificates + ## and OpenSSL libraries are up to date + && apk upgrade --no-cache ca-certificates openssl-dev \ + ## Install yamllint + && apk add --no-cache yamllint \ + ## Install pip + && apk add --no-cache py3-pip \ + ## Install terminal multiplexers + && apk add --no-cache screen tmux \ + ## Install hadolint + && case "$sysArch" in \ + x86_64) tarArch="x86_64" ;; \ + aarch64) tarArch="arm64" ;; \ + *) echo "error: Architecture $sysArch unsupported"; exit 1 ;; \ + esac \ + && apiResponse="$(curl -sSL \ + https://api.github.com/repos/hadolint/hadolint/releases/latest)" \ + && downloadUrl="$(echo "$apiResponse" | grep -e \ + "browser_download_url.*Linux-$tarArch\"" | cut -d : -f 2,3 | tr -d \")" \ + && echo "$downloadUrl" | xargs curl -sSLo /usr/local/bin/hadolint \ + && chmod 755 /usr/local/bin/hadolint \ + ## Create folders in root directory + && mkdir -p /root/.local/bin \ + ## Create folders in skeleton directory + && mkdir -p /etc/skel/.local/bin + +## Update environment +ARG USE_ZSH_FOR_ROOT +ARG SET_LANG +ARG SET_TZ + +ENV TZ=${SET_TZ:-$TZ} \ + LANG=${SET_LANG:-$LANG} + + ## Change root's shell to ZSH +RUN if [ -n "$USE_ZSH_FOR_ROOT" ]; then \ + apk add --no-cache zsh shadow; \ + fix-chsh.sh; \ + chsh -s /bin/zsh; \ + fi \ + ## Update timezone if needed + && if [ "$TZ" != "" ]; then \ + apk add --no-cache tzdata; \ + echo "Setting TZ to $TZ"; \ + ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime \ + && echo "$TZ" > /etc/timezone; \ + fi \ + ## Add/Update locale if needed + && if [ "$LANG" != "C.UTF-8" ]; then \ + if [ -n "$LANG" ]; then \ + apk add --no-cache musl-locales musl-locales-lang; \ + fi; \ + sed -i "s/LANG=C.UTF-8/LANG=$LANG/" /etc/profile.d/*locale.sh; \ + sed -i "s/LANG:-C.UTF-8/LANG:-$LANG/" /etc/profile.d/*locale.sh; \ + sed -i "s/LC_COLLATE=C/LC_COLLATE=$LANG/" /etc/profile.d/*locale.sh; \ + sed -i "s/LC_COLLATE:-C/LC_COLLATE:-$LANG/" /etc/profile.d/*locale.sh; \ + fi + +## Copy binaries as late as possible to avoid cache busting +## Install Stack +COPY --from=ssi /usr/local /usr/local +## Install HLS +COPY --from=hls /usr/local /usr/local +## Install HLint +COPY --from=hlsi /usr/local /usr/local +## Install ShellCheck +COPY --from=sci --chown=root:root /bin/shellcheck /usr/local/bin + +ARG HLS_VERSION +ARG STACK_VERSION + +ARG STACK_VERSION_OVERRIDE=${STACK_VERSION} + +ENV HLS_VERSION=${HLS_VERSION} \ + STACK_VERSION=${STACK_VERSION_OVERRIDE:-$STACK_VERSION} + +RUN if [ "${GHC_VERSION%.*}" = "9.2" ]; then \ + if [ -f /usr/local/bin/stack ]; then \ + mv -f /usr/local/bin/stack /usr/bin/; \ + fi \ + fi diff --git a/.devcontainer/LICENSE b/.devcontainer/LICENSE new file mode 100644 index 0000000000..74e3bbd1bb --- /dev/null +++ b/.devcontainer/LICENSE @@ -0,0 +1,35 @@ +Copyright (c) 2023 Olivier Benz + +The code in this directory is not part of Stack (the software) and, with the +exceptions noted below, is distributed under the terms of the MIT License: + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +This directory also contains code with other copyrights. The affected files, +their copyrights and license statements are listed below. + +-------------------------------------------------------------------------------- +scripts/fix-chsh.sh +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the MIT License. See +https://github.com/devcontainers/features/blob/main/LICENSE for +license information. + +-------------------------------------------------------------------------------- diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 0000000000..d8704bfde8 --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,12 @@ +# Dev Containers + +See [haskellstack.org](https://docs.haskellstack.org): Stack's code (advanced) +\> Maintainers \> Dev Containers or +[../doc/maintainers/devcontainers.md](../doc/maintainers/devcontainers.md) +for more information. + +## License + +The code in this directory is not part of Stack (the software) and, with the +exceptions noted in [LICENSE](LICENSE), is distributed under the terms of the +MIT License. diff --git a/.devcontainer/assets/cradles/cabal/hie.yaml b/.devcontainer/assets/cradles/cabal/hie.yaml new file mode 100644 index 0000000000..cb6b06a9cb --- /dev/null +++ b/.devcontainer/assets/cradles/cabal/hie.yaml @@ -0,0 +1,19 @@ +cradle: + multi: + - path: "./Setup.hs" + config: + cradle: + direct: + arguments: [] + - path: "./" + config: + cradle: + cabal: + - path: "./src" + component: "lib:stack" + - path: "./app" + component: "stack:exe:stack" + - path: "./tests/integration" + component: "stack:exe:stack-integration-test" + - path: "./tests/unit" + component: "stack:test:stack-unit-test" diff --git a/.devcontainer/assets/cradles/stack/hie.yaml b/.devcontainer/assets/cradles/stack/hie.yaml new file mode 100644 index 0000000000..bcc8ccd9c4 --- /dev/null +++ b/.devcontainer/assets/cradles/stack/hie.yaml @@ -0,0 +1,19 @@ +cradle: + multi: + - path: "./Setup.hs" + config: + cradle: + direct: + arguments: [] + - path: "./" + config: + cradle: + stack: + - path: "./src" + component: "stack:lib" + - path: "./app" + component: "stack:exe:stack" + - path: "./tests/integration" + component: "stack:exe:stack-integration-test" + - path: "./tests/unit" + component: "stack:test:stack-unit-test" diff --git a/.devcontainer/bind-mounts/.keep b/.devcontainer/bind-mounts/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.devcontainer/conf/etc/stack/config.yaml b/.devcontainer/conf/etc/stack/config.yaml new file mode 100644 index 0000000000..37c999c3d6 --- /dev/null +++ b/.devcontainer/conf/etc/stack/config.yaml @@ -0,0 +1,4 @@ +# Use only the GHC available on the PATH +system-ghc: true +# Do not automatically install GHC when necessary +install-ghc: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..4db825f12d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,65 @@ +{ + "name": "Default", + "build": { + "dockerfile": "GHC.Dockerfile", + "args": { + "GHC_VERSION": "9.4.6", + "HLS_VERSION": "2.1.0.0", + "USE_ZSH_FOR_ROOT": "unset-to-use-ash", + "SET_LANG": "C.UTF-8", + "SET_TZ": "" + } + }, + + "onCreateCommand": "onCreateCommand.sh", + "postCreateCommand": "cabal update", + + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": true, + "upgradePackages": false, + "username": "vscode", + "userUid": "automatic", + "userGid": "automatic" + } + }, + + "customizations": { + "vscode": { + "extensions": [ + "eamodio.gitlens@11.7.0", + "exiasr.hadolint", + "GitHub.vscode-pull-request-github", + "haskell.haskell", + "mhutchie.git-graph", + "mutantdino.resourcemonitor", + "timonwong.shellcheck" + ], + "settings": { + "gitlens.showWelcomeOnInstall": false, + "gitlens.showWhatsNewAfterUpgrades": false, + "haskell.manageHLS": "PATH", + "resmon.show.battery": false, + "resmon.show.cpufreq": false + } + } + }, + + // Set 'remoteUser' to 'root' to connect as root instead. + "remoteUser": "vscode", + "mounts": [ + "source=stack-default-home-vscode,target=/home/vscode,type=volume" + // "source=${localWorkspaceFolder}/.devcontainer/bind-mounts/stack-default-home-vscode,target=/home/vscode,type=bind" + ], + + // "remoteUser": "root", + // "mounts": [ + // "source=stack-default-root,target=/root,type=volume" + // // "source=${localWorkspaceFolder}/.devcontainer/bind-mounts/stack-default-root,target=/root,type=bind" + // ], + + // Pip: Install packages to the user site + "remoteEnv": { + "PIP_USER": "1" + } +} diff --git a/.devcontainer/ghc-9.6.2/devcontainer.json b/.devcontainer/ghc-9.6.2/devcontainer.json new file mode 100644 index 0000000000..83d0f9fd30 --- /dev/null +++ b/.devcontainer/ghc-9.6.2/devcontainer.json @@ -0,0 +1,63 @@ +{ + "name": "GHC 9.6.2", + "build": { + "dockerfile": "../GHC.Dockerfile", + "context": "..", + "args": { + "GHC_VERSION": "9.6.2", + "USE_ZSH_FOR_ROOT": "unset-to-use-ash", + "SET_LANG": "C.UTF-8", + "SET_TZ": "" + } + }, + + "onCreateCommand": "onCreateCommand.sh", + "postCreateCommand": "cabal update", + + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "configureZshAsDefaultShell": true, + "upgradePackages": false, + "username": "vscode", + "userUid": "automatic", + "userGid": "automatic" + } + }, + + "customizations": { + "vscode": { + "extensions": [ + "eamodio.gitlens@11.7.0", + "exiasr.hadolint", + "GitHub.vscode-pull-request-github", + "mhutchie.git-graph", + "mutantdino.resourcemonitor", + "timonwong.shellcheck" + ], + "settings": { + "gitlens.showWelcomeOnInstall": false, + "gitlens.showWhatsNewAfterUpgrades": false, + "resmon.show.battery": false, + "resmon.show.cpufreq": false + } + } + }, + + // Set 'remoteUser' to 'root' to connect as root instead. + "remoteUser": "vscode", + "mounts": [ + "source=stack-ghc-9.6.2-home-vscode,target=/home/vscode,type=volume" + // "source=${localWorkspaceFolder}/.devcontainer/bind-mounts/stack-ghc-9.6.2-home-vscode,target=/home/vscode,type=bind" + ], + + // "remoteUser": "root", + // "mounts": [ + // "source=stack-ghc-9.6.2-root,target=/root,type=volume" + // // "source=${localWorkspaceFolder}/.devcontainer/bind-mounts/stack-ghc-9.6.2-root,target=/root,type=bind" + // ], + + // Pip: Install packages to the user site + "remoteEnv": { + "PIP_USER": "1" + } +} diff --git a/.devcontainer/scripts/usr/local/bin/fix-chsh.sh b/.devcontainer/scripts/usr/local/bin/fix-chsh.sh new file mode 100755 index 0000000000..84ab89f1cd --- /dev/null +++ b/.devcontainer/scripts/usr/local/bin/fix-chsh.sh @@ -0,0 +1,18 @@ +#!/bin/bash +#------------------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://github.com/devcontainers/features/blob/main/LICENSE for license information. +#------------------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/devcontainers/features/tree/main/src/common-utils +# Maintainer: The Dev Container spec maintainers + +set -e + +# Fixing chsh always asking for a password on alpine linux +# ref: https://askubuntu.com/questions/812420/chsh-always-asking-a-password-and-get-pam-authentication-failure. +if [ ! -f "/etc/pam.d/chsh" ] || ! grep -Eq '^auth(.*)pam_rootok\.so$' /etc/pam.d/chsh; then + echo "auth sufficient pam_rootok.so" >> /etc/pam.d/chsh +elif [[ -n "$(awk '/^auth(.*)pam_rootok\.so$/ && !/^auth[[:blank:]]+sufficient[[:blank:]]+pam_rootok\.so$/' /etc/pam.d/chsh)" ]]; then + awk '/^auth(.*)pam_rootok\.so$/ { $2 = "sufficient" } { print }' /etc/pam.d/chsh > /tmp/chsh.tmp && mv /tmp/chsh.tmp /etc/pam.d/chsh +fi diff --git a/.devcontainer/scripts/usr/local/bin/onCreateCommand.sh b/.devcontainer/scripts/usr/local/bin/onCreateCommand.sh new file mode 100755 index 0000000000..88d76094d0 --- /dev/null +++ b/.devcontainer/scripts/usr/local/bin/onCreateCommand.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Copyright (c) 2023 b-data GmbH. +# Distributed under the terms of the MIT License. + +set -e + +mkdir -p "$HOME/.cabal/bin" +mkdir -p "$HOME/.local/bin" + +# Copy Zsh-related files and folders from the untouched home directory +if [ "$(id -un)" == "root" ]; then + if [ ! -d /root/.oh-my-zsh ]; then + cp -R /home/*/.oh-my-zsh /root; + fi + if [ ! -f /root/.zshrc ]; then + cp /home/*/.zshrc /root; + fi +else + if [ ! -d "$HOME/.oh-my-zsh" ]; then + sudo cp -R /root/.oh-my-zsh "$HOME"; + sudo chown -R "$(id -u)":"$(id -g)" "$HOME/.oh-my-zsh"; + fi + if [ ! -f "$HOME/.zshrc" ]; then + sudo cp /root/.zshrc "$HOME"; + sudo chown "$(id -u)":"$(id -g)" "$HOME/.zshrc"; + fi +fi + +# Set PATH so it includes user's private bin if it exists +if ! grep -q "user's private bin" "$HOME/.zshrc"; then + echo -e "\n# set PATH so it includes user's private bin if it exists\nif [ -d \"\$HOME/bin\" ] && [[ \"\$PATH\" != *\"\$HOME/bin\"* ]] ; then\n PATH=\"\$HOME/bin:\$PATH\"\nfi" >> "$HOME/.zshrc"; + echo -e "\n# set PATH so it includes user's private bin if it exists\nif [ -d \"\$HOME/.local/bin\" ] && [[ \"\$PATH\" != *\"\$HOME/.local/bin\"* ]] ; then\n PATH=\"\$HOME/.local/bin:\$PATH\"\nfi" >> "$HOME/.zshrc"; +fi + +# Set PATH so it includes cabal's bin if it exists +if ! grep -q "cabal's bin" "$HOME/.zshrc"; then + echo -e "\n# set PATH so it includes cabal's bin if it exists\nif [ -d \"\$HOME/.cabal/bin\" ] && [[ \"\$PATH\" != *\"\$HOME/.cabal/bin\"* ]] ; then\n PATH=\"\$HOME/.cabal/bin:\$PATH\"\nfi" >> "$HOME/.zshrc"; +fi + +# Enable Oh My Zsh plugins +sed -i "s/plugins=(git)/plugins=(cabal git pip stack screen tmux vscode)/g" "$HOME/.zshrc" + +# Remove old .zcompdump files +rm -f "$HOME"/.zcompdump* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b1219df5f..a229375e42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -692,6 +692,22 @@ Stack can be built with Stack (which is recommended) or with Cabal (the tool). A cradle is not committed to Stack's repository because it imposes a choice of build tool. +## Dev Containers + +A [Development Container](https://containers.dev) (or Dev Container for short) +allows you to use a container as a full‑featured development environment. + +You can run Dev Containers locally/remotely (with VS Code) or create a +[Codespace](https://github.com/features/codespaces) for a branch in a +repository to develop online. + +Stack's default Dev Container is intended for use with its default +project‑level configuration (`stack.yaml`). But there are also Dev Containers +for the experimental project‑level configurations. + +See [Stack's code (advanced) > Maintainers > Dev Containers](../maintainers/devcontainers) +for more information. + ## Slack channel If you're making deep changes and real-time communication with the Stack team diff --git a/ChangeLog.md b/ChangeLog.md index 06b9ad5e69..6cf292c64f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,8 @@ Release notes: * Further to the release notes for Stack 2.3.1, the `-static` suffix has been removed from the statically linked Linux/x86_64 binaries. * The binaries for Linux/Aarch64 are now statically linked. +* Dev Containers are now available for this repository. See + [#6228](https://github.com/commercialhaskell/stack/pull/6228). **Changes since v2.11.1:** diff --git a/doc/img/manageHLS.png b/doc/img/manageHLS.png new file mode 100644 index 0000000000..d6589d593a Binary files /dev/null and b/doc/img/manageHLS.png differ diff --git a/doc/maintainers/devcontainers.md b/doc/maintainers/devcontainers.md new file mode 100644 index 0000000000..675433bf58 --- /dev/null +++ b/doc/maintainers/devcontainers.md @@ -0,0 +1,146 @@ +
+ +# Dev Containers + +Stack's Dev Containers provide the following tools: + +1. The [**Haskell Toolchain**](https://www.haskell.org/ghcup/install/#supported-tools): + (installed at `/usr/local/bin`[^1]) + * [GHC](https://www.haskell.org/ghc) + * [Cabal](https://cabal.readthedocs.io) + * Stack + * [HLS](https://haskell-language-server.readthedocs.io) (Default Dev Container only) +1. Additionally: + * [Git](https://git-scm.com) + * [HLint](https://hackage.haskell.org/package/hlint) + * [yamllint](https://yamllint.readthedocs.io) + * [ShellCheck](https://www.shellcheck.net) + * [hadolint](https://github.com/hadolint/hadolint) + +[^1]: `PATH=$HOME/.cabal/bin:$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` + +!!! info + + Executables installed with Cabal (at `$HOME/.cabal/bin`) or Stack/Pip (at + `$HOME/.local/bin`) take precedence over the same executable installed at + `/usr/local/sbin`, `/usr/local/bin`, etc. + +[VS Code](https://code.visualstudio.com) is used as IDE, with the following +extensions pre‑installed: + +* [Haskell](https://marketplace.visualstudio.com/items?itemName=haskell.haskell) + (Default Dev Container only) +* [GitHub Pull Requests and Issues](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) +* [GitLens — Git supercharged](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) + * Pinned to version 11.7.0 due to unsolicited AI content + in recent versions +* [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) +* [ShellCheck](https://marketplace.visualstudio.com/items?itemName=timonwong.shellcheck) +* [hadolint](https://marketplace.visualstudio.com/items?itemName=exiasr.hadolint) +* [Resource Monitor](https://marketplace.visualstudio.com/items?itemName=mutantdino.resourcemonitor) + +## Parent images + +These Dev Containers are derived from the same docker images used to build the +*statically linked* Linux amd64 and arm64 binary releases of Stack itself. + +Those multi‑arch (`linux/amd64`, `linux/arm64/v8`) *ghc‑musl* images are based +on Alpine Linux (i.e. [musl libc](https://musl.libc.org) and +[BusyBox](https://www.busybox.net)) and contain *unofficial* builds of GHC. + +To make sure only the GHC available in the Dev Containers is used, flags +