1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
from macros import error
when NimMajor < 1 and NimMinor <= 19 and NimPatch < 9:
from ospaths import `/`, splitFile
else:
from os import `/`, splitFile
const
doOptimize = true
let
# pcre
pcreVersion = getEnv("PCREVER", "8.42")
pcreSourceDir = "pcre-" & pcreVersion
pcreArchiveFile = pcreSourceDir & ".tar.bz2"
pcreDownloadLink = "https://downloads.sourceforge.net/pcre/" & pcreArchiveFile
pcreInstallDir = (thisDir() / "pcre/") & pcreVersion
# http://www.linuxfromscratch.org/blfs/view/8.1/general/pcre.html
pcreConfigureCmd = ["./configure", "--prefix=" & pcreInstallDir, "--enable-pcre16", "--enable-pcre32", "--disable-shared"]
pcreIncludeDir = pcreInstallDir / "include"
pcreLibDir = pcreInstallDir / "lib"
pcreLibFile = pcreLibDir / "libpcre.a"
# libressl
libreSslVersion = getEnv("LIBRESSLVER", "2.8.1")
libreSslSourceDir = "libressl-" & libreSslVersion
libreSslArchiveFile = libreSslSourceDir & ".tar.gz"
libreSslDownloadLink = "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/" & libreSslArchiveFile
libreSslInstallDir = (thisDir() / "libressl/") & libreSslVersion
libreSslConfigureCmd = ["./configure", "--disable-shared", "--prefix=" & libreSslInstallDir]
libreSslLibDir = libreSslInstallDir / "lib"
libreSslLibFile = libreSslLibDir / "libssl.a"
libreCryptoLibFile = libreSslLibDir / "libcrypto.a"
libreSslIncludeDir = libreSslInstallDir / "include/openssl"
# openssl
openSslSeedConfigOsCompiler = "linux-x86_64"
openSslVersion = getEnv("OPENSSLVER", "1.1.1")
openSslSourceDir = "openssl-" & openSslVersion
openSslArchiveFile = openSslSourceDir & ".tar.gz"
openSslDownloadLink = "https://www.openssl.org/source/" & openSslArchiveFile
openSslInstallDir = (thisDir() / "openssl/") & openSslVersion
# "no-async" is needed for openssl to compile using musl
# - https://gitter.im/nim-lang/Nim?at=5bbf75c3ae7be940163cc198
# - https://www.openwall.com/lists/musl/2016/02/04/5
# -DOPENSSL_NO_SECURE_MEMORY is needed to make openssl compile using musl.
# - https://github.com/openssl/openssl/issues/7207#issuecomment-420814524
openSslConfigureCmd = ["./Configure", openSslSeedConfigOsCompiler, "no-shared", "no-zlib", "no-async", "-fPIC", "-DOPENSSL_NO_SECURE_MEMORY", "--prefix=" & openSslInstallDir]
openSslLibDir = openSslInstallDir / "lib"
openSslLibFile = openSslLibDir / "libssl.a"
openCryptoLibFile = openSslLibDir / "libcrypto.a"
openSslIncludeDir = openSslInstallDir / "include/openssl"
# https://github.com/kaushalmodi/nimy_lisp
proc dollar[T](s: T): string =
result = $s
proc mapconcat[T](s: openArray[T]; sep = " "; op: proc(x: T): string = dollar): string =
## Concatenate elements of ``s`` after applying ``op`` to each element.
## Separate each element using ``sep``.
for i, x in s:
result.add(op(x))
if i < s.len-1:
result.add(sep)
task installPcre, "Installs PCRE using musl-gcc":
if not existsFile(pcreLibFile):
if not existsDir(pcreSourceDir):
if not existsFile(pcreArchiveFile):
exec("curl -LO " & pcreDownloadLink)
exec("tar xf " & pcreArchiveFile)
else:
echo "PCRE lib source dir " & pcreSourceDir & " already exists"
withDir pcreSourceDir:
putEnv("CC", "/usr/bin/x86_64-alpine-linux-musl-gcc -static")
exec(pcreConfigureCmd.mapconcat())
exec("make -j8")
exec("make install")
else:
echo pcreLibFile & " already exists"
setCommand("nop")
task installLibreSsl, "Installs LIBRESSL using musl-gcc":
if (not existsFile(libreSslLibFile)) or (not existsFile(libreCryptoLibFile)):
if not existsDir(libreSslSourceDir):
if not existsFile(libreSslArchiveFile):
exec("curl -LO " & libreSslDownloadLink)
exec("tar xf " & libreSslArchiveFile)
else:
echo "LibreSSL lib source dir " & libreSslSourceDir & " already exists"
withDir libreSslSourceDir:
# -idirafter /usr/include/ # Needed for linux/sysctl.h
# -idirafter /usr/include/x86_64-linux-gnu/ # Needed for Travis/Ubuntu build to pass, for asm/types.h
putEnv("CC", "/usr/bin/x86_64-alpine-linux-musl-gcc -static -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/")
putEnv("C_INCLUDE_PATH", libreSslIncludeDir)
exec(libreSslConfigureCmd.mapconcat())
exec("make -j8 -C crypto") # build just the "crypto" component
exec("make -j8 -C ssl") # build just the "ssl" component
exec("make -C crypto install")
exec("make -C ssl install")
else:
echo libreSslLibFile & " already exists"
setCommand("nop")
task installOpenSsl, "Installs OPENSSL using musl-gcc":
if (not existsFile(openSslLibFile)) or (not existsFile(openCryptoLibFile)):
if not existsDir(openSslSourceDir):
if not existsFile(openSslArchiveFile):
exec("curl -LO " & openSslDownloadLink)
exec("tar xf " & openSslArchiveFile)
else:
echo "OpenSSL lib source dir " & openSslSourceDir & " already exists"
withDir openSslSourceDir:
# https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html
# -idirafter /usr/include/ # Needed for Travis/Ubuntu build to pass, for linux/version.h, etc.
# -idirafter /usr/include/x86_64-linux-gnu/ # Needed for Travis/Ubuntu build to pass, for asm/types.h
putEnv("CC", "/usr/bin/x86_64-alpine-linux-musl-gcc -static -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/")
putEnv("C_INCLUDE_PATH", openSslIncludeDir)
exec(openSslConfigureCmd.mapconcat())
echo "The insecure switch -DOPENSSL_NO_SECURE_MEMORY is needed so that OpenSSL can be compiled using MUSL."
exec("make -j8 depend")
exec("make -j8")
exec("make install_sw")
else:
echo openSslLibFile & " already exists"
setCommand("nop")
# -d:musl
when defined(musl):
var
muslGccPath: string
echo " [-d:musl] Building a static binary using musl .."
muslGccPath = findExe("x86_64-alpine-linux-musl-gcc")
# echo "debug: " & muslGccPath
if muslGccPath == "":
error("'musl-gcc' binary was not found in PATH.")
switch("passL", "-static")
switch("gcc.exe", muslGccPath)
switch("gcc.linkerexe", muslGccPath)
# -d:pcre
when defined(pcre):
if not existsFile(pcreLibFile):
selfExec "installPcre" # Install PCRE in current dir if pcreLibFile is not found
switch("passC", "-I" & pcreIncludeDir) # So that pcre.h is found when running the musl task
switch("define", "usePcreHeader")
switch("passL", pcreLibFile)
# -d:libressl or -d:openssl
when defined(libressl) or defined(openssl):
switch("define", "ssl") # Pass -d:ssl to nim
when defined(libressl):
let
sslLibFile = libreSslLibFile
cryptoLibFile = libreCryptoLibFile
sslIncludeDir = libreSslIncludeDir
sslLibDir = libreSslLibDir
when defined(openssl):
let
sslLibFile = openSslLibFile
cryptoLibFile = openCryptoLibFile
sslIncludeDir = openSslIncludeDir
sslLibDir = openSslLibDir
if (not existsFile(sslLibFile)) or (not existsFile(cryptoLibFile)):
# Install SSL in current dir if sslLibFile or cryptoLibFile is not found
when defined(libressl):
selfExec "installLibreSsl"
when defined(openssl):
selfExec "installOpenSsl"
switch("passC", "-I" & sslIncludeDir) # So that ssl.h is found when running the musl task
switch("passL", "-L" & sslLibDir)
switch("passL", "-lssl")
switch("passL", "-lcrypto") # This *has* to come *after* -lssl
switch("dynlibOverride", "libssl")
switch("dynlibOverride", "libcrypto")
proc binOptimize(binFile: string) =
## Optimize size of the ``binFile`` binary.
echo ""
if findExe("strip") != "":
echo "Running 'strip -s' .."
exec "strip -s " & binFile
if findExe("upx") != "":
# https://github.com/upx/upx/releases/
echo "Running 'upx --best' .."
exec "upx --best " & binFile
# nim musl foo.nim
task musl, "Builds an optimized static binary using musl":
## Usage: nim musl [-d:pcre] [-d:libressl|-d:openssl] <FILE1> <FILE2> ..
var
switches: seq[string]
nimFiles: seq[string]
let
numParams = paramCount()
when defined(libressl) and defined(openssl):
error("Define only 'libressl' or 'openssl', not both.")
# param 0 will always be "nim"
# param 1 will always be "musl"
for i in 2 .. numParams:
if paramStr(i)[0] == '-': # -d:foo or --define:foo
switches.add(paramStr(i))
else:
# Non-switch parameters are assumed to be Nim file names.
nimFiles.add(paramStr(i))
if nimFiles.len == 0:
error(["The 'musl' sub-command accepts at least one Nim file name",
" Examples: nim musl FILE.nim",
" nim musl FILE1.nim FILE2.nim",
" nim musl -d:pcre FILE.nim",
" nim musl -d:libressl FILE.nim",
" nim musl -d:pcre -d:openssl FILE.nim"].mapconcat("\n"))
for f in nimFiles:
let
extraSwitches = switches.mapconcat()
(dirName, baseName, _) = splitFile(f)
binFile = dirName / baseName # Save the binary in the same dir as the nim file
nimArgsArray = when doOptimize:
["c", "-d:musl", "-d:release", "--opt:size", extraSwitches, f]
else:
["c", "-d:musl", extraSwitches, f]
nimArgs = nimArgsArray.mapconcat()
# echo "[debug] f = " & f & ", binFile = " & binFile
# Build binary
echo "\nRunning 'nim " & nimArgs & "' .."
selfExec nimArgs
when doOptimize:
# Optimize binary
binOptimize(binFile)
echo "\nCreated binary: " & binFile