How to compile a lablgtk3 program from command-line without using dune

I was unable to find the exact syntax to compile a lablgtk3 program from command line.
I.e. having the correct include paths and library paths.
With “dune build @all” i can build all examples but i need to know which commands are executed by dune, or have correct includes,link libraries.

You can have a look at _build/log to see what commands got executed by dune. (or you can pass --display verbose to have it display that as it’s running the build)

Thanks but this gives me no clue at all what i should put on the commandline for ocamlc…
Here is the logfile, which only gives the “context” of dune.
HOST:x: /home/x/lablgtk/_build >cat log

# dune build -f @all
# OCAMLPARAM: unset
# Workspace root: /usr/home/x/lablgtk
$ /usr/bin/getconf _NPROCESSORS_ONLN > /tmp/dune9a3194.output 2> /dev/null
# Auto-detected concurrency: 8
# disable binary cache
$ /usr/home/x/.opam/4.13.1+options/bin/ocamlc.opt -config > /tmp/duned975d6.output
# Dune context:
#  { name = "default"
#  ; kind = "default"
#  ; profile = Dyn
#  ; merlin = true
#  ; for_host = None
#  ; fdo_target_exe = None
#  ; build_dir = "default"
#  ; toplevel_path =
#      Some External "/usr/home/x/.opam/4.13.1+options/lib/toplevel"
#  ; ocaml_bin = External "/usr/home/x/.opam/4.13.1+options/bin"
#  ; ocaml = Ok External "/usr/home/x/.opam/4.13.1+options/bin/ocaml"
#  ; ocamlc = External "/usr/home/x/.opam/4.13.1+options/bin/ocamlc.opt"
#  ; ocamlopt = Ok External "/usr/home/x/.opam/4.13.1+options/bin/ocamlopt.opt"
#  ; ocamldep = Ok External "/usr/home/x/.opam/4.13.1+options/bin/ocamldep.opt"
#  ; ocamlmklib =
#      Ok External "/usr/home/x/.opam/4.13.1+options/bin/ocamlmklib.opt"
#  ; env =
#      map
#        { "DUNE_OCAML_HARDCODED" : "/usr/home/x/.opam/4.13.1+options/lib"
#        ; "DUNE_OCAML_STDLIB" : "/usr/home/x/.opam/4.13.1+options/lib/ocaml"
#        ; "DUNE_SOURCEROOT" : "/usr/home/x/lablgtk"
#        ; "INSIDE_DUNE" : "/usr/home/x/lablgtk/_build/default"
#        ; "MANPATH" : "/usr/home/x/lablgtk/_build/install/default/bin"
#        ; "OCAMLFIND_IGNORE_DUPS_IN" :
#            "/usr/home/x/lablgtk/_build/install/default/lib"
#        ; "OCAMLPATH" : "/usr/home/x/lablgtk/_build/install/default/lib"
#        ; "OCAMLTOP_INCLUDE_PATH" :
#            "/usr/home/x/lablgtk/_build/install/default/lib/toplevel"
#        ; "OCAML_COLOR" : "always"
#        ; "OPAMCOLOR" : "always"
#        }
#  ; findlib_path = [ External "/usr/home/x/.opam/4.13.1+options/lib" ]
#  ; arch_sixtyfour = true
#  ; natdynlink_supported = true
#  ; supports_shared_libraries = true
#  ; ocaml_config =
#      { version = "4.13.1"
#      ; standard_library_default =
#          "/usr/home/x/.opam/4.13.1+options/lib/ocaml"
#      ; standard_library = "/usr/home/x/.opam/4.13.1+options/lib/ocaml"
#      ; standard_runtime = "the_standard_runtime_variable_was_deleted"
#      ; ccomp_type = "cc"
#      ; c_compiler = "gcc"
#      ; ocamlc_cflags =
#          [ "-O2"; "-fno-strict-aliasing"; "-fwrapv"; "-pthread"; "-fPIC" ]
#      ; ocamlc_cppflags = [ "-D_FILE_OFFSET_BITS=64" ]
#      ; ocamlopt_cflags =
#          [ "-O2"; "-fno-strict-aliasing"; "-fwrapv"; "-pthread"; "-fPIC" ]
#      ; ocamlopt_cppflags = [ "-D_FILE_OFFSET_BITS=64" ]
#      ; bytecomp_c_compiler =
#          [ "gcc"
#          ; "-O2"
#          ; "-fno-strict-aliasing"
#          ; "-fwrapv"
#          ; "-pthread"
#          ; "-fPIC"
#          ; "-D_FILE_OFFSET_BITS=64"
#          ]
#      ; bytecomp_c_libraries = [ "-lm"; "-lpthread" ]
#      ; native_c_compiler =
#          [ "gcc"
#          ; "-O2"
#          ; "-fno-strict-aliasing"
#          ; "-fwrapv"
#          ; "-pthread"
#          ; "-fPIC"
#          ; "-D_FILE_OFFSET_BITS=64"
#          ]
#      ; native_c_libraries = [ "-lm" ]
#      ; cc_profile = []
#      ; architecture = "amd64"
#      ; model = "default"
#      ; int_size = 63
#      ; word_size = 64
#      ; system = "freebsd"
#      ; asm = [ "gcc"; "-c" ]
#      ; asm_cfi_supported = true
#      ; with_frame_pointers = false
#      ; ext_exe = ""
#      ; ext_obj = ".o"
#      ; ext_asm = ".s"
#      ; ext_lib = ".a"
#      ; ext_dll = ".so"
#      ; os_type = "Unix"
#      ; default_executable_name = "a.out"
#      ; systhread_supported = true
#      ; host = "x86_64-unknown-freebsd13.0"
#      ; target = "x86_64-unknown-freebsd13.0"
#      ; profiling = false
#      ; flambda = false
#      ; spacetime = false
#      ; safe_string = true
#      ; exec_magic_number = "Caml1999X030"
#      ; cmi_magic_number = "Caml1999I030"
#      ; cmo_magic_number = "Caml1999O030"
#      ; cma_magic_number = "Caml1999A030"
#      ; cmx_magic_number = "Caml1999Y030"
#      ; cmxa_magic_number = "Caml1999Z030"
#      ; ast_impl_magic_number = "Caml1999M030"
#      ; ast_intf_magic_number = "Caml1999N030"
#      ; cmxs_magic_number = "Caml1999D030"
#      ; cmt_magic_number = "Caml1999T030"
#      ; natdynlink_supported = true
#      ; supports_shared_libraries = true
#      ; windows_unicode = false
#      }
#  }

