72

I am writing a utility function for my unit tests which is used by unit tests in multiple packages. This utility function must read a particular file (always the same file). Here are three solutions which do not work, to explain what I am looking for and why.

  1. Hardcode the absolute path. This fails because another user who is trying to test the project might have a different prefix on the absolute path.

  2. Hardcode a relative path from the path of the file which defines the utility function. This fails because packages which import and use this function are not necessarily at the same level of the file hierarchy as the file that defines the utility function, and relative paths are interpreted relative to the importer, not the imported.

  3. Pass in the relative path to the file from every caller relative to the caller's package. This actually works but seems to be very verbose because now every caller must be changed to pass one file.

I see a fourth solution, whereby I can hardcode a path in the utility function which is relative to the root directory of the top-level package. However, I have not been able to find a way to get the root directory in code, although I suspect there is one because imports can be resolved from the root.

Thus, how might I get the sought-after root directory?

I've looked over various Go documents but have so far failed to find a solution. I have also seen this question but the solution there is equivalent to #3 above.

Community
  • 1
  • 1
merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • As far as I know, relative paths are interpreted relative to the [current working directory](http://golang.org/pkg/os/#Getwd), not a particular package. – thwd Aug 07 '15 at 08:55
  • @thwd, You are correct, but when I'm running `go test` on a another package, that package's directory is the current working directory. By empirical testing, this is true even if the actual current working directory is a different directory. – merlin2011 Aug 07 '15 at 08:57
  • oh, now I get it. One other idea: During `go test`, `$GOPATH` should be set to the root of the current workspace, so just use a path relative to `$GOPATH/src`. – thwd Aug 07 '15 at 09:01
  • @thwd, Thanks for the suggestions. Unfortunately `$GOPATH` can contain multiple `:`-delimited paths, and it is not possible for the code to know which of them it belongs to. It seems very hacky to try them all until it finds a file with the name it wants. – merlin2011 Aug 07 '15 at 09:02
  • 1
    One last thought: [`runtime.GOROOT()`](https://golang.org/pkg/runtime/#GOROOT) returns "the root used during the Go build". As setting a `$GOROOT` is discouraged, this might be a solution. – thwd Aug 07 '15 at 09:07
  • @thwd, That did look very promising although I'm not sure if it is the root of the project or the root of the `go` executable. Unfortunately, `$GOROOT` is set in most of our dev environments at my workplace. I cannot help but think that this should be a problem more general than `go`, and that I'm missing some obvious solution that doesn't require finding the root. – merlin2011 Aug 07 '15 at 09:11
  • Does something like mentioned in [this answer](http://stackoverflow.com/a/31059125/1705598) satisfies you? Also can't you just embed the file in the Go source? – icza Aug 07 '15 at 10:38
  • @izca All three of your solutions essentially assume that the base path is the same on most machines or that all other developer users will need to specify a configuration manually just to run the unit test successfully. My question is exactly trying to avoid such manual effort by other developers. – merlin2011 Aug 07 '15 at 10:50
  • With respect to embedding that might be an option but is a bit messy because the file is rather large and is occasionally regenerated and checked into source. – merlin2011 Aug 07 '15 at 10:53
  • @merlin2011 If you want to avoid all manual configuration effort and avoid embedding, then I think your best option is iterating over and trying each path of `$GOPATH` until you find your file. The reason is because non-go sources are not processed by the compiler. – icza Aug 07 '15 at 11:34

6 Answers6

91

You can also use my method without C:

package mypackage

import (
    "path/filepath"
    "runtime"
    "fmt"
)

var (
    _, b, _, _ = runtime.Caller(0)
    basepath   = filepath.Dir(b)
)

func PrintMyPath() {
    fmt.Println(basepath)
}

https://play.golang.org/p/ifVRIq7Tx0

Oleksiy Chechel
  • 1,274
  • 9
  • 12
39

Building on the answer by Oleksiy you can create a sub-package in your project called ./internal/projectpath/projectpath.go and paste in the following:

package projectpath

import (
    "path/filepath"
    "runtime"
)

var (
    _, b, _, _ = runtime.Caller(0)

    // Root folder of this project
    Root = filepath.Join(filepath.Dir(b), "../..")
)

Then you can use projectpath.Root in any other package to have the root folder of your project.

Xeoncross
  • 55,620
  • 80
  • 262
  • 364
  • 1
    This feels like the most elegant solution: it uses a global variable but we treat it here more like a constant providing a single place to access it. – Konrad Reiche Apr 06 '23 at 18:38
23

Returns the root of the application:

import (
    "path"
    "path/filepath"
    "runtime"
)  

func RootDir() string {
    _, b, _, _ := runtime.Caller(0)
    d := path.Join(path.Dir(b))
    return filepath.Dir(d)
}
Dele
  • 730
  • 6
  • 10
15

Go directories:

// from Executable Directory
ex, _ := os.Executable()
fmt.Println("Executable DIR:", filepath.Dir(ex))

// Current working directory
dir, _ := os.Getwd()
fmt.Println("CWD:", dir)

// Relative on runtime DIR:
_, b, _, _ := runtime.Caller(0)
d1 := path.Join(path.Dir(b))
fmt.Println("Relative", d1)
Petre Sosa
  • 2,097
  • 1
  • 23
  • 19
1

Here is an example of a test helper to get the root path of your module (project) without having to set it as global variable, or using relative paths like .. by utilize go list in case you need to generate the path at runtime or if you don't want to be bound by in which file the variable is defined.

func getRelativeRootPath(tb testing.TB) string {
    tb.Helper()

    importPath := runGoList(tb, "list", "-f", "{{.ImportPath}}")
    modulePath := runGoList(tb, "list", "-m", "-f", "{{.Path}}")
    pkgPath := runGoList(tb, "list", "-f", "{{.Dir}}")

    relativePath, err := filepath.Rel(importPath, modulePath)
    if err != nil {
        tb.Fatal(err)
    }
    return filepath.Join(pkgPath, relativePath)
}

func runGoList(tb testing.TB, arg ...string) string {
    tb.Helper()
    cmd := exec.Command("go", arg...)
    output, err := cmd.CombinedOutput()
    if err != nil {
        tb.Fatalf("runGoList: %v: %s", err, string(output))
    }
    return strings.TrimSpace(string(output))
}

This will work independent on running go test in the package where the test makes use of the helper or if go test is run at the root level.


The import path is the path of the referenced package, the module path contains only the path for the module itself which we use here as a reference to figure out how deep another package is nested, allowing us to compute a relative path, for example . or .. or ../.., and so on.

Finally we also use runtime.Caller(0) to get the path of the current file but can now at runtime compute the root path of the project.

Konrad Reiche
  • 27,743
  • 15
  • 106
  • 143
-3

Yes, Finding package path is possible:

pathfind.go:

package main

/*
const char* GetMyPathFILE = __FILE__;
*/
import "C"
import "path/filepath"

var basepath = ""

//GetMyPath Returns the absolute directory of this(pathfind.go) file
func GetMyPath() string {
    if basepath == "" {
        g := C.GoString(C.GetMyPathFILE)
        basepath = filepath.Dir(g)
    }
    return basepath
}

All you have to do is copy this file to your project. Keep in mind this comes up with the path for the file, not the caller so you have to copy the function/file to every project you need the function in. Additionally if you put this in a file with other code be sure to respect CGO's import rules.

Abex
  • 300
  • 5
  • 11