2 # -*- coding: utf-8 -*-
5 release.py - build the release of cabal-install"
9 This utility is only intended for use in building cabal-install
10 binary distributions on platforms with existing cabal-install.
13 # TODO, by using v2-install we build from sdists, which is good
14 # But we cannot get plan.json, to get dependency-receipt
15 # https://github.com/haskell/cabal/issues/6988
17 # TODO provide DWARF enabled builds?
19 # We don't build documentation, its well built by readthedocs.
20 # We cannot make tarball, as the private key for signing should be on the builder machine.
21 # We also don't use caching, this way we have one moving part less.
28 from pathlib
import Path
29 from textwrap
import dedent
30 from typing
import NamedTuple
32 DEFAULT_INDEXSTATE
='2020-07-23T11:14:13Z'
34 Args
= NamedTuple('Args', [
47 #######################################################################
49 def subprocess_run(args
, **kwargs
):
50 "Like subprocess.run, but also print what we run"
52 args
= list(map(str, args
)) # For Windows, https://www.scivision.dev/windows-python-pathlib-subprocess-bug/
53 args_str
= ' '.join(map(str, args
))
56 extras
+= f
' cwd={kwargs["cwd"]}'
57 print(f
'%{extras} {args_str}')
59 return subprocess
.run(args
, **kwargs
)
62 #######################################################################
66 r
= r
.strip() # release
67 if i
== '': return 'linux'
68 else: return f
"{i}-{r}".lower()
71 # https://en.wikipedia.org/wiki/MacOS_version_history#Releases
72 if macver
.startswith('10.12.'): return 'sierra'
73 if macver
.startswith('10.13.'): return 'high-sierra'
74 if macver
.startswith('10.14.'): return 'mojave'
75 if macver
.startswith('10.15.'): return 'catalina'
76 if macver
.startswith('11.0.'): return 'big-sur'
79 def archive_name(cabalversion
):
80 # Ask platform information
81 machine
= platform
.machine().lower()
82 if machine
== '': machine
= "unknown"
83 if machine
== 'amd64': machine
= "x86_64"
85 system
= platform
.system().lower()
86 if system
== '': system
= "unknown"
91 i
= subprocess_run(['lsb_release', '-si'], stdout
=subprocess
.PIPE
, encoding
='UTF-8')
92 r
= subprocess_run(['lsb_release', '-sr'], stdout
=subprocess
.PIPE
, encoding
='UTF-8')
93 version
= linuxname(i
.stdout
, r
.stdout
)
96 with
open('/etc/alpine-release') as f
:
97 alpinever
= f
.read().strip()
98 version
= f
'alpine-{alpinever}'
101 elif system
== 'darwin':
102 version
= 'darwin-' + macname(platform
.mac_ver()[0])
103 elif system
== 'freebsd':
104 version
= 'freebsd-' + platform
.release().lower()
106 return f
'cabal-install-{cabalversion}-{machine}-{version}'
109 #######################################################################
111 def step_makedirs(args
: Args
):
112 (args
.builddir
/ 'bin').mkdir(parents
=True, exist_ok
=True)
113 (args
.builddir
/ 'cabal').mkdir(parents
=True, exist_ok
=True)
115 def step_config(args
: Args
):
117 if platform
.system() == 'Linux':
118 splitsections
= 'split-sections: True'
120 # https://github.com/Mistuke/CabalChoco/blob/d0e1d2fd8ce13ab4271c4b906ca0bde3b710a310/3.2.0.0/cabal/tools/chocolateyInstall.ps1#L289
121 extraprogpath
= str(args
.builddir
/ 'bin')
122 if platform
.system() == 'Windows':
123 msysbin
= Path('C:\\tools\\msys64\\usr\\bin')
125 extraprogpath
= extraprogpath
+ "," + str(msysbin
)
129 repository hackage.haskell.org
130 url: http://hackage.haskell.org/
132 remote-build-reporting: anonymous
133 remote-repo-cache: {args.builddir}/cabal/packages
135 write-ghc-environment-files: never
137 overwrite-policy: always
142 build-summary: {args.builddir}/cabal/logs/build.log
143 installdir: {args.builddir}/bin
144 logs-dir: {args.builddir}/cabal/logs
145 store-dir: {args.builddir}/cabal/store
146 symlink-bindir: {args.builddir}/bin
147 extra-prog-path: {extraprogpath}
152 prefix: {args.builddir}
155 with
open(args
.builddir
/ 'cabal' / 'config', 'w') as f
:
159 cabal_project
= dedent(f
"""
160 packages: {args.tarlib}
161 packages: {args.tarexe}
162 packages: {args.tarsolver}
168 ghc-options: -fexpose-all-unfoldings -fspecialise-aggressively
171 ghc-options: -fexpose-all-unfoldings
175 # --enable-executable-static doesn't affect "non local" executables, as in v2-install project
176 cabal_project
+= dedent("""
177 package cabal-install
178 executable-static: True
180 cabal_project
+= dedent(f
"""
182 flags: {'+' if args.ofdlocking else '-'}ofd_locking
185 with
open(args
.builddir
/ 'cabal.project', 'w') as f
:
186 f
.write(cabal_project
)
188 def make_env(args
: Args
):
190 'PATH': os
.environ
['PATH'],
191 'CABAL_DIR': str(args
.builddir
),
192 'CABAL_CONFIG': str(args
.builddir
/ 'cabal' / 'config'),
194 # https://superuser.com/questions/1079017/is-there-an-environment-variable-for-c-users-username-appdata-local-temp-in-w
195 # In particular, we surely need 'TEMP'
196 # And also SYSTEMROOT to make 'curl' work!
199 'HOME', 'HOMEDRIVE', 'HOMEPATH',
201 'PATHEXT', 'APPDATA', 'LOCALAPPDATA', 'SYSTEMROOT',
204 if key
in os
.environ
:
205 env
[key
] = os
.environ
[key
]
209 def step_cabal_update(args
: Args
):
215 f
'--index-state={args.indexstate}',
216 ], cwd
=args
.builddir
, check
=True, env
=env
)
218 def step_cabal_install(args
: Args
):
224 'cabal-install:exe:cabal',
225 '--project-file=cabal.project',
226 f
'--with-compiler={args.compiler}',
227 ], cwd
=args
.builddir
, check
=True, env
=env
)
229 def step_make_archive(args
: Args
):
232 print(f
'Creating distribution tarball')
234 # Get bootstrapped cabal version
235 # This also acts as smoke test
236 cabal_path
= args
.builddir
/ 'bin' / 'cabal'
237 if platform
.system() == 'Windows':
238 cabal_path
= cabal_path
.with_suffix('.exe')
239 p
= subprocess_run([cabal_path
, '--numeric-version'], stdout
=subprocess
.PIPE
, check
=True, encoding
='UTF-8')
240 cabalversion
= p
.stdout
.replace('\n', '').strip()
243 name
= archive_name(cabalversion
)
245 name
= name
+ "-static"
246 if not args
.ofdlocking
:
247 name
= name
+ "-noofd"
249 basename
= args
.builddir
/ 'artifacts' / name
251 # In temporary directory, create a directory which we will archive
252 tmpdir
= args
.builddir
/ 'tmp'
253 tmpdir
.mkdir(parents
=True, exist_ok
=True)
255 rootdir
= Path(tempfile
.mkdtemp(dir=tmpdir
))
256 shutil
.copy(cabal_path
, rootdir
/ 'cabal')
260 if platform
.system() == 'Windows': fmt
= 'zip'
261 archivename
= shutil
.make_archive(basename
, fmt
, rootdir
)
266 #######################################################################
271 parser
= argparse
.ArgumentParser(
272 description
="release packaging utility for cabal-install.",
274 formatter_class
= argparse
.RawDescriptionHelpFormatter
)
276 class EnableDisable(argparse
.Action
):
277 def __call__(self
, parser
, namespace
, values
, option_string
=None):
278 value
= option_string
.startswith('--enable')
279 setattr(namespace
, self
.dest
, value
)
281 parser
.add_argument('-w', '--with-compiler', type=str, default
='ghc', help='path to GHC')
282 parser
.add_argument('-C', '--with-cabal', type=str, default
='cabal', help='path to cabal-install')
283 parser
.add_argument('-i', '--index-state', type=str, default
=DEFAULT_INDEXSTATE
, help='index state of Hackage to use')
284 parser
.add_argument('--enable-static-executable', '--disable-static-executable', dest
='static', nargs
=0, default
=False, action
=EnableDisable
, help='Statically link cabal executable')
285 parser
.add_argument('--enable-ofd-locking', '--disable-ofd-locking', dest
='ofd_locking', nargs
=0, default
=True, action
=EnableDisable
, help='OFD locking (lukko)')
286 parser
.add_argument('--tarlib', dest
='tarlib', required
=True, metavar
='LIBTAR', help='path to Cabal-version.tar.gz')
287 parser
.add_argument('--tarsolver', dest
='tarsolver', required
=True, metavar
='SOLVERTAR', help='path to cabal-install-solver-version.tar.gz')
288 parser
.add_argument('--tarexe', dest
='tarexe', required
=True, metavar
='EXETAR', help='path to cabal-install-version.tar.gz')
289 parser
.add_argument('--builddir', dest
='builddir', type=str, default
='_build', help='build directory')
291 args
= parser
.parse_args()
294 compiler
= Path(shutil
.which(args
.with_compiler
)),
295 cabal
= Path(shutil
.which(args
.with_cabal
)),
296 indexstate
= args
.index_state
,
297 builddir
= Path(args
.builddir
).resolve(),
298 static
= args
.static
,
299 ofdlocking
= args
.ofd_locking
,
300 tarlib
= Path(args
.tarlib
).resolve(),
301 tarexe
= Path(args
.tarexe
).resolve(),
302 tarsolver
= Path(args
.tarsolver
).resolve()
306 compiler: {args.compiler}
308 index-state: {args.indexstate}
309 builddir: {args.builddir}
310 static: {args.static}
311 ofd-locking: {args.ofdlocking}
312 lib-tarball: {args.tarlib}
313 solver-tarball: {args.tarsolver}
314 exe-tarball: {args.tarexe}
318 subprocess_run([args
.compiler
, '--version'], check
=True)
319 subprocess_run([args
.compiler
, '--print-project-git-commit-id'], check
=True)
320 subprocess_run([args
.cabal
, '--version'], check
=True)
324 step_cabal_update(args
)
325 step_cabal_install(args
)
326 archivename
= step_make_archive(args
)
331 Distribution have been archived in
337 if __name__
== '__main__':