aboutsummaryrefslogtreecommitdiff
path: root/src/lapis.nim
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lapis.nim210
1 files changed, 210 insertions, 0 deletions
diff --git a/src/lapis.nim b/src/lapis.nim
new file mode 100644
index 0000000..4a1940c
--- /dev/null
+++ b/src/lapis.nim
@@ -0,0 +1,210 @@
+import std / [
+ macros,
+ strformat,
+ strutils
+]
+
+macro tessera*(packageName: untyped, body: untyped): untyped =
+ ## Small declerative DSL for defining packages
+ ## The schema is:
+ ## package "name":
+ ## source: "<url>"
+ ## patches: @[...] (if any)
+ ## dependencies: @[...] (if any)
+ ## build: @[...]
+ ## result: @[...]
+ ##
+ ## The macro expands at compile time into a build() procedure.
+ ## When run it:
+ ## - verifies the `dependencies` are installed properly
+ ## ( this rn means that it checks $PATH,
+ ## since I haven't worked on mosaic yet )
+ ## ( if a dependency isn't installed, it installs it )
+ ## - uses cURL to fetch the tarball from the `sourceURL`
+ ## - extracts the tarball, and cd's into it
+ ## - applies `patches` (if any)
+ ## - runs the build commands defined in `build: @[...]`
+
+ if packageName.kind == nnkIdent:
+ error("Name of package must be a string: {packageName}->\"{packageName}\"")
+
+ body.expectKind nnkStmtList
+
+ var
+ nameLit = newLit($toStrLit(packageName))
+ sourceNode = newLit("")
+ patchesNode = newLit(newSeq[string]())
+ depsNode = newLit(newSeq[string]())
+ buildNode = newLit(newSeq[string]())
+ resultNode = newLit("")
+
+ for stmt in body:
+ if stmt.kind == nnkCall:
+ let key = $stmt[0]
+ let value = stmt[1]
+ case key:
+ of "source": sourceNode = value
+ of "patches": patchesNode = value
+ of "dependencies": depsNode = value
+ of "build": buildNode = value
+ of "result": resultNode = value
+ else:
+ error("tessera: unknown field {key}".fmt)
+ else:
+ error("tessera: unexpected statement in package block: {stmt.repr}".fmt)
+
+ let buildIdent = ident("build_{packageName}".fmt)
+
+ result = quote do:
+ import os, osproc, strutils
+
+ const
+ pkgName = `nameLit`
+ pkgSource = `sourceNode`
+ pkgPatches = `patchesNode`
+ pkgDeps = `depsNode`
+ pkgBuild = `buildNode`
+ pkgResult = `resultNode`
+
+ # Helper procs
+ proc extractFilename(url: string): string =
+ if url.len == 0:
+ return ""
+ let parts = url.split('/')
+ result = parts[^1]
+
+ proc stripExtension(filename: string): string =
+ result = filename
+ # list of tar suffixes from Wikipedia
+ let suffixList = @[
+ ".tar.gz",
+ ".tar.xz",
+ ".tar.bz2",
+ ".tar.lz",
+ ".tar.lzma",
+ ".tar.lzo",
+ ".tar.Z",
+ ".tar.zst",
+ ".tb2",
+ ".tbz",
+ ".tbz2",
+ ".tz2",
+ ".taz",
+ ".tgz",
+ ".tlz",
+ ".txz",
+ ".tZ",
+ ".taZ",
+ ".tzst"
+ ]
+ for suffix in suffixList:
+ if result.endsWith(suffix):
+ result.removeSuffix(suffix)
+
+ proc runCommand(command: string, workingDir: string = ""): string =
+ echo "Running command: " & command
+ let (output, exitCode) = execCmdEx(command, workingDir=workingDir)
+ if exitCode != 0:
+ quit("Command failed with exit code: \"" & $exitCode & "\":\n" & $output)
+ result = output
+
+ proc isExecutable(filename: string): bool =
+ let permissions = getFilePermissions(filename)
+ result = ((fpUserExec in permissions) or (fpGroupExec in permissions))
+
+ proc isInstalled(name: string): bool =
+ result = false
+ let possibleFolders = @["/usr/bin/", "/usr/sbin/", "/usr/lib/"]
+ for folder in possibleFolders:
+ if fileExists(folder & name):
+ result = true
+
+ # TODO: Make this not depend on the tar file
+ proc ensureDependency(dependency: string, tarFile: string) =
+ if dependency.len > 0:
+ # TODO: When I fix the declerativeness, I must also fix this
+ let localInstaller = "/mosaic/panel/" & $dependency
+ echo "checking " & localInstaller & "..."
+
+ if not (fileExists(localInstaller) or isExecutable(localInstaller)):
+ quit("dependency \"" & $dependency & "\" not defined yet.")
+
+ let output = runCommand("/mosaic/panel/" & $dependency)
+ echo output
+
+ proc `buildIdent`() =
+ echo "Building " & $pkgName
+
+ let
+ mosaicSourceFolder = "/mosaic/quarry/"
+ sourceFile = $mosaicSourceFolder & extractFilename(url=pkgSource)
+
+ # Step 1: check dependencies
+ if isInstalled(pkgResult):
+ echo pkgName & " already installed..."
+ return
+
+ if pkgDeps.len > 0:
+ for dependency in pkgDeps:
+ echo "checking dependency: " & $dependency
+ ensureDependency(dependency=dependency, tarFile=sourceFile)
+
+ # Step 2: fetch source via cURL
+ var expectedFolder = stripExtension(sourceFile) & "/"
+ echo "Making " & expectedFolder & "..."
+ createDir(expectedFolder)
+
+ if not fileExists(sourceFile):
+ echo "Fetching source: " & $pkgSource & " -> " & sourceFile
+
+ let wgetOutput = runCommand(command="wget " & $pkgSource, workingDir=mosaicSourceFolder)
+ echo "wget " & wgetOutput
+ else:
+ echo $sourceFile & " exists. Continuing..."
+
+ # Step 3: untar
+ echo "extracting " & $sourceFile & "..."
+ let tarOutput = runCommand(command="tar -xf " & $sourceFile, workingDir=mosaicSourceFolder)
+ echo tarOutput
+
+ var untarFolders: seq[string] = @[]
+ for folder in walkDir(expectedFolder):
+ untarFolders.add(folder.path)
+
+ echo untarFolders
+ if untarFolders.len == 0:
+ quit("No folders produces in untarring step (?)... Quitting...")
+ if untarFolders.len == 1:
+ expectedFolder = untarFolders[0]
+
+ # Step 5
+ if pkgPatches.len > 0:
+ for patchURL in pkgPatches:
+ if patchURL.len > 0:
+ let patchFile = extractFilename(url=patchURL)
+
+ echo "Fetching patch: " & $patchURL & " -> " & $patchFile
+
+ let wgetPatchOutput = runCommand(command="wget " & $patchURL, workingDir=mosaicSourceFolder)
+ echo wgetPatchOutput
+ assert fileExists(patchFile)
+
+ # Step 6: Run the build commands:
+ if pkgBuild.len == 0:
+ quit("No commands given to run for " & $pkgName & "... Nothing to do.")
+
+ var newWorkingFolder = expectedFolder
+ echo newWorkingFolder
+ for command in pkgBuild:
+ echo command
+ if command.startsWith("cd "):
+ newWorkingFolder = newWorkingFolder & command[3..^1]
+ continue
+
+ let buildOutput = runCommand(command=command, workingDir=newWorkingFolder)
+ echo buildOutput
+
+ echo "tessera " & $pkgName & "built."
+
+ when isMainModule:
+ `buildIdent`()
Directive (EU) 2019/790, Article 4(3); all rights regarding Text and Data Mining (TDM) are reserved.