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  }