The log file only contains commands from the previous run. You can call dune clean and rebuild to get a full run.

Now i have something more to work with. But it’s a long command,
cat log | grep entry.ml

$ (cd _build/default && /usr/home/x/.opam/4.13.1+options/bin/ocamldep.opt -modules -impl examples/entry.ml) > _build/default/examples/.about.eobjs/entry.ml.d
$ (cd _build/default && /usr/home/x/.opam/4.13.1+options/bin/ocamlc.opt -w @1..3@5..28@30..39@43@46..47@49..57@61..62-40 -strict-sequence -strict-formats -short-paths -keep-locs -w -3-6-7-10-24-26-27-33-35 -no-strict-sequence -g -bin-annot -I examples/.about.eobjs/byte -I /usr/home/x/.opam/4.13.1+options/lib/cairo2 -I /usr/home/x/.opam/4.13.1+options/lib/ocaml/threads -I src/.lablgtk3.objs/byte -no-alias-deps -opaque -o examples/.about.eobjs/byte/entry.cmo -c -impl examples/entry.ml)
$ (cd _build/default && /usr/home/x/.opam/4.13.1+options/bin/ocamlopt.opt -w @1..3@5..28@30..39@43@46..47@49..57@61..62-40 -strict-sequence -strict-formats -short-paths -keep-locs -w -3-6-7-10-24-26-27-33-35 -no-strict-sequence -g -I examples/.about.eobjs/byte -I examples/.about.eobjs/native -I /usr/home/x/.opam/4.13.1+options/lib/cairo2 -I /usr/home/x/.opam/4.13.1+options/lib/ocaml/threads -I src/.lablgtk3.objs/byte -I src/.lablgtk3.objs/native -intf-suffix .ml -no-alias-deps -opaque -o examples/.about.eobjs/native/entry.cmx -c -impl examples/entry.ml)

You didn’t explained why to do not want to use dune. Do you plan to export your project where dune is not available ?

2 Likes

Recommendation: don’t try to do it without dune :slightly_smiling_face:

