github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/internal/env/path.go (about) 1 package env 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 9 "github.com/go-git/go-git/v5" 10 ) 11 12 var quickfeedRoot string 13 14 const quickfeedModulePath = "github.com/quickfeed/quickfeed" 15 16 func init() { 17 quickfeedRoot = os.Getenv("QUICKFEED") 18 } 19 20 // Root returns the root directory as defined by $QUICKFEED or 21 // sets it relative to the quickfeed module's root. 22 // This function will panic if called when the working directory 23 // is not within the quickfeed repository. In this case, the 24 // environment variable $QUICKFEED must be set manually. 25 func Root() string { 26 if quickfeedRoot != "" { 27 return quickfeedRoot 28 } 29 setRoot() 30 return quickfeedRoot 31 } 32 33 func setRoot() { 34 root, err := gitRoot() 35 if err != nil { 36 // When the working directory is outside the git repository, we must set the QUICKFEED env variable. 37 wd, _ := os.Getwd() 38 fmt.Printf("Working directory (%s) may be outside quickfeed's git repository.\n", wd) 39 fmt.Println("Please set the QUICKFEED environment variable to the root of the repository.") 40 panic(fmt.Sprintf("Failed to determine root of the git repository: %v", err)) 41 } 42 if err := checkModulePath(root); err != nil { 43 panic(fmt.Sprintf("Invalid module path: %v", err)) 44 } 45 os.Setenv("QUICKFEED", root) 46 quickfeedRoot = root 47 } 48 49 // gitRoot return the root of the Git repository. 50 func gitRoot() (string, error) { 51 path, err := os.Getwd() 52 if err != nil { 53 return "", err 54 } 55 // PlainOpen opens a git repository from the given path and searches upwards. 56 repo, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{DetectDotGit: true}) 57 if err != nil { 58 return "", err 59 } 60 w, err := repo.Worktree() 61 if err != nil { 62 return "", err 63 } 64 return w.Filesystem.Root(), nil 65 } 66 67 // checkModulePath checks that the root directory contains a go.mod file 68 // with the correct module path for QuickFeed. 69 func checkModulePath(root string) error { 70 modFile := filepath.Join(root, "go.mod") 71 data, err := os.ReadFile(modFile) 72 if err != nil { 73 return fmt.Errorf("failed to read %s: %v", modFile, err) 74 } 75 if !bytes.Contains(data, []byte("module "+quickfeedModulePath)) { 76 return fmt.Errorf("invalid go.mod file: %s", modFile) 77 } 78 return nil 79 } 80 81 // RootEnv returns the path $QUICKFEED/{envFile}. 82 func RootEnv(envFile string) string { 83 return filepath.Join(Root(), envFile) 84 } 85 86 // PublicEnv returns the path $QUICKFEED/public/{envFile}. 87 func PublicEnv(envFile string) string { 88 return filepath.Join(Root(), "public", envFile) 89 } 90 91 // PublicDir returns the path to the public directory. 92 func PublicDir() string { 93 return filepath.Join(Root(), "public") 94 } 95 96 // DatabasePath returns the path to the database file. 97 func DatabasePath() string { 98 return filepath.Join(Root(), "qf.db") 99 } 100 101 // TestdataPath returns the path to the testdata/courses directory. 102 func TestdataPath() string { 103 return filepath.Join(Root(), "testdata", "courses") 104 }