diff options
Diffstat (limited to 'tools/niminst.nim')
-rw-r--r-- | tools/niminst.nim | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/tools/niminst.nim b/tools/niminst.nim new file mode 100644 index 000000000..2418ea6c9 --- /dev/null +++ b/tools/niminst.nim @@ -0,0 +1,346 @@ +# +# +# The Nimrod Installation Generator +# (c) Copyright 2008 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +const + haveZipLib = defined(unix) + +when haveZipLib: + import zipfiles + +import + os, strutils, parseopt, parsecfg, strtabs, streams + +const + maxOS = 20 # max number of OSes + maxCPU = 10 # max number of CPUs + buildShFile = "build.sh" + +type + TAppType = enum appConsole, appGUI + TAction = enum + actionNone, # action not yet known + actionCSource # action: create C sources + actionInno, # action: create Inno Setup installer + actionZip # action: create zip file + TConfigData = object of TObject + actions: set[TAction] + commonFiles, windowsFiles, unixFiles, binPaths, authors, + oses, cpus: seq[string] + cfiles: array[1..maxOS, array[1..maxCPU, seq[string]]] + ccompiler, innosetup: tuple[path, flags: string] + name, version, description, license, infile, outdir: string + innoSetupFlag, installScript, uninstallScript: bool + vars: PStringTable + app: TAppType + nimrodArgs: string + +proc initConfigData(c: var TConfigData) = + c.actions = {} + c.commonFiles = @[] + c.windowsFiles = @[] + c.unixFiles = @[] + c.binPaths = @[] + c.authors = @[] + c.oses = @[] + c.cpus = @[] + c.ccompiler = ("", "") + c.innosetup = ("", "") + c.name = "" + c.version = "" + c.description = "" + c.license = "" + c.infile = "" + c.outdir = "" + c.nimrodArgs = "" + c.innoSetupFlag = false + c.installScript = false + c.uninstallScript = false + c.vars = newStringTable(modeStyleInsensitive) + +include "inno.tmpl" +include "install.tmpl" + +# ------------------------- configuration file ------------------------------- + +const + Version = "0.5" + Usage = "niminst - Nimrod Installation Generator Version " & version & """ + + (c) 2008 Andreas Rumpf +Usage: + niminst [options] command[;command2...] ini-file[.ini] [compile_options] +Command: + csource build C source code for source based installations + zip build the ZIP file + inno build the Inno Setup installer +Options: + -o, --output:dir set the output directory + --var:name=value set the value of a variable + -h, --help shows this help + -v, --version shows the version +Compile_options: + will be passed to the Nimrod compiler +""" + +proc parseCmdLine(c: var TConfigData) = + var p = init() + while true: + next(p) + var kind = p.kind + var key = p.key + var val = p.val + case kind + of cmdArgument: + if c.actions == {}: + for a in split(normalize(key), {';', ','}): + case a + of "csource": incl(c.actions, actionCSource) + of "zip": incl(c.actions, actionZip) + of "inno": incl(c.actions, actionInno) + else: quit(Usage) + else: + c.infile = appendFileExt(key, "ini") + c.nimrodArgs = getRestOfCommandLine(p) + break + of cmdLongOption, cmdShortOption: + case normalize(key) + of "help", "h": write(stdout, Usage) + of "version", "v": writeln(stdout, Version) + of "o", "output": c.outdir = val + of "var": + var idx = findSubStr('=', val) + if idx < 0: quit("invalid command line") + c.vars[copy(val, 0, idx-1)] = copy(val, idx+1) + else: quit(Usage) + of cmdEnd: break + if c.infile.len == 0: quit(Usage) + +proc walkDirRecursively(s: var seq[string], root: string) = + for k, f in walkDir(root): + case k + of pcFile, pcLinkToFile: add(s, UnixToNativePath(f)) + of pcDirectory: walkDirRecursively(s, f) + of pcLinkToDirectory: nil + +proc addFiles(s: var seq[string], patterns: seq[string]) = + for p in items(patterns): + if existsDir(p): + walkDirRecursively(s, p) + else: + var i = 0 + for f in walkFiles(p): + add(s, UnixToNativePath(f)) + inc(i) + if i == 0: echo("[Warning] No file found that matches: " & p) + +proc pathFlags(p: var TCfgParser, k, v: string, + t: var tuple[path, flags: string]) = + case normalize(k) + of "path": t.path = v + of "flags": t.flags = v + else: quit(errorStr(p, "unknown variable: " & k)) + +proc parseIniFile(c: var TConfigData) = + var + p: TCfgParser + section: string # current section + var input = newFileStream(c.infile, fmRead) + if input != nil: + open(p, input, c.infile) + while true: + var k = next(p) + case k.kind + of cfgEof: break + of cfgSectionStart: + section = normalize(k.section) + case section + of "innosetup": c.innoSetupFlag = true + of "installscript": c.installScript = true + of "uninstallscript": c.uninstallScript = true + of "var", "project", "common", "ccompiler", "windows", "unix", "7z": nil + else: nil # quit(errorStr(p, "invalid section: " & section)) + + of cfgKeyValuePair: + var v = k.value % c.vars + c.vars[k.key] = v + + case section + of "project": + case normalize(k.key) + of "name": c.name = v + of "version": c.version = v + of "os": c.oses = splitSeq(v, {';'}) + of "cpu": c.cpus = splitSeq(v, {';'}) + of "authors": c.authors = splitSeq(v, {';'}) + of "description": c.description = v + of "app": + case normalize(v) + of "console": c.app = appConsole + of "gui": c.app = appGUI + else: quit(errorStr(p, "expected: console or gui")) + of "license": c.license = UnixToNativePath(k.value) + else: quit(errorStr(p, "unknown variable: " & k.key)) + of "var": nil + of "installscript", "uninstallscript": + quit(errorStr(p, "unknown variable: " & k.key)) + of "common": + case normalize(k.key) + of "files": addFiles(c.commonFiles, splitSeq(v, {';'})) + else: quit(errorStr(p, "unknown variable: " & k.key)) + of "innosetup": pathFlags(p, k.key, v, c.innoSetup) + of "ccompiler": pathFlags(p, k.key, v, c.ccompiler) + of "windows": + case normalize(k.key) + of "files": addFiles(c.windowsFiles, splitSeq(v, {';'})) + of "binpath": c.binPaths = splitSeq(v, {';'}) + else: quit(errorStr(p, "unknown variable: " & k.key)) + of "unix": + case normalize(k.key) + of "files": addFiles(c.unixFiles, splitSeq(v, {';'})) + else: quit(errorStr(p, "unknown variable: " & k.key)) + else: nil + + of cfgOption: quit(errorStr(p, "syntax error")) + of cfgError: quit(errorStr(p, k.msg)) + close(p) + if c.name.len == 0: c.name = changeFileExt(extractFilename(c.infile), "") + else: + quit("cannot open: " & c.infile) + +# ------------------------- generate source based installation --------------- + +proc readCFiles(c: var TConfigData, osA, cpuA: int) = + var cfg: TCfgParser + var cfilesSection = false + var f = extractDir(c.infile) / "mapping.txt" + c.cfiles[osA][cpuA] = @[] + var input = newFileStream(f, fmRead) + if input != nil: + open(cfg, input, f) + while true: + var k = next(cfg) + case k.kind + of cfgEof: break + of cfgSectionStart: + if cfilesSection: break + cfilesSection = cmpIgnoreStyle(k.section, "cfiles") == 0 + of cfgKeyValuePair: nil + of cfgOption: + if cfilesSection and cmpIgnoreStyle(k.key, "file") == 0: + add(c.cfiles[osA][cpuA], k.value) + of cfgError: quit(errorStr(cfg, k.msg)) + close(cfg) + else: + quit("Cannot open: " & f) + +proc buildDir(os, cpu: int): string = + return "build" / ($os & "_" & $cpu) + +proc srcdist(c: var TConfigData) = + for x in walkFiles("lib/*.h"): CopyFile("build" / extractFilename(x), x) + for osA in 1..c.oses.len: + for cpuA in 1..c.cpus.len: + var dir = buildDir(osA, cpuA) + createDir(dir) + var cmd = ("nimrod compile -f --symbolfiles:off --compileonly " & + "--gen_mapping $1 " & + " --os:$2 --cpu:$3 $4") % + [c.nimrodArgs, c.oses[osA-1], c.cpus[cpuA-1], + changeFileExt(c.infile, "nim")] + echo("Executing: " & cmd) + if executeShellCommand(cmd) != 0: + quit("Error: call to nimrod compiler failed") + readCFiles(c, osA, cpuA) + for i in 0 .. c.cfiles[osA][cpuA].len-1: + var dest = dir / extractFilename(c.cfiles[osA][cpuA][i]) + CopyFile(dest, c.cfiles[osA][cpuA][i]) + c.cfiles[osA][cpuA][i] = dest + # second pass: remove duplicate files + for osA in countdown(c.oses.len, 1): + for cpuA in countdown(c.cpus.len, 1): + for i in 0..c.cfiles[osA][cpuA].len-1: + var dup = c.cfiles[osA][cpuA][i] + var f = extractFilename(dup) + for osB in 1..c.oses.len: + for cpuB in 1..c.cpus.len: + if osB != osA or cpuB != cpuA: + var orig = buildDir(osB, cpuB) / f + if ExistsFile(orig) and ExistsFile(dup) and + sameFileContent(orig, dup): + # file is identical, so delete duplicate: + RemoveFile(dup) + c.cfiles[osA][cpuA][i] = orig + var scrpt = GenerateInstallScript(c) + var f: TFile + if openFile(f, buildShFile, fmWrite): + writeln(f, scrpt) + closeFile(f) + else: + quit("Cannot open for writing: " & buildShFile) + +# --------------------- generate inno setup ----------------------------------- +proc setupDist(c: var TConfigData) = + var scrpt = GenerateInnoSetup(c) + var f: TFile + var n = "build" / "install_$1_$2.iss" % [toLower(c.name), c.version] + if openFile(f, n, fmWrite): + writeln(f, scrpt) + closeFile(f) + when defined(windows): + if c.innoSetup.path.len == 0: + c.innoSetup.path = "iscc.exe" + var outcmd = if c.outdir.len == 0: "build" else: c.outdir + var cmd = "$1 $2 /O$3 $4" % [c.innoSetup.path, c.innoSetup.flags, + outcmd, n] + Echo("Executing: " & cmd) + if executeShellCommand(cmd) == 0: + removeFile(n) + else: + quit("External program failed") + else: + quit("Cannot open for writing: " & n) + +# ------------------ generate ZIP file --------------------------------------- +when haveZipLib: + proc zipDist(c: var TConfigData) = + var proj = toLower(c.name) + var n = "$1_$2.zip" % [proj, c.version] + if c.outdir.len == 0: n = "build" / n + else: n = c.outdir / n + var z: TZipArchive + if open(z, n, fmWrite): + addFile(z, proj / buildShFile, buildShFile) + for f in walkFiles("lib/*.h"): + addFile(z, proj / "build" / extractFilename(f), f) + for osA in 1..c.oses.len: + for cpuA in 1..c.cpus.len: + var dir = buildDir(osA, cpuA) + for k, f in walkDir(dir): + if k == pcFile: addFile(z, proj / dir / extractFilename(f), f) + for f in items(c.commonFiles): addFile(z, proj / f, f) + for f in items(c.unixFiles): addFile(z, proj / f, f) + close(z) + else: + quit("Cannot open for writing: " & n) + +# ------------------- main ---------------------------------------------------- + +var c: TConfigData +initConfigData(c) +parseCmdLine(c) +parseIniFile(c) +if actionInno in c.actions: + setupDist(c) +if actionCSource in c.actions: + srcdist(c) +if actionZip in c.actions: + when haveZipLib: + zipdist(c) + else: + quit("libzip is not installed") |