You can get something much more reasonable using ocamlfind:

# Compile test.ml
$ ocamlfind ocamlopt -package lablgtk3 -thread -c test.ml
# Find the actual command used
$ ocamlfind ocamlopt -only-show -package lablgtk3 -thread -c test.ml
ocamlopt.opt -c -thread -I /home/x/.opam/4.14.0/lib/cairo2 -I /home/x/.opam/4.14.0/lib/lablgtk3 test.ml

(Note: if you don’t add the -thread flag, ocamlfind will helpfully suggest that you might actually need it).

Unlike dune, it’s not a build system, so you can’t use it to find the commands involved in building a whole project, but maybe that’s still useful for you.

I tried dune (please note i’m learning).
The hello.ml file
cat hello.ml

(**************************************************************************)
(*    Lablgtk - Examples                                                  *)
(*                                                                        *)
(*    This code is in the public domain.                                  *)
(*    You may freely copy parts of it in your application.                *)
(*                                                                        *)
(**************************************************************************)

(* $Id$ *)

let _ = GMain.init ()

let window = GWindow.window ~border_width: 10 ()

let button = GButton.button ~label:"Hello World" ~packing: window#add ()

let main () =
  window#event#connect#delete 
    ~callback:(fun _ -> prerr_endline "Delete event occured"; true);
  window#connect#destroy ~callback:GMain.quit;
  button#connect#clicked ~callback:(fun () -> prerr_endline "Hello World");
  button#connect#clicked ~callback:window#destroy;
  window#show ();
  GMain.main ()
let _ = Printexc.print main ()

The dune config file
cat dune

(executable
 (name hello))

The compile script:

dune clean
dune build hello.exe

This results in:

./compile 
File "hello.ml", line 11, characters 8-18:
11 | let _ = GMain.init ()
             ^^^^^^^^^^
Error: Unbound module GMain

I think i must in the dune config file tell to link to lablgtk.
What is a dune helloworld lablgtk config file ?

This worked , in the sense it created a correct test.o file.
My current problem is how to build an executable, i.e. link to the correct libraries.

ocamlfind ocamlopt -package lablgtk3 -thread -c hello.ml
ocamlopt hello.o

Produces:

