diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0cbd574 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: ⚙️ Build Binaries + +on: + workflow_call: + +env: + APP_NAME: ccnz + NIM_VERSION: stable + +jobs: + build-artifact: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - uses: jiro4989/setup-nim-action@v1 + with: + nim-version: ${{ env.NIM_VERSION }} + + # for cross compilation with ccnz + - uses: goto-bus-stop/setup-zig@v2 + + - name: Bootstrap with installed version + run: nimble install -Y + + - name: Generate build artifacts + run: nimble release + + - name: Create artifact bundles + run: nimble bundle + + - uses: actions/upload-artifact@v3 + with: + name: artifacts + path: | + dist/*.tar.gz + dist/*.zip + diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..04b004d --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,60 @@ +name: 🌙 Nightly Release + +on: + workflow_dispatch: + schedule: + - cron: '0 2 * * *' + +permissions: + contents: write + +jobs: + check-commits: + runs-on: ubuntu-latest + name: Check latest commit + outputs: + quit: ${{ steps.should_run.outputs.quit }} + steps: + - uses: actions/checkout@v3 + + - name: print latest commit + run: echo ${{ github.sha }} + + - id: should_run + name: check latest commit is less than a day + if: ${{ github.event_name == 'schedule' }} + run: | + test -n "$(git rev-list --since="24 hours" HEAD)" \ + && echo "quit=true" >> "$GITHUB_OUTPUT" + + build-artifacts: + needs: check-commits + if: ${{ needs.check-commits.outputs.quit != 'true' }} + uses: ./.github/workflows/build.yml + + create-release: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} + needs: + - build-artifacts + steps: + - uses: actions/checkout@v3 + + - name: Download Build Artifacts + uses: actions/download-artifact@v3 + + - run: ls -R artifacts + + - name: Remove Old Nightly Release + run: | + gh release delete nightly --yes || true + git push origin :nightly || true + + - name: Generate New Nightly Release + run: | + gh release create nightly \ + --title "Nightly Release (Pre-release)" \ + --prerelease \ + ./artifacts/* + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..935ea02 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +name: 🚀 Release + +on: + push: + tags: + - 'v*.*' + +permissions: + contents: write + +jobs: + build-artifacts: + uses: ./.github/workflows/build.yml + + create-release: + env: + GH_TOKEN: ${{ github.token }} + runs-on: ubuntu-latest + needs: + - build-artifacts + steps: + - uses: actions/checkout@v3 + + - name: Download Build Artifacts + uses: actions/download-artifact@v3 + + - run: ls -R artifacts/ + + - name: Generate New Nightly Release + run: | + gh release create ${{ github.ref }} ./artifacts/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21d8609 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/bin +/dist diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f89bfa --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ccnz compiles nim w/zig + + diff --git a/ccnz.nimble b/ccnz.nimble new file mode 100644 index 0000000..92c43df --- /dev/null +++ b/ccnz.nimble @@ -0,0 +1,46 @@ +version = "2023.1001" +author = "Daylin Morgan" +description = "ccnz compiles nim w/zig" +license = "MIT" +srcDir = "src" +bin = @["ccnz", "ccnzcc"] +binDir = "bin" + + +requires "nim >= 2.0.0", + "cligen" + + + +import strformat +const targets = [ + "x86_64-linux-gnu", + "x86_64-linux-musl", + "x86_64-macos-none", + "x86_64-windows-gnu" + ] + +task release, "build release assets": + mkdir "dist" + for target in targets: + let ext = if target == "x86_64-windows-gnu": ".cmd" else: "" + for app in @["ccnz", "ccnzcc"]: + let outdir = &"dist/{target}/" + exec &"ccnz cc --target {target} --nimble -- --out:{outdir}{app}{ext} -d:release src/{app}" + +task bundle, "package build assets": + cd "dist" + for target in targets: + let + app = projectName() + cmd = + if target == "x86_64-windows-gnu": + &"7z a {app}_{target}.zip {target}" + else: + &"tar czf {app}_{target}.tar.gz {target}" + + cpFile("../README.md", &"{target}/README.md") + exec cmd + + + diff --git a/nimble.lock b/nimble.lock new file mode 100644 index 0000000..1e4ee0c --- /dev/null +++ b/nimble.lock @@ -0,0 +1,16 @@ +{ + "version": 2, + "packages": { + "cligen": { + "version": "1.6.14", + "vcsRevision": "bee21f2d0878d4eba4631fd8b67370e4778424de", + "url": "https://github.com/c-blake/cligen.git", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "195164d6a417c5393ee8aac1d1c2dfb0d45574b1" + } + } + }, + "tasks": {} +} diff --git a/src/ccnz.nim b/src/ccnz.nim new file mode 100644 index 0000000..a944ff9 --- /dev/null +++ b/src/ccnz.nim @@ -0,0 +1,72 @@ +import std/[osproc, strformat, strutils, tables, terminal] +import ccnz/utils + +proc genFlags(target: string, args: seq[string]): seq[string] = + let targetList = zigTargets() + if target notin targetList: + errQuit &"unknown target: {target}", "", "must be one of:", + targetList.columns + + addFlag "cpu" + addFlag "os" + + result &= @[ + "--cc:clang", + &"--clang.exe='ccnzcc'", + &"--clang.linkerexe='ccnzcc'", + # &"--passC:\"-target {target} -fno-sanitize=undefined\"", + &"--passC:'-target {target}'", + # &"--passL:\"-target {target} -fno-sanitize=undefined\"", + &"--passL:'-target {target}'", + ] + +proc targets() = + ## show available targets + let targetList = zigTargets() + styledEcho styleBright, fgGreen, "available targets:" + echo targetList.columns + +proc cc(target: string, dryrun: bool = false, nimble: bool = false, args: seq[string]) = + ## compile with zig cc + let ccArgs = genFlags(target, args) + if args.len == 0: + errQuit "expected additional arguments i.e. -- -d:release src/main.nim\n" + + let rest = + if args[0] == "c": + args[1..^1] + else: + args + + let baseCmd = if nimble: "nimble" else: "nim" + let cmd = (@[baseCmd] & @["c"] & ccArgs & rest).join(" ") + if dryrun: + stderr.write cmd, "\n" + else: + quit(execCmd cmd) + +when isMainModule: + import cligen + zigExists() + + const + customMulti = "${doc}Usage:\n $command {SUBCMD} [sub-command options & parameters]\n\nsubcommands:\n$subcmds" + vsn = staticExec "git describe --tags --always HEAD" + + + if clCfg.useMulti == "": clCfg.useMulti = customMulti + if clCfg.helpAttr.len == 0: + clCfg.helpAttr = {"cmd": "\e[1;36m", "clDescrip": "", "clDflVal": "\e[33m", + "clOptKeys": "\e[32m", "clValType": "\e[31m", "args": "\e[3m"}.toTable + clCfg.helpAttrOff = {"cmd": "\e[m", "clDescrip": "\e[m", "clDflVal": "\e[m", + "clOptKeys": "\e[m", "clValType": "\e[m", "args": "\e[m"}.toTable + + var vsnCfg = clCfg + vsnCfg.version = vsn + + + dispatchMulti(["multi", cf = vsnCfg], [cc, help = { + "dryrun": "show command instead of executing", + "nimble": "use nimble as base command for compiling" + }, short = {"dryrun": 'n'}], + [targets]) diff --git a/src/ccnz/utils.nim b/src/ccnz/utils.nim new file mode 100644 index 0000000..b6c81ac --- /dev/null +++ b/src/ccnz/utils.nim @@ -0,0 +1,113 @@ +import std/[json, macros, math, os, osproc, strutils, terminal, sequtils] + +proc errQuit*(msg: varargs[string]) = + stderr.write msg.join("\n") & "\n" + quit 1 + +# based on https://github.com/enthus1ast/zigcc +template callZig*(zigCmd: string) = + # Set the zig compiler to call and append args + var args = @[zigCmd] + args &= commandLineParams() + # Start process + let process = startProcess( + "zig", + args = args, + options = {poStdErrToStdOut, poUsePath, poParentStreams} + ) + # Get the code so we can carry across the exit code + let exitCode = process.waitForExit() + # Clean up + close process + quit exitCode + +proc zigExists*() = + if (findExe "zig") == "": + errQuit "zig not found", + "ccnz requires a working installation of zig", + "see: https://ziglang.org/download/" + +proc zigTargets*(): seq[string] = + let (output, _) = execCmdEx "zig targets" + parseJson(output)["libc"].to(seq[string]) + +macro addFlag*(arg: untyped): untyped = + let + flag = "--" & arg.strVal & ":" + inferProc = newCall("infer" & arg.strVal, newIdentNode("target")) + + quote do: + if `flag` notin args: + let selected = `inferProc` + if selected != "": + result.add `flag` & selected + + +proc inferOs*(target: string): string = + if "windows" in target: + "Windows" + elif "macos" in target: + "MacOSX" + elif "linux" in target: + "Linux" + elif "wasm" in target: + "Linux" + else: + "" + +proc inferCpu*(target: string): string = + # Available options are: + # i386, m68k, alpha, powerpc, powerpc64, powerpc64el, sparc, + # vm, hppa, ia64, amd64, mips, mipsel, arm, arm64, js, + # nimvm, avr, msp430, sparc64, mips64, mips64el, riscv32, + # riscv64, esp, wasm32, e2k, loongarch64 + # + let candidate = target.split("-")[0] + # NOTE: I don't know what the _be eb means but if nim + # can't handle them then maybe an error would be better + result = + case candidate: + of "x86_64": + "amd64" + of "aarch64", "aarch64_be": + "arm64" + of "arm", "armeb": + "arm" + of "x86": + "i386" + of "powerpc64el": + "powerpc64le" + # remain the same + of "m68k", "mips64el", "mipsel", "mips", "powerpc", "powerpc64", "riscv64", + "sparc", "sparc64", "wasm32": + candidate + else: + "" + +# s390x-linux-gnu +# s390x-linux-musl +# sparc-linux-gnu +# sparc64-linux-gnu +# wasm32-freestanding-musl +# wasm32-wasi-musl +# x86_64-linux-gnu +# x86_64-linux-gnux32 +# x86_64-linux-musl +# x86_64-windows-gnu +# x86_64-macos-none +# x86_64-macos-none +# x86_64-macos-none +# +proc columns*(items: seq[string]): string = + ## return a list of items as equally spaced columns + let + maxWidth = max(items.mapIt(it.len)) + nColumns = floor((terminalWidth() + 1) / (maxWidth + 1)).int + result = ( + items.mapIt(it.alignLeft(maxWidth + 1)) + ).distribute( + (items.len / nColumns).int + 1 + ).mapIt(it.join("")).join("\n") + + + diff --git a/src/ccnzcc.nim b/src/ccnzcc.nim new file mode 100644 index 0000000..6cb4bfc --- /dev/null +++ b/src/ccnzcc.nim @@ -0,0 +1,3 @@ +import ccnz/utils + +callZig("cc")