Today I Learned (July 05, 2024)

How to import local packages in Golang

When requiring packages in your go.mod file, you either have to specify a relative path that points to a directory within GOPATH; or, you need to specify a URL under which the package may be downloaded.

Sadly it’s not possible to directly specify a path relative to the current working directory. This is a somewhat uncanny limitation, since it implicitly prevents the arguably most simple importing mechanism of importing packages from local directories (such as subdirectories of the project).

For example, consider the following hello-golang project

mkdir hello-golang
cd hello-golang
go mod init friedrichkurz.me/hello-golang
go mod tidy
mkdir -p lib/hello
echo 'package hello

import (
    "fmt"
)

func Hello() {
    fmt.Println("Hello!")
}
' > lib/hello/hello.go
echo 'package main

import (
    "lib/hello"  
)

func main() {
    hello.Hello()
}
' > main.go

As you can see, we simply refactor the hello function to a library package hello and want to import it in our main. Running this program with go run, however, will yield a somewhat cryptic error:

$ go run main.go
main.go:4:2: package lib/hello is not in std (/usr/local/go/src/lib/hello)

We may use the Golang module system however, to alias our hello package and import it from the local system using the replace directive.

mkdir hello-golang-local
cd hello-golang-local
echo 'module friedrichkurz.me/hello-golang
go 1.22.5

require (
    "friedrichkurz.me/hello" v0.0.1
)

replace (
    "friedrichkurz.me/hello" => ./lib/hello
)
' > go.mod 
mkdir -p lib/hello
echo 'package hello

import (
    "fmt"
)

func Hello() {
    fmt.Println("Hello!")
}
' > lib/hello/hello.go
echo 'module friedrichkurz.me/hello' > lib/hello/go.mod
echo 'package main

import (
    hello "friedrichkurz.me/hello" 
)

func main() {
    hello.Hello()
}
' > main.go

:bulb: As shown above, the imported local directory must (in this case) be a Golang module named friedrichkurz.me/hello. This means that it must have a separate go.mod containng the directive module "friedrichkurz.me/hello".

With this change, the local directory is imported correctly and we may run the program successfully:

$ go run main.go
Hello!

:link: (thewebivore.com) Using replace in go mod to point to your local module