From 71405b18a5ba21f5ed9d55d2c57350de74e9b659 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 21 Apr 2020 07:05:16 +0300 Subject: [PATCH] Static Linux builds with Alpine --- .azure/azure-linux-template.yml | 38 +++++++++++++++----- .azure/azure-nightly-template-linux.yml | 4 +-- etc/scripts/release.hs | 47 ++++++++++++++----------- package.yaml | 4 +++ stack.cabal | 4 ++- stack.yaml | 4 ++- 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/.azure/azure-linux-template.yml b/.azure/azure-linux-template.yml index 7828327ed4..928ba2561f 100644 --- a/.azure/azure-linux-template.yml +++ b/.azure/azure-linux-template.yml @@ -10,17 +10,27 @@ jobs: GHC 8.4: BUILD: stack STACK_YAML: stack-ghc-84.yaml + EXTRA_SUFFIX: "" GHC 8.6: BUILD: stack STACK_YAML: stack-ghc-86.yaml + EXTRA_SUFFIX: "" GHC 8.8: BUILD: stack STACK_YAML: stack-ghc-88.yaml + EXTRA_SUFFIX: "" + Alpine: + BUILD: stack + STACK_YAML: stack.yaml + EXTRA_SUFFIX: "alpine" + STACK_ARGS: --docker --system-ghc --no-install-ghc --flag stack:static style: BUILD: style + EXTRA_SUFFIX: "" pedantic: BUILD: pedantic STACK_YAML: stack.yaml + EXTRA_SUFFIX: "" steps: - script: | export STACK_ROOT="$(Build.SourcesDirectory)"/.stack-root; @@ -28,8 +38,8 @@ jobs: curl -f -L "https://github.com/fpco/cache-s3/releases/download/${CACHE_S3_VERSION}/cache-s3-${CACHE_S3_VERSION}-${OS_NAME}-x86_64.tar.gz" -o ~/.local/bin/cache-s3.tar.gz tar xzf ~/.local/bin/cache-s3.tar.gz -C ~/.local/bin export PATH=$HOME/.local/bin:$PATH; - cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}" restore stack --base-branch="${BASE_BRANCH}" - cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}" restore stack work --base-branch="${BASE_BRANCH}" + cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}${EXTRA_SUFFIX}" restore stack --base-branch="${BASE_BRANCH}" + cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}${EXTRA_SUFFIX}" restore stack work --base-branch="${BASE_BRANCH}" etc/scripts/ci-setup.sh case "$BUILD" in style) @@ -38,11 +48,9 @@ jobs: ;; *) export PATH=$HOME/.local/bin:$PATH - stack --install-ghc $ARGS test --bench --only-dependencies + stack test $STACK_ARGS --bench --only-dependencies ;; esac - GHC_OPTIONS="-Werror" - if [ "$GHCVER" = "8.2.1" ]; then GHC_OPTIONS="$GHC_OPTIONS -Wno-missing-home-modules"; fi set -ex case "$BUILD" in style) @@ -51,7 +59,21 @@ jobs: hlint test/ --cpp-simple ;; stack) - stack test --haddock --no-haddock-deps --ghc-options="$GHC_OPTIONS" + stack test $STACK_ARGS --haddock --no-haddock-deps --ghc-options="-Werror" --copy-bins --local-bin-path bin + + # Get output about whether the exe is dynamically linked + if [[ "$EXTRA_SUFFIX" == "alpine" ]] + then + # ldd returns exit code 1 if it's static, so failure is success + (ldd ./bin/stack && exit 1) || true + else + ldd ./bin/stack + fi + + # Make sure we can run the executable on this OS + # Important to make sure the Alpine exe is properly static + ./bin/stack --version + ;; pedantic) stack --system-ghc build --pedantic @@ -94,9 +116,9 @@ jobs: export AWS_SECRET_ACCESS_KEY="$(AWS_SECRET_ACCESS_KEY)"; export STACK_ROOT="$(Build.SourcesDirectory)"/.stack-root; if [ "$(Build.SourceBranchName)" = "${BASE_BRANCH}" ]; then - cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}" save stack; + cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}${EXTRA_SUFFIX}" save stack; fi; - cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}" save stack work + cache-s3 --prefix="${CACHE_S3_PREFIX}" --git-branch="$(Build.SourceBranchName)" --suffix="${OS_NAME}${EXTRA_SUFFIX}" save stack work condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI')) env: OS_NAME: ${{ parameters.os }} diff --git a/.azure/azure-nightly-template-linux.yml b/.azure/azure-nightly-template-linux.yml index cf663bc322..c53519f86b 100644 --- a/.azure/azure-nightly-template-linux.yml +++ b/.azure/azure-nightly-template-linux.yml @@ -40,7 +40,7 @@ jobs: nix-channel --update # Get GHC 8.2.2 export STACK_ROOT="$(Build.SourcesDirectory)"/.stack-root; export PATH=$HOME/.local/bin:$PATH; - stack etc/scripts/release.hs check + stack etc/scripts/release.hs check --alpine set +ex displayName: Integration Test @@ -49,7 +49,7 @@ jobs: export STACK_ROOT="$(Build.SourcesDirectory)"/.stack-root; export PATH=$HOME/.local/bin:$PATH; - stack etc/scripts/release.hs build + stack etc/scripts/release.hs build --alpine cp _release/stack-* $(Build.ArtifactStagingDirectory) set +ex diff --git a/etc/scripts/release.hs b/etc/scripts/release.hs index 4bf2d8496e..5a89589545 100644 --- a/etc/scripts/release.hs +++ b/etc/scripts/release.hs @@ -139,8 +139,12 @@ options = "Label to give the uploaded release asset" , Option "" [noTestHaddocksOptName] (NoArg $ Right $ \g -> g{gTestHaddocks = False}) "Disable testing building haddocks." - , Option "" [staticOptName] (NoArg $ Right $ \g -> g{gBuildArgs = gBuildArgs g ++ ["--flag=stack:static"]}) - "Build a static binary." + , Option "" [alpineOptName] + (NoArg $ Right $ \g -> + g{gBuildArgs = + gBuildArgs g ++ + ["--flag=stack:static", "--docker", "--system-ghc", "--no-install-ghc"]}) + "Build a static binary using Alpine Docker image." , Option "" [buildArgsOptName] (ReqArg (\v -> Right $ \g -> g{gBuildArgs = gBuildArgs g ++ words v}) @@ -196,18 +200,19 @@ rules global@Global{..} args = do Stdout dirty <- cmd "git status --porcelain" when (not gAllowDirty && not (null (trim dirty))) $ error ("Working tree is dirty. Use --" ++ allowDirtyOptName ++ " option to continue anyway.") - withTempDir $ \tmpDir -> do - let cmd0 c = cmd [gProjectRoot releaseBinDir binaryName stackExeFileName] - (stackArgs global) - ["--local-bin-path=" ++ tmpDir] - c - () <- cmd0 "install" gBuildArgs integrationTestFlagArgs $ concat $ concat - [["--pedantic --no-haddock-deps "] - ,[" --haddock" | gTestHaddocks] - ,[" stack"]] - let cmd' c = cmd (AddPath [tmpDir] []) stackProgName (stackArgs global) c - () <- cmd' "test" gBuildArgs integrationTestFlagArgs "--pedantic --exec stack-integration-test stack" - return () + () <- cmd + [gProjectRoot releaseBinDir binaryName stackExeFileName] + (stackArgs global) + ["build"] + gBuildArgs + integrationTestFlagArgs + ["--pedantic", "--no-haddock-deps", "--test"] + ["--haddock" | gTestHaddocks] + ["stack"] + () <- cmd + [gProjectRoot releaseBinDir binaryName stackExeFileName] + ["exec"] + [gProjectRoot releaseBinDir binaryName "stack-integration-test"] copyFileChanged (releaseBinDir binaryName stackExeFileName) out unless gUploadOnly $ releaseDir binaryPkgZipFileName %> \out -> do @@ -242,7 +247,7 @@ rules global@Global{..} args = do unless gUploadOnly $ releaseDir binaryExeFileName %> \out -> do need [releaseBinDir binaryName stackExeFileName] (Stdout versionOut) <- cmd (releaseBinDir binaryName stackExeFileName) "--version" - -- () <- cmd "git diff" + () <- cmd "git diff" when (not gAllowDirty && "dirty" `isInfixOf` lower versionOut) $ error ("Refusing continue because 'stack --version' reports dirty. Use --" ++ allowDirtyOptName ++ " option to continue anyway.") @@ -265,8 +270,8 @@ rules global@Global{..} args = do ,out]) (removeFile out) Linux -> - cmd "strip -p --strip-unneeded --remove-section=.comment -o" - [out, releaseBinDir binaryName stackExeFileName] + -- Using Ubuntu's strip to strip an Alpine exe doesn't work, so just copy + liftIO $ copyFile (releaseBinDir binaryName stackExeFileName) out _ -> cmd "strip -o" [out, releaseBinDir binaryName stackExeFileName] @@ -534,9 +539,9 @@ noTestHaddocksOptName = "no-test-haddocks" buildArgsOptName :: String buildArgsOptName = "build-args" --- | @--static@ command-line option name. -staticOptName :: String -staticOptName = "static" +-- | @--alpine@ command-line option name. +alpineOptName :: String +alpineOptName = "alpine" -- | @--certificate-name@ command-line option name. certificateNameOptName :: String @@ -548,7 +553,7 @@ uploadOnlyOptName = "upload-only" -- | Arguments to pass to all 'stack' invocations. stackArgs :: Global -> [String] -stackArgs Global{..} = ["--install-ghc", "--arch=" ++ display gArch, "--interleaved-output"] +stackArgs Global{..} = ["--arch=" ++ display gArch, "--interleaved-output"] -- | Name of the 'stack' program. stackProgName :: FilePath diff --git a/package.yaml b/package.yaml index fa497eaaa9..679c55ef85 100644 --- a/package.yaml +++ b/package.yaml @@ -302,6 +302,10 @@ executables: when: - condition: ! '!(flag(integration-tests))' buildable: false + - condition: flag(static) + ld-options: + - -static + - -pthread tests: stack-test: main: Spec.hs diff --git a/stack.cabal b/stack.cabal index b954ddb6d1..83fcc8df80 100644 --- a/stack.cabal +++ b/stack.cabal @@ -4,7 +4,7 @@ cabal-version: 2.0 -- -- see: https://github.com/sol/hpack -- --- hash: f003cdea0be4961a87f3e62f3f9e5fee3e29924f746a6154a15ac18985deb871 +-- hash: 04f6dd3ff3b1b0f7db0a0ad6192866ad73926008fd5df9215d6abae839da8874 name: stack version: 2.3.0.2 @@ -553,6 +553,8 @@ executable stack-integration-test unix if !(flag(integration-tests)) buildable: False + if flag(static) + ld-options: -static -pthread default-language: Haskell2010 test-suite stack-test diff --git a/stack.yaml b/stack.yaml index e0669a57fe..86c9a5abbf 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,7 +5,9 @@ packages: docker: enable: false - repo: fpco/stack-build:lts-14.27 + + #image: fpco/alpine-haskell-stack:8.6.5 + image: fpco/alpine-haskell-stack@sha256:49e7e15f3b1d3f882ba5bb701463b1d508fbf40e5aafce6ea31acd210da570ba nix: # --nix on the command-line to enable.