Setting CGO_CFLAGS persistently in a Go package? - go

My Go package includes a .c file that uses a library which needs certain CFLAGS set. On the command line to "go install" I can specify CGO_CFLAGS with the needed flags, and everything works. However, I would like to make it so that someone can "go get" my package and build it without passing any extra command line arguments.
Does the Go packaging system provide a place where I could put some config like this, to specify some arguments that are always needed when go installing a package?
(I'm aware of doing #cgo CFLAGS: directives in Go source files, but recall that in my package I have a .c source file so need the CGO_CFLAGS setting to the overall build process)

cgo extracts your #cgo CFLAGS: to an environment variable during build (pass "-x" flag to go build). If you go install -x you see that it honors your cflags/ldflags specified in your Go library. In other words, it should work to just specify them in your Go files.

In order to use any of your C functions you still have to mix some cgo into your .go files, in those files just declare your flags, for example:
test.go:
package main
import "fmt"
/*
#cgo CFLAGS: -DTEST
#include <stdio.h>
extern void ACFunction();
*/
import "C"
//export AGoFunction
func AGoFunction() {
fmt.Println("AGoFunction()")
}
func main() {
C.ACFunction()
}
test.c:
#include "_cgo_export.h"
void ACFunction() {
#ifdef TEST
printf("ACFunction()\n");
#endif
AGoFunction();
}
Putting these in the same directory will make go build pickup the flags defined in test.go and apply them when building test.c.

Related

How to resolve circular dependencies when using go modules and cgo

In my project, I am using callbacks for bi-directional calls from C into go and vice versa using CGO. I resolved the issue of circular dependencies by compiling the C part into a library, then compiling the go part into a library, then a final linker pass puts it all together. This is working fine when not using go modules. Go source files are listed on the command line explicitly. I have been told that as of go 1.12 "this is not the right way to do it".
As the project has grown, I now want to use go modules. Unfortunately, this changes the behaviour of the go compiler. It now wants to resolve external dependencies and implicitly includes them in the output file. Due to the circular dependency, it now always ends up with an undefined reference or multiple definitions. How to resolve circular dependencies when using cgo and go modules "the right way"?
This is a minimal example to illustrate the problem. Remove the file-name "hello.go" from the call to go in the Makefile to see how it falls apart.
This is the error message:
hello.c:3: multiple definition of `c_hello'; $WORK/b001/_cgo_hello.o:/tmp/go-build/hello.c:3: first defined here
Makefile:
libchello.a: Makefile hello.c
gcc -fPIC -c -o chello.o hello.c
ar r libchello.a chello.o
libgohello.a: Makefile hello.go libchello.a
env CGO_LDFLAGS=libchello.a go build -buildmode=c-archive -o libgohello.a hello.go
main: Makefile main.c libgohello.a libchello.a
gcc -o main main.c libchello.a libgohello.a -pthread
.PHONY: clean
clean:
rm -f main *.a *.o
echo "extern void go_hello();" > libgohello.h
hello.go:
package main
/*
extern void c_hello();
*/
import "C"
import "time"
import "fmt"
//export go_hello
func go_hello() {
fmt.Printf("Hello from go\n")
time.Sleep(1 * time.Second)
C.c_hello()
}
func main() {}
libgohello.h:
extern void go_hello();
hello.c:
#include "libgohello.h"
#include <stdio.h>
void c_hello() {
printf("Hello from c\n");
go_hello();
}
main.c:
void c_hello();
int main() {
c_hello();
}
go.mod:
module hehoe.de/cgocircular
If you look at the verbose output from the go build command, you will see that when compiling the directory as a complete go package, the main.c file is being included as part of the C code used in hello.go.
From the documentation:
When the Go tool sees that one or more Go files use the special import "C", it will look for other non-Go files in the directory and compile them as part of the Go package
The easiest solution here is to separate the main C and Go packages, so that they don't interfere with each other's build process. Testing this out, removing the main.c file will build libchello.a and libgohello.a, and then adding it back in will complete the build of main.

Cgo "undefined reference" (gstreamer)

I am writing something using cgo to interact with the gstreamer-1.0 library. I have everything almost working perfectly, but for some reason an entire header file's objects are not getting imported correctly.
go version go1.15.2 linux/amd64 for whatever that is worth
package main
// #cgo pkg-config: gstreamer-1.0
// #cgo CFLAGS: -Wno-deprecated-declarations
// #include <gst/gst.h> // using this file extensively with no issues
// #include <gst/app/gstappsink.h> // objects in this file are not getting read, but the compiler is having no issue reading it
import "C"
func init () { C.gst_init(nil, nil) }
func main () {
// ...
C.gst_app_sink_pull_sample() // a non-variadic function that does take args
// but cgo doesn't even think it exists.
// ...
}
The error back from the compiler: /tmp/go-build/cgo-gcc-prolog:64: undefined reference to 'gst_app_sink_pull_sample'
I've looked at the header file and gst_app_sink_pull_sample is indeed there. I can reproduce this both trying to build locally and in the golang docker container.
If I remove the include entirely the error is different: could not determine kind of name for C.gst_app_sink_pull_sample.
So am I the problem or is gstreamer the problem?
The appsrc and appsink symbols are not part of the base gstreamer library. Instead they are found in the extra library gstreamer-app-1.0. Add this library to your cgo pkgconfig line and it should find the missing symbols.
"undefined reference to xxx" means the C compiler of cgo recognize the definitions but it can't find the implementations (covered by corresponding C libraries)
This indicates that you have your C header files imported correctly. To solve the undefined reference problem, you just have to add some thing as below if your dynamic library is called libgstreamer.so.1.0.0
# cgo LDFLAGS: -lgstreamer

Preprocessor flag to detect CGO build?

In a c file that's beside my go file and is compiled together via CGO, I'd like to check via preprocessor whether it's being compiled via go or not. I'd like to do this because, for example, I'd like to protect #include _cgo_export.h with #ifdef flags, since such header exists solely during compilation and I don't want my editor to warn about its absence.
From the documentation just do:
// #cgo CFLAGS: -DWHATEVER_YOU_WANT_TO_INDICATE_CGO=1
import "C"
(or just -D FOO if you don't want a value) or set CGO_CFLAGS in the environment.
You can see what is happening behind the scenes with go build -x.
For me it shows -D GOOS_freebsd -D GOARCH_amd64 while compiling the cgo generated _cgo_defun.c file, but only for that file and not to my own *.c files. So I don't think there are any usable predefined preprocessor flags (and the documentation also doesn't mention any).

Override an external package's cgo compiler and linker flags?

Let's say I want to use some awesome go package. I can include it by:
import "github.com/really-awesome/project/foobar"
And inside that project's foobar.go file, it defines some cgo instructions like:
#cgo windows CFLAGS: -I C:/some-path/Include
#cgo windows LDFLAGS: -L C:/some-path/Lib -lfoobar
But if I have that foobar C dependency installed somewhere else, I would really need those lines to say:
#cgo windows CFLAGS: -I C:/different-path/Include
#cgo windows LDFLAGS: -L C:/different-path/Lib -lfoobar
Is there a way to override or trump where cgo is looking for these dependencies? Right now my fix is to manually edit those two lines after running go get ./... which will fetch the github.comreally-awesome/project/foobar code.
NOTE: I'm using the MinGw compiler, though I doubt that matters.
update:
I have tried adding flags to go build to no avail:
go build -x -gcflags="-I C:/different/include -L C:/different-path/lib -lfoobar"
go build -x -ccflags="-I C:/different/include" -ldflags="-L C:/different-path/lib -lfoobar"
With the -x argument I see the printout of flags and they don't include the ones I am setting on the command line. Perhaps the #cgo CFLAGS/LDFLAGS statements at the top of the external go package squash what I am telling it to use...
You can do this by setting the CGO_CPPFLAGS and CGO_LDFLAGS environment variables.
For example, on my MacBook, Homebrew is installed in ~/.homebrew (instead of /usr/local), so when I try to go get packages with native bindings they can't find the headers and libs.
To fix that I added these two lines to my ~/.zshenv file:
export CGO_CPPFLAGS="-I $BREW_HOME/include"
export CGO_LDFLAGS="-L $BREW_HOME/lib"
This is kind of the role filled by #cgo pkgconfig: foobar. If the library had been written that way, it would pick up the correct paths from foobar's pkgconfig definition.
I realise its not a direct answer to the question, and that pkgconfig isn't exactly a native windows tool... I'd be interested to hear if any other solutions exist.

go build doesn't find my C standard library when compiling cgo package

I'm trying to compile a go project in a raspberry pi.
The project has 5 files, two small .c files and its counterparts .h (one of these files is my code -- it calls the other, which is a base64 library) and a .go files which calls my .c code using cgo.
When I compile my C code only (with its calls and everything) with gcc alone at the raspberry pi it does well without any configuration.
When I compile the entire go project on my x86 Linux Ubuntu machine with go build, it also does pretty well.
But when I try to compile the go project with go build in the raspberry pi it doesn't get my C libraries:
fiatjaf#raspberrypi ~/g/s/b/f/project> go build -x
WORK=/tmp/go-build702187084
mkdir -p $WORK/bitbucket.org/fiatjaf/project/_obj/
cd /home/fiatjaf/go/src/bitbucket.org/fiatjaf/project
/usr/lib/go/pkg/tool/linux_arm/5c -FVw -I $WORK/bitbucket.org/fiatjaf/project/_obj/ -I /usr/lib/go/pkg/linux_arm -o $WORK/bitbucket.org/fiatjaf/project/_obj/base64.5 -DGOOS_linux -DGOARCH_arm ./base64.c
# bitbucket.org/fiatjaf/project
./base64.c:2 5c: No such file or directory: math.h
(If I put the <stdlib.h> before the <math.h> the problem occurs for it too, so the problem is not the absence of math.h, I think)
I tried to:
add // #cgo CFLAGS: -I/usr/include to the .go file
add // #cgo LDFLAGS: -I/usr/include (I can't discover what is the proper usage of these flags)
use go build -ldflags '-I/usr/include'
I don't understand why go is trying to compile base64.c with -I /usr/lib/go/pkg/linux_arm. Really don't. Someone help.
EDIT: Clarifying note about the structure of the project:
It has 5 files, 2 C (and its counterparts H):
base64.c
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
... // definitions of functions used at project.c
project.c
#include <stdlib.h>
#include <string.h>
#include "base64.h"
... // functions used at project.go
and 1 Go:
...
// #include <stdlib.h>
// #include <string.h>
// #include "project.h"
// #cgo CFLAGS: -I/usr/include
// #cgo LDFLAGS: -lm
import "C"
...
Where, what and how should I change in this declarations for this thing to work? And why did it worked on my x86 linux?
cgo inline syntax
The correct syntax for cgo parameters in the go file is this:
// #cgo CFLAGS: -I/usr/include
without the whitespace between # and cgo. See cmd/cgo for details on the syntax.
-ldflags parameter
The go -ldflags parameter passes parameters to the go linkers (5l, 6l, 8l, ...).
Even if the parameter would be passed to the C linker, this wouldn't do you any good
as the linker does not handle includes, the compiler does.
I'm afraid that this parameter won't help you here. All relevant parameters should
be configured in the go source file using the #cgo tags.
misc. notes
If you're using math.h you most likely need to link libmath. You can do this
by writing this to your go source file:
// #cgo LDFLAGS: -lm
It seems my problem was something related to not having set the CGO_ENABLED flag.
I don't know for sure, but it seems, because I uninstalled my Go from the Raspbian repositories (which seems to come with CGO disabled by default) and installed Go from source (just like I had made in my x86 Linux) then it started to work.

Resources