Skip to content

Commit 44025d7

Browse files
committed
Re #6531 Create release-linux-aarch64.hs for use on macOS/AArch64
1 parent 2373948 commit 44025d7

File tree

1 file changed

+288
-0
lines changed

1 file changed

+288
-0
lines changed

etc/scripts/release-linux-aarch64.hs

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
{- stack script
2+
--resolver lts-22.7
3+
--ghc-options -Wall
4+
-}
5+
6+
-- As no packages are specified in the `stack script` command in the Stack
7+
-- interpreter options comment, Stack deduces the required packages from the
8+
-- module imports, being: Cabal, base, bytestring, directory, extra, process,
9+
-- shake, tar, zip-archive and zlib. These are either GHC boot packages or in
10+
-- the snapshot. Stackage LTS Haskell 22.0 does not include boot packages
11+
-- directly. As GHC 9.6.4 boot packages Cabal and Cabal-syntax expose modules
12+
-- with the same names, the language extension PackageImports is required.
13+
14+
-- EXPERIMENTAL
15+
16+
-- This corresponds to release.hs but is intended to be run only on
17+
-- macOS/AArch64 in order to create a statically-linked Linux/AArch64 version of
18+
-- Stack:
19+
--
20+
-- Install pre-requisites:
21+
--
22+
-- > brew install docker
23+
-- > brew install colima
24+
--
25+
-- Start colima and run script:
26+
--
27+
-- > colima start
28+
-- > release-linux-aarch64.hs build --alpine --build-args --docker-stack-exe=image
29+
--
30+
-- Could be incorporated into release.hs, in due course.
31+
32+
{-# LANGUAGE OverloadedRecordDot #-}
33+
{-# LANGUAGE PackageImports #-}
34+
{-# LANGUAGE PatternSynonyms #-}
35+
36+
import qualified Codec.Archive.Tar as Tar
37+
import qualified Codec.Archive.Tar.Entry as TarEntry
38+
import qualified Codec.Compression.GZip as GZip
39+
import Control.Exception ( tryJust )
40+
import Control.Monad ( guard )
41+
import qualified Data.ByteString.Lazy.Char8 as L8
42+
import Data.List.Extra ( stripPrefix )
43+
import Development.Shake
44+
( Action, Change (..), pattern Chatty, Rules
45+
, ShakeOptions (..), (%>), actionOnException, alwaysRerun
46+
, cmd, copyFileChanged, getDirectoryFiles, liftIO, need
47+
, phony, shakeArgsWith, shakeOptions, want
48+
)
49+
import Development.Shake.FilePath
50+
( (<.>), (</>), exe, takeDirectory, toStandard )
51+
import "Cabal" Distribution.PackageDescription
52+
( PackageDescription (..), packageDescription, pkgVersion
53+
)
54+
import Distribution.Simple.PackageDescription
55+
( readGenericPackageDescription )
56+
import "Cabal" Distribution.System
57+
( Arch, OS (..), Platform (..), buildPlatform )
58+
import "Cabal" Distribution.Text ( display )
59+
import Distribution.Verbosity ( silent )
60+
import System.Console.GetOpt ( ArgDescr (..), OptDescr (..) )
61+
import System.Directory ( removeFile )
62+
import System.IO.Error ( isDoesNotExistError )
63+
import System.Process ( readProcess )
64+
65+
-- | Entrypoint.
66+
main :: IO ()
67+
main = shakeArgsWith
68+
shakeOptions { shakeFiles = releaseDir
69+
, shakeVerbosity = Chatty
70+
, shakeChange = ChangeModtimeAndDigestInput
71+
}
72+
options $
73+
\flags args -> do
74+
-- build the default value of type Global, with predefined constants
75+
76+
-- 'stack build --dry-run' just ensures that 'stack.cabal' is generated from
77+
-- 'package.yaml'.
78+
_ <- readProcess "stack" ["build", "--dry-run"] ""
79+
gStackPackageDescription <-
80+
packageDescription <$> readGenericPackageDescription silent "stack.cabal"
81+
82+
let Platform arch _ = buildPlatform
83+
gArch = arch
84+
gBuildArgs = ["--flag", "stack:-developer-mode"]
85+
global = foldl
86+
(flip id)
87+
Global
88+
{ gStackPackageDescription
89+
, gArch
90+
, gBuildArgs
91+
}
92+
flags
93+
94+
pure $ Just $ rules global args
95+
96+
-- | Additional command-line options.
97+
options :: [OptDescr (Either String (Global -> Global))]
98+
options =
99+
[ Option "" [alpineOptName]
100+
( NoArg $ Right $ \g ->
101+
g { gBuildArgs =
102+
gBuildArgs g
103+
++ [ "--flag=stack:static"
104+
, "--docker"
105+
, "--system-ghc"
106+
, "--no-install-ghc"
107+
]
108+
}
109+
)
110+
"Build a statically linked binary using an Alpine Docker image."
111+
, Option "" [buildArgsOptName]
112+
( ReqArg
113+
(\v -> Right $ \g -> g{gBuildArgs = gBuildArgs g ++ words v})
114+
"\"ARG1 ARG2 ...\""
115+
)
116+
"Additional arguments to pass to 'stack build'."
117+
]
118+
119+
-- | Shake rules.
120+
rules :: Global -> [String] -> Rules ()
121+
rules global args = do
122+
case args of
123+
[] -> error "No wanted target(s) specified."
124+
_ -> want args
125+
126+
phony buildPhony $
127+
mapM_ (\f -> need [releaseDir </> f]) binaryPkgFileNames
128+
129+
releaseDir </> binaryPkgTarGzFileName %> \out -> do
130+
stageFiles <- getBinaryPkgStageFiles
131+
writeTarGz id out releaseStageDir stageFiles
132+
133+
releaseStageDir </> binaryName </> stackExeFileName %> \out -> do
134+
copyFileChanged (releaseDir </> binaryExeFileName) out
135+
136+
releaseStageDir </> (binaryName ++ "//*") %> \out -> do
137+
copyFileChanged
138+
(dropDirectoryPrefix (releaseStageDir </> binaryName) out)
139+
out
140+
141+
releaseDir </> binaryExeFileName %> \out -> do
142+
need [releaseBinDir </> binaryName </> stackExeFileName]
143+
case platformOS of
144+
OSX ->
145+
cmd "strip -o"
146+
[out, releaseBinDir </> binaryName </> stackExeFileName]
147+
_ -> undefined
148+
149+
releaseBinDir </> binaryName </> stackExeFileName %> \out -> do
150+
alwaysRerun
151+
actionOnException
152+
( cmd stackProgName
153+
(stackArgs global)
154+
["--local-bin-path=" ++ takeDirectory out]
155+
"install"
156+
global.gBuildArgs
157+
integrationTestFlagArgs
158+
"--pedantic"
159+
"stack"
160+
)
161+
(tryJust (guard . isDoesNotExistError) (removeFile out))
162+
163+
where
164+
integrationTestFlagArgs =
165+
-- Explicitly enabling 'hide-dependency-versions' and 'supported-build' to
166+
-- work around https://github.com/commercialhaskell/stack/issues/4960
167+
[ "--flag=stack:hide-dependency-versions"
168+
, "--flag=stack:supported-build"
169+
]
170+
171+
getBinaryPkgStageFiles = do
172+
docFiles <- getDocFiles
173+
let stageFiles = concat
174+
[ [releaseStageDir </> binaryName </> stackExeFileName]
175+
, map ((releaseStageDir </> binaryName) </>) docFiles
176+
]
177+
need stageFiles
178+
pure stageFiles
179+
180+
getDocFiles = getDirectoryFiles "." ["LICENSE", "*.md", "doc//*.md"]
181+
182+
buildPhony = "build"
183+
184+
releaseStageDir = releaseDir </> "stage"
185+
releaseBinDir = releaseDir </> "bin"
186+
187+
binaryPkgFileNames =
188+
case platformOS of
189+
OSX -> [binaryExeFileName, binaryPkgTarGzFileName]
190+
_ -> undefined
191+
binaryPkgTarGzFileName = binaryName <.> tarGzExt
192+
binaryExeFileName = binaryName ++ "-bin" <.> exe
193+
binaryName = concat
194+
[ stackProgName
195+
, "-"
196+
, stackVersionStr global
197+
, "-"
198+
, display targetPlatformOS
199+
, "-"
200+
, display global.gArch
201+
]
202+
stackExeFileName = stackProgName <.> exe
203+
204+
tarGzExt = tarExt <.> gzExt
205+
gzExt = ".gz"
206+
tarExt = ".tar"
207+
208+
-- | Create a .tar.gz files from files. The paths should be absolute, and will
209+
-- be made relative to the base directory in the tarball.
210+
writeTarGz ::
211+
(FilePath -> FilePath)
212+
-> FilePath
213+
-> FilePath
214+
-> [FilePath]
215+
-> Action ()
216+
writeTarGz fixPath out baseDir inputFiles = liftIO $ do
217+
content <- Tar.pack baseDir $ map (dropDirectoryPrefix baseDir) inputFiles
218+
L8.writeFile out $ GZip.compress $ Tar.write $ map fixPath' content
219+
where
220+
fixPath' :: Tar.Entry -> Tar.Entry
221+
fixPath' entry =
222+
case TarEntry.toTarPath isDir $ fixPath $ TarEntry.entryPath entry of
223+
Left e -> error $ show (Tar.entryPath entry, e)
224+
Right tarPath -> entry { TarEntry.entryTarPath = tarPath }
225+
where
226+
isDir =
227+
case TarEntry.entryContent entry of
228+
TarEntry.Directory -> True
229+
_ -> False
230+
231+
-- | Drops a directory prefix from a path. The prefix automatically has a path
232+
-- separator character appended. Fails if the path does not begin with the
233+
-- prefix.
234+
dropDirectoryPrefix :: FilePath -> FilePath -> FilePath
235+
dropDirectoryPrefix prefix path =
236+
case stripPrefix (toStandard prefix ++ "/") (toStandard path) of
237+
Nothing -> error
238+
( "dropDirectoryPrefix: cannot drop "
239+
++ show prefix
240+
++ " from "
241+
++ show path
242+
)
243+
Just stripped -> stripped
244+
245+
-- | String representation of Stack package version.
246+
stackVersionStr :: Global -> String
247+
stackVersionStr =
248+
display . pkgVersion . package . gStackPackageDescription
249+
250+
-- | Current operating system.
251+
platformOS :: OS
252+
platformOS =
253+
let Platform _ os = buildPlatform
254+
in os
255+
256+
-- | Target operating system
257+
targetPlatformOS :: OS
258+
targetPlatformOS = Linux
259+
260+
-- | Directory in which to store build and intermediate files.
261+
releaseDir :: FilePath
262+
releaseDir = "_release"
263+
264+
-- | @--build-args@ command-line option name.
265+
buildArgsOptName :: String
266+
buildArgsOptName = "build-args"
267+
268+
-- | @--alpine@ command-line option name.
269+
alpineOptName :: String
270+
alpineOptName = "alpine"
271+
272+
-- | Arguments to pass to all 'stack' invocations.
273+
stackArgs :: Global -> [String]
274+
stackArgs global = [ "--arch=" ++ display global.gArch
275+
, "--interleaved-output"
276+
]
277+
278+
-- | Name of the 'stack' program.
279+
stackProgName :: FilePath
280+
stackProgName = "stack"
281+
282+
-- | Global values and options.
283+
data Global = Global
284+
{ gStackPackageDescription :: !PackageDescription
285+
, gArch :: !Arch
286+
, gBuildArgs :: [String]
287+
}
288+
deriving Show

0 commit comments

Comments
 (0)