I need to compile proto files and generate pb files dynamically. I have my protoc commands in a Makefile. I am planning to trigger this from golang init() function.
How can I call this Makefile from my golang code? Tried finding solution, but all of them suggest how I can achieve this the other way round.
Thanks.
As a continuation to what Muffin Top mentioned, I achieved this using the below code:
import (
"bytes"
"fmt"
"log"
"os/exec"
)
func init() {
e := exec.Command("make", "all")
var out bytes.Buffer
e.Stdout = &out
err := e.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Output: %q\n", out.String())
}
You should use the go:generate syntax:
package main
//go:generate protoc bla bla bla
When you run your "go build" command, it will then run everything in your code prefixed with //go:generate
https://golang.org/pkg/cmd/go/internal/generate/
Related
Folder structure:
- dev.env
- go.mod
- main.go
dev.env:
ENV="DEV"
PASSWORD="DEV!##$%"
main.go:
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/joho/godotenv"
)
var environment string
func init() {
fmt.Println("ENV: ", environment)
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal(err)
}
devEnvPath := filepath.Join(dir, "dev.env")
_ = godotenv.Load(devEnvPath)
}
func main() {
fmt.Println("PASSWORD", os.Getenv("PASSWORD"))
}
Command:
go run -ldflags="-X 'main.environment=DEV'" .
Output:
ENV: DEV
PASSWORD
It works with go build but curious why it doesn't work with go run
As a general rule, don't use go run unless in the most trivial of use cases - it is the most common footgun in the Go community.
As #Marc pointed out the error stems from the go run binary is built in a temporary directory. To keep things simple, just use a relative path in your directory. This will work for both go build & go run:
dir := "."
devEnvPath := filepath.Join(dir, "dev.env")
err := godotenv.Load(devEnvPath)
if err != nil {
log.Fatal(err)
}
As of Go 1.16, the simplest way to access files from the directory containing a package's source code is to use //go:embed and the embed package to embed those files into the compiled binary:
//go:embed dev.env
var devEnv string
func init() {
m, err := godotenv.Unmarshal(devEnv)
…
}
What is the syntax so go generate can pipe stdout from go run to gofmt and ultimately to a file? Below is simple example of what I have tried. Its in the file main.go. I can't find any examples of this after searching. Thank you.
Edit: ultimately I would like to use go generate and have it write a formatted file.
//go:generate go run main.go | go fmt > foo.go
package main
import "fmt"
const content = `
package main
func foo() string {return "Foo"}
`
func main() {
fmt.Print(content)
}
Use the format package directly instead of running a shell:
//go:generate go run main.go
package main
import (
"go/format"
"io/ioutil"
"log"
)
const content = `
package main
func foo() string {return "Foo"}
`
func main() {
formattedContent, err := format.Source([]byte(content))
if err != nil {
log.Fatal(err)
}
err = ioutil.WriteFile("foo.go", formattedContent, 0666)
if err != nil {
log.Fatal(err)
}
}
Avoid using a shell like bash because the shell may not be available on all systems where the Go tools run.
I need to read golang code from the play.golang.org link and save to a .go file. I'm wondering if there is any public API support for play.golang.org. I googled but no hints. Has anyone attempted anything similar?
To get the text of a shared playground program, append ".go" to the URL. For example, you can get the text of the program at https://play.golang.org/p/HmnNoBf0p1z with https://play.golang.org/p/HmnNoBf0p1z.go.
You can upload a program by posting the program text to https://play.golang.org/share. The response is the ID of the shared program. This program uploads stdin to the playground and prints the ID of the uploaded program to stdout:
package main
import (
"io"
"log"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("POST", "https://play.golang.org/share", os.Stdin)
if err != nil {
log.Fatal(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
}
Assuming the above program is in upload.go, the following shell script prints HmnNoBf0p1z.
go run upload.go << EOF
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, playground")
}
EOF
If you want to download the program as a file using a browser, then add the query ?download=true to the .go URL. Example: https://play.golang.org/p/HmnNoBf0p1z.go?download=true
There are two ways that I know one of which is described by #ThunderCat. Another simple solution is go to the URL https://play.golang.org/p/HmnNoBf0p1z and Press Ctrl+save on the page it will be downloaded as a .go file.
I need to run a variable as shell script file in golang. I tried like below code
package main
import (
"fmt"
"os/exec"
)
func main() {
var namespaceYaml string = `#!/bin/bash
docker verson`
out, err := exec.Command(namespaceYaml).Output()
fmt.Println(err, string(out))
}
But I cannot get any result. I cannot find where is the mistake.
Please anyone to fix this issue. Thanks in advance.
From official doc:
func Command(name string, arg ...string) *Cmd
Command returns the Cmd struct to execute the named program with the given arguments.
Try this:
package main
import (
"fmt"
"os/exec"
)
func main() {
out, err := exec.Command("docker", "version").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Docker version is: %s\n", out)
}
Useful links for further details:
exec
examples
Note: make sure docker is installed on your machine.
So this is the example from Programming in Go by Mark Summerfield.
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
var britishAmerican = "british-american.txt"
func init() {
dir, _ := filepath.Split(os.Args[0])
britishAmerican = filepath.Join(dir, britishAmerican)
}
func main() {
rawBytes, err := ioutil.ReadFile(britishAmerican)
if err != nil {
fmt.Println(err)
}
text := string(rawBytes)
usForBritish := make(map[string]string)
lines := strings.Split(text, "\n")
fmt.Println(lines)
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) == 2 {
usForBritish[fields[0]] = fields[1]
}
}
fmt.Println(usForBritish)
}
When I run this code with the init() func commented out, it works perfectly fine. If I leave it in I get this error:
open /var/folders/l6/rdqtyrfd303dw1cz8qvlfcvc0000gn/T/go- build652175567/command-line-arguments/_obj/exe/british-american.txt: no such file or directory exit status 1
My question is, why does the init() func not grab the file from the appropriate directory?
You change the variable britishAmerican in the init function. Without init(), the program looks in the current directory (no path given, only the file name). With init(), it looks in the path where the executable is (os.Args[0]). And with go run main.go, the directory with the executable is not the current working directory.
You should use go build to build the binary and then run it, or you should tell us what you want to achieve (as written by #RoninDev).
The MCVE I've mentioned could look like this:
package main
import (
"io/ioutil"
"log"
"os"
"path/filepath"
)
var filename = "foo.txt"
func init() {
// change to true and things break
if false {
dir, _ := filepath.Split(os.Args[0])
filename = filepath.Join(dir, filename)
}
}
func main() {
// requires a file 'foo.txt' in the current directory
_, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
}
It can (of course) be even shorter, but this should be enough for the others in the community to see what is going on.
It looks to me like the program is expecting a file called british-american.txt in the directory that the executable is in.
That is what the code in init() does - it finds the path the the executable and constructs a path to the dictionary relative to that.
I can see from your error message that you are using go run to run the code. This makes a temporary executable in /tmp and runs that. If you leave the init() code in then it will look for the dictionary in the /tmp directory and it won't find it. If you take the init() code out it will look for the dictionary in the current directory and it will succeed.
If you want to use it as the author intended then use go build to build a binary and then run it - that will work.