7

For now I used runtime.Caller(0) in combination with path.Dir and filepath.Abs to get the path to the currently executed file and get the project root relative to it.

So lets say I've a folder structure like this:

$GOPATH/src/example.org/myproject
$GOPATH/src/example.org/myproject/main.go
$GOPATH/src/example.org/myproject/path
$GOPATH/src/example.org/myproject/path/loader.go

If I want my project root I call the loader.go which in turn gets its path with runtime.Caller(0) and then goes up one folder to reach the project root.

The problem is when using go test -cover the executed file is not in its normal place anymore but in a special sub directory for the testing and coverage analysis.
runtime.Caller(0) gives me the following:

example.org/myproject/path/_test/_obj_/loader.go

Running it through path.Dir and filepath.Abs will give me:

$GOPATH/src/example.org/myproject/path/example.org/myproject/path/_test/_obj_

When I go up from there I won't reach the project root but something totally different obviously. So my questions stands:
Is there any reliable way of getting the project root?

SilentStorm
  • 149
  • 1
  • 2
  • 10
  • Possible duplicate of [how to reference a relative file from code and tests](https://stackoverflow.com/questions/31059023/how-to-reference-a-relative-file-from-code-and-tests/31059125#31059125). – icza Jul 21 '17 at 17:13

3 Answers3

3

You can build it from the $GOPATH env variable:

gp := os.Getenv("GOPATH")
ap := path.Join(gp, "src/example.org/myproject")
fmt.Println(ap)

That will yield the absolute path to your paroject dir:

/path/to/gopath/src/example.org/myproject

This obviously only works when GOPATH is set. aka. on your dev machine. On production you need to supply directories via a config file.

RickyA
  • 15,465
  • 5
  • 71
  • 95
  • 2
    Wouldn't that cause problems in production? I don't know where a user might place the binary. So the path needs to be somewhat relative. – SilentStorm Jul 21 '17 at 10:52
  • 3
    uhm, on production there is only the binary. Your projectroot will not be there anyway, unless you put it there on purpose in which case you know beforehand where it is. – RickyA Jul 21 '17 at 10:55
  • are you trying to deploy files along with your binary? – RickyA Jul 21 '17 at 10:55
  • That's correct I want to deploy files alongside my binary. Is this a bad practice? – SilentStorm Jul 21 '17 at 12:14
  • 3
    Not a bad practice but if that's what you're doing you should offer a config setting to give the path to those files. In production there will be no "project root". – Adrian Jul 21 '17 at 13:14
  • -1 Sorry. I thought this might work myself, and tried it out before hitting SO. However, if the user doesn't have GOPATH set--instead relying on the platform-dependent default--then the path you construct is incorrect. – Benny Jobigan Oct 20 '17 at 16:30
  • This answer has a reliable way to determine GOPATH. https://stackoverflow.com/a/32650077/262748 – Benny Jobigan Oct 20 '17 at 16:39
  • Using GOPATH made it work in dev and production, but broke my tests (both locally and on CI) since the GOPATH seems to be messed up – Cyril Duchon-Doris Mar 02 '20 at 13:17
2

See this answer. In case you are using go ~ 1.8, func Executable() (string, error) is an option I stumbled over when I needed it. I tested briefly how it interacts with go test -cover and it seems to work fine:

func Executable() (string, error)

Executable returns the path name for the executable that started the current process. There is no guarantee that the path is still pointing to the correct executable. If a symlink was used to start the process, depending on the operating system, the result might be the symlink or the path it pointed to. If a stable result is needed, path/filepath.EvalSymlinks might help.

package main

import (
    "fmt"
    "os"
    "path"
)

func main() {
    e, err := os.Executable()
    if err != nil {
        panic(err)
    }
    path := path.Dir(e)
    fmt.Println(path)
}

The test:

binpath.go:

package binpath

import (
    "os"
    "path"
)

func getBinPath() string {
    e, err := os.Executable()
    if err != nil {
        panic(err)
    }
    path := path.Dir(e)
    return path
}

binpath_test.go:

package binpath

import (
    "fmt"
    "testing"
)

func TestGetBinPath(t *testing.T) {
    fmt.Println(getBinPath())
}

Results in something like /tmp/go-build465775039/github.com/tworabbits/binpath/_test

Community
  • 1
  • 1
tworabbits
  • 1,203
  • 12
  • 17
  • If you had a resource in the GOPATH/src/whatever folder, but installed your executable to GOPATH/bin using `go install`, would `os.Executable()` still point to the appropriate directory though? – Benny Jobigan Oct 20 '17 at 18:10
  • This won't work if you run you go script direct ex: `go run main.py` – Shady Smaoui Jul 03 '23 at 21:30
0

Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path

absPath, err := filepath.Abs()
if err != nil {
    panic("Ooops)
}
Shady Smaoui
  • 867
  • 9
  • 11