openpeeps / denim

Node 💖 Nim = Denim! Build powerful NodeJS / BunJS addons with Nim language via Node API (NAPI)
https://openpeeps.github.io/denim/
MIT License
41 stars 1 forks source link

Build binding.gyp given a package name #1

Closed jfilby closed 1 year ago

jfilby commented 1 year ago

It would be very useful if Denim included a utility to build a binding.gyp file given a package name.

I would also like to know: must I list every .c file in the project's cache? Or only the main .c file? Thanks.

georgelemon commented 1 year ago

Just posted on nim forum. Here is a fully working example.

Denim is a hybrid package. Check ~/.nimble/denim after installation.

Simply denim -h

DENIM 🔥 Native Node/BunJS addons powered by Nim

  build <entry> <links> --release

Create a hybrid project via nimble init, then

import myprogram/submodule

when defined napibuild:
  # Running `denim build myprogram.nim` compiles with `-d:napibuild`
  import denim # NAPI bindings

  import jsony
  from strutils import `%`

  type
    User = object
      name: string
      enabled: bool

  init proc(module: Module) =
    # `Env` singleton represent the current `napi_env` context.
    module.registerFn(0, "getWelcomeMessage"):
      # Create and export a function named `getWelcomeMessage` with no arguments
      return %* getWelcomeMessage()

    module.registerFn(3, "testWithArgs"):
      # a function with 3 arguments.

      if not Env.expect(args, "MyProgram", ("name", napi_string), ("city", napi_string), ("?zipcode", napi_number)): return

      # retrieve `napi_value` values. ugly too.
      # todo macro to `args.has("zipcode")` and `args.get("zipcode")`
      let
        name = args[0].getStr
        city = args[1].getStr
        zipcode = if args.len == 3: $(args[2].getInt) else: "n/a"
      let output = "Name: $1\nCity: $2\nZipcode: $3" % [name, city, zipcode]
      return %* output

    module.registerFn(1, "testNapiCall"):
      if not Env.expect(args, "ErrorName", ("name", napi_string)): return
      var user = User(name: args[0].getStr, enabled: true)
      return napiCall("JSON.parse", [%* toJson(user)])

elif isMainModule:
  echo(getWelcomeMessage())

Basically, denim build combines Nim compilation + NodeGYP

denim build src/myprogram.nim

This will generate a dir called denim_build in root of your project (contains necessary c sources and binding.gyp - this directory gets flushed everytime you run denim build).

For myprogram.node check in myprogram/bin directory

JavaScript

const nim = require('./myprogram.node')
console.log(nim) // {}

console.log(nim.getWelcomeMessage()) // Hello, World!

// nim.testWithArgs()
// Error: Type mismatch parameter: `name`. Got `undefined`, expected `string`

// nim.testWithArgs("J. Doe")
// Error: Type mismatch parameter: `city`. Got `undefined`, expected `string`

let x = nim.testWithArgs("J. Doe", "London") // works because `zipcode` is optional
console.log(x)
// nim.testWithArgs("Doe Again", "London", true) // ...error Got `bool`, expected `number``  

let user = nim.testNapiCall("John Do the Do")
console.log(user) // {...}
console.log(user.name) // John Do the Do

What will the High-level API be like? Is it worth waiting for before I start?

Anything that can make code more readable (no ideas yet). A good start would be type definition (when calling registerFn. Anyway, the low-level API is pretty stable, you can give a try.

On my todo list for cli:

  1. Allow passing any flags to Nim/C compiler
  2. Generate a JS index loader to detect the platform/load the corresponding binary module
  3. Command to generate a package.json
  4. Command publish to NPM. Here I need some inspiration. Maybe rust-to-npm
  5. Denim should have its own directory, next to .nimble, where will store generated c sources and node-gyp files.
  6. Allow linking external C headers (binding.gyp) ref
georgelemon commented 1 year ago

The current README is pretty dumb, I will add useful examples soon. Also, I should highlight that Denim is both a library and a CLI toolkit (maybe using a carbon.now.sh screen)

jfilby commented 1 year ago

Closing this, I understand how to build the add-on now. Thanks.