usr/local/bin/ld: hello.o: in function `camlHello__code_begin':
:(.text+0x27): undefined reference to `caml_send0'
/usr/local/bin/ld: :(.text+0x40): undefined reference to `caml_send0'
/usr/local/bin/ld: :(.text+0x62): undefined reference to `caml_send1'
/usr/local/bin/ld: :(.text+0x7e): undefined reference to `caml_send0'
/usr/local/bin/ld: :(.text+0x85): undefined reference to `camlGMain'
/usr/local/bin/ld: :(.text+0xa2): undefined reference to `caml_send1'
/usr/local/bin/ld: :(.text+0xbf): undefined reference to `caml_send0'
/usr/local/bin/ld: :(.text+0xe1): undefined reference to `caml_send1'
/usr/local/bin/ld: :(.text+0xfe): undefined reference to `caml_send0'
/usr/local/bin/ld: :(.text+0x11e): undefined reference to `caml_send0'
/usr/local/bin/ld: :(.text+0x140): undefined reference to `caml_send1'
/usr/local/bin/ld: :(.text+0x15f): undefined reference to `caml_send1'
/usr/local/bin/ld: :(.text+0x166): undefined reference to `camlGMain'
/usr/local/bin/ld: hello.o: in function `camlHello__entry':
:(.text+0x1f7): undefined reference to `camlGtkMain__init_inner_1004'
/usr/local/bin/ld: :(.text+0x2bc): undefined reference to `camlGWindow__window_1184'
/usr/local/bin/ld: :(.text+0x372): undefined reference to `caml_apply18'
/usr/local/bin/ld: :(.text+0x3f1): undefined reference to `camlGButton__button_1054'
/usr/local/bin/ld: :(.text+0x416): undefined reference to `caml_apply6'
/usr/local/bin/ld: :(.text+0x43d): undefined reference to `camlStdlib__Printexc__print_245'
collect2: error: ld returned 1 exit status
File "caml_startup", line 1:
Error: Error during linking (exit code 1)

I assume that there’s some documentation on ocamlfind somewhere that explains all of that, but my own take would be ocamlfind ocamlopt -package lablgtk3 -thread -linkpkg hello.o. You can then add -only-show if you want to see the actual command-line. (Roughly speaking, -package by default only adds the compilation-related flags (mostly include paths), but if you add the -linkpkg option then it also adds the required files/flags for linking for all the packages you’ve specified).
If you want to specify the name of the executable file you can do it with the -o flag.
You can also create an executable directly from a source file (or source files):

ocamlfind ocamlopt -package lablgtk3 -thread -linkpkg helper.ml hello.ml -o hello.exe

You can tell dune to use the dependency:

(executable
 (name hello)
 (libraries lablgtk3))

As you noted earlier, the examples were all compiling. This was the missing piece. I recommend taking a few minutes to read the dune quick-start guide: Quickstart — dune documentation

1 Like

This file should works:

(executable
 (name hello)
 (libraries 
   lablgtk3
   )
 )

dune config file:

(executable
 (name entry)
 (libraries 
   lablgtk3
   )
 )

entry.ml:
https://raw.githubusercontent.com/garrigue/lablgtk/lablgtk3/examples/entry.ml

compile script

dune clean
dune build entry.exe

Results in:

File "entry.ml", line 24, characters 2-15:
24 |   GMain.init ();
       ^^^^^^^^^^^^^
Error: This expression has type string but an expression was expected of type
         unit
       because it is in the left-hand side of a sequence

This worked. Thanks alot. It’s not even a long line, but not obvious for the first time.

That function returns a string, in all the examples it’s used similarly to this:

let main () =
  let _ = GMain.init () in
  ...

let () = main ()
2 Likes

This error is normal. The function GMain.init return a string, which is ignored in the examples providen by lablgtk3.

If you want to discard to result, you can either write:

ignore @@ GMain.init (); (* The function ignore is given is stdlib *)

or

let _ = GMain.init (); (* assign the result in an anonymous variable *)

By default, dune will raise an error for all thoses kind of warning (which is a good thing), but ignore them when you compile your code with --profile release.

As a beginner to OCaml, you will face with a lot a errors from the compiler which as no tolerance and expect you to write the perfect code. Yes it’s hard, but all thoses kind of messages are just here to point an error in the code. Everybody here has begun a day, do not desespair !

1 Like

I’ve edited entry.ml a bit , now dune compiles it fine. Below the code
(neovim with lsp-server was a real help)
cat entry.ml

open Printf

let enter_callback entry =
    let _ = printf "Entry contents: %s\n" entry#text in
    flush stdout

let entry_toggle_editable button entry = entry#set_editable button#active
let entry_toggle_visibility button entry = entry#set_visibility button#active

let main () =
    let _ = GMain.init () in
    let window = GWindow.window ~title:"GTK Entry" ~width:200 ~height:100 () in
    let _ = window#connect#destroy ~callback:GMain.quit in

    let vbox = GPack.vbox ~packing:window#add () in

    let entry = GEdit.entry ~max_length:50 ~packing:vbox#add () in
    let _ = entry#connect#activate ~callback:(fun () -> enter_callback entry) in
    let _ = entry#set_text "Hello" in
    (* Appending text now requires getting the underlying buffer, and
     * entry#buffer is not exposed in the bindings yet *)
    (*entry#append_text " world";*)
    let _ = entry#select_region ~start:0 ~stop:entry#text_length in

    let hbox = GPack.hbox ~packing:vbox#add () in

    let check =
        GButton.check_button ~label:"Editable" ~active:true ~packing:hbox#add ()
    in
    let _ =
        check#connect#toggled ~callback:(fun () ->
            entry_toggle_editable check entry)
    in

    let check =
        GButton.check_button ~label:"Visible" ~active:true ~packing:hbox#add ()
    in
    let _ =
        check#connect#toggled ~callback:(fun () ->
            entry_toggle_visibility check entry)
    in

    let button = GButton.button ~label:"Close" ~packing:vbox#add () in
    let _ = button#connect#clicked ~callback:window#destroy in
    let _ = button#grab_default () in

    let _ = window#show () in

    let _ = GMain.main () in
    ()

let () = main ()

Off-course now i want to create my own class widgets.
This link is largely outdated but gives me a bit of idea,
https://www2.ocaml.org/learn/tutorials/introduction_to_gtk.html