reworked paths package

the location of the configuration directory now depends on how the
binary is built. release versions of the binary will look for the
gopher2600 directory in the user's os configuration directory.
non-release versions will look for the .gopher2600 directory in the CWD.

paths.ResourcePath() will create the paths that don't exist

paths.ResourcePath() now clearly differentiates paths and files

added build target to the Makefile; disabled release target for now
This commit is contained in:
steve 2020-01-07 17:30:50 +00:00
parent 299db2277e
commit 4534da0db6
13 changed files with 170 additions and 74 deletions

View file

@ -12,8 +12,13 @@ clean:
@echo "removing binary and profiling files"
@rm -f gopher2600 cpu.profile mem.profile debug.cpu.profile debug.mem.profile
build:
go build -gcflags '-c 3 -B -+ -wb=false'
release:
go build -gcflags '-c 3 -B -+ -wb=false' .
@#go build -gcflags '-c 3 -B -+ -wb=false' -tags release
@echo "use 'make build' for now. the release target will"
@echo "reappear in a future commit."
profile:
go build -gcflags '-c 3 -B -+ -wb=false' .

View file

@ -71,7 +71,7 @@ const (
DatabaseFileUnavailable = "database error: cannot open database (%v)"
// regression
RegressionError = "regression test error: %v"
RegressionError = "regression error: %v"
RegressionDigestError = "digest entry: %v"
RegressionPlaybackError = "playback entry: %v"
@ -82,8 +82,7 @@ const (
SetupTelevisionError = "tv setup: %v"
// patch
PatchError = "patch error: %v"
PatchFileError = "patch error: patch file not found (%v)"
PatchError = "patch error: %v"
// symbols
SymbolsFileError = "symbols error: error processing symbols file: %v"

View file

@ -106,7 +106,7 @@ func play(md *modalflag.Modes) error {
p, err := md.Parse()
if p != modalflag.ParseContinue {
return err
return errors.New(errors.PlayError, err)
}
switch len(md.RemainingArgs()) {
@ -160,15 +160,20 @@ func play(md *modalflag.Modes) error {
func debug(md *modalflag.Modes) error {
md.NewMode()
defInitScript, err := paths.ResourcePath("", defaultInitScript)
if err != nil {
return errors.New(errors.DebuggerError, err)
}
cartFormat := md.AddString("cartformat", "AUTO", "force use of cartridge format")
spec := md.AddString("tv", "AUTO", "television specification: NTSC, PAL")
termType := md.AddString("term", "COLOR", "terminal type to use in debug mode: COLOR, PLAIN")
initScript := md.AddString("initscript", paths.ResourcePath(defaultInitScript), "script to run on debugger start")
initScript := md.AddString("initscript", defInitScript, "script to run on debugger start")
profile := md.AddBool("profile", false, "run debugger through cpu profiler")
p, err := md.Parse()
if p != modalflag.ParseContinue {
return err
return errors.New(errors.DebuggerError, err)
}
tv, err := television.NewTelevision(*spec)
@ -246,7 +251,7 @@ func disasm(md *modalflag.Modes) error {
p, err := md.Parse()
if p != modalflag.ParseContinue {
return err
return errors.New(errors.DisassemblyError, err)
}
switch len(md.RemainingArgs()) {
@ -290,7 +295,7 @@ func perform(md *modalflag.Modes) error {
p, err := md.Parse()
if p != modalflag.ParseContinue {
return err
return errors.New(errors.PerformanceError, err)
}
switch len(md.RemainingArgs()) {
@ -304,24 +309,24 @@ func perform(md *modalflag.Modes) error {
tv, err := television.NewTelevision(*spec)
if err != nil {
return err
return errors.New(errors.PerformanceError, err)
}
defer tv.End()
if *display {
scr, err := sdlplay.NewSdlPlay(tv, float32(*scaling))
if err != nil {
return err
return errors.New(errors.PerformanceError, err)
}
err = scr.(gui.GUI).SetFeature(gui.ReqSetVisibility, true)
if err != nil {
return err
return errors.New(errors.PerformanceError, err)
}
err = scr.(gui.GUI).SetFeature(gui.ReqSetFPSCap, *fpscap)
if err != nil {
return err
return errors.New(errors.PerformanceError, err)
}
}
@ -349,7 +354,7 @@ func regress(md *modalflag.Modes) error {
p, err := md.Parse()
if p != modalflag.ParseContinue {
return err
return errors.New(errors.RegressionError, err)
}
switch md.Mode() {
@ -442,7 +447,7 @@ func regressAdd(md *modalflag.Modes) error {
p, err := md.Parse()
if p != modalflag.ParseContinue {
return err
return errors.New(errors.RegressionError, err)
}
switch len(md.RemainingArgs()) {

View file

@ -20,6 +20,7 @@
package patch
import (
"fmt"
"gopher2600/errors"
"gopher2600/hardware/memory/cartridge"
"gopher2600/paths"
@ -42,13 +43,16 @@ const pokeLineSeparator = ":"
func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
var err error
p := paths.ResourcePath(patchPath, patchFile)
p, err := paths.ResourcePath(patchPath, patchFile)
if err != nil {
return false, errors.New(errors.PatchError, err)
}
f, err := os.Open(p)
if err != nil {
switch err.(type) {
case *os.PathError:
return false, errors.New(errors.PatchFileError, p)
return false, errors.New(errors.PatchError, fmt.Sprintf("patch file not found (%s)", p))
}
return false, errors.New(errors.PatchError, err)
}

27
paths/dev_path.go Normal file
View file

@ -0,0 +1,27 @@
// +build !release
package paths
import (
"os"
"path"
)
const gopherConfigDir = ".gopher2600"
// the non-release version of getBasePath looks for and if necessary creates
// the gopherConfigDir (and child directories) in the current working
// directory
func getBasePath(subPth string) (string, error) {
pth := path.Join(gopherConfigDir, subPth)
if _, err := os.Stat(pth); err == nil {
return pth, nil
}
if err := os.MkdirAll(pth, 0700); err != nil {
return "", err
}
return pth, nil
}

View file

@ -17,21 +17,24 @@
// git repository, are also covered by the licence, even when this
// notice is not present ***
// Package paths contains functions to prepare paths to gopher2600 resources.
// Package paths contains functions to prepare paths for gopher2600 resources.
//
// The ResourcePath() function modifies the supplied resource string such that
// it is prepended with the appropriate config directory. For example, the
// following will return the path to a cartridge patch.
// The ResourcePath() function returns the correct path to the resource
// directory/file specified in the arguments. The result of ResourcePath
// depends on the build tag used to compile the program.
//
// d := paths.ResourcePath("patches", "ET")
// For "release" tagged builds, the correct path is one rooted in the user's
// configuration directory. On modern Linux systems that would be something
// like:
//
// The policy of ResourcePath() is simple: if the base resource path, currently
// defined to be ".gopher2600", is present in the program's current directory
// then that is the base path that will used. If it is not preseent not, then
// the user's config directory is used. The package uses os.UserConfigDir()
// from go standard library for this.
// /home/user/.config/gopher2600/
//
// In the example above, on a modern Linux system, the path returned will be:
// For "non-release" tagged builds, the correct path is rooted in the current
// working directory:
//
// /home/user/.config/gopher2600/patches/ET
// ./.gopher2600
//
// The reason for this is simple. During development, it is more convenient to
// have the config directory close to hand. For release binaries meanwhile, the
// config directory should be somewhere the user expects.
package paths

View file

@ -20,44 +20,27 @@
package paths
import (
"os"
"path"
)
// the base path for all resources. note that we don't use this value directly
// except in the getBasePath() function. that function should be used instead.
const baseResourcePath = ".gopher2600"
// ResourcePath returns the resource string (representing the resource to be
// loaded) prepended with operating system specific details.
func ResourcePath(resource ...string) string {
var p []string
p = make([]string, 0, len(resource)+1)
p = append(p, getBasePath())
p = append(p, resource...)
return path.Join(p...)
}
// getBasePath() returns baseResourcePath with the user's home directory
// prepended if it the unadorned baseResourcePath cannot be found in the
// current directory.
// loaded) prepended with OS/build specific paths.
//
// note that we're not checking for the existance of the resource requested by
// the caller, or even the existance of 'baseResourcePath' in the home
// directory.
// The function takes care of creation of all folders necessary to reach the
// end of sub-path. It does not otherwise touch or create the file.
//
// note: this is a UNIX thing. there's no real reason why any other OS should
// implement this.
func getBasePath() string {
if _, err := os.Stat(baseResourcePath); err == nil {
return baseResourcePath
}
// Either subPth or file can be empty, depending on context.
func ResourcePath(subPth string, file string) (string, error) {
var pth []string
home, err := os.UserConfigDir()
basePath, err := getBasePath(subPth)
if err != nil {
return baseResourcePath
return "", err
}
return path.Join(home, baseResourcePath[1:])
pth = make([]string, 0)
pth = append(pth, basePath)
pth = append(pth, file)
return path.Join(pth...), nil
}

33
paths/release_path.go Normal file
View file

@ -0,0 +1,33 @@
// +build release
package paths
import (
"os"
"path"
)
const gopherConfigDir = "gopher2600"
// the release version of getBasePath looks for and if necessary creates the
// gopherConfigDir (and child directories) in the User's configuration
// directory, which is dependent on the host OS (see os.UserConfigDir()
// documentation for details)
func getBasePath(subPth string) (string, error) {
cnf, err := os.UserConfigDir()
if err != nil {
return "", err
}
pth := path.Join(cnf, gopherConfigDir, subPth)
if _, err := os.Stat(pth); err == nil {
return pth, nil
}
if err := os.MkdirAll(pth, 0700); err != nil {
return "", err
}
return pth, nil
}

View file

@ -234,7 +234,10 @@ func (reg *DigestRegression) regress(newRegression bool, output io.Writer, msg s
if reg.State {
// create a unique filename
reg.stateFile = uniqueFilename("state", reg.CartLoad)
reg.stateFile, err = uniqueFilename("state", reg.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionDigestError, err)
}
// check that the filename is unique
nf, _ := os.Open(reg.stateFile)

View file

@ -193,7 +193,10 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
// regressionScripts directory
if newRegression {
// create a unique filename
newScript := uniqueFilename("playback", plb.CartLoad)
newScript, err := uniqueFilename("playback", plb.CartLoad)
if err != nil {
return false, "", errors.New(errors.RegressionPlaybackError, err)
}
// check that the filename is unique
nf, _ := os.Open(newScript)

View file

@ -27,7 +27,6 @@ import (
"gopher2600/paths"
"io"
"math/rand"
"os"
"sort"
"strconv"
"time"
@ -65,10 +64,10 @@ func initDBSession(db *database.Session) error {
}
// make sure regression script directory exists
if err := os.MkdirAll(paths.ResourcePath(regressionScripts), 0755); err != nil {
msg := fmt.Sprintf("regression script directory: %s", err)
return errors.New(errors.RegressionError, msg)
}
// if err := os.MkdirAll(paths.ResourcePath(regressionScripts), 0755); err != nil {
// msg := fmt.Sprintf("regression script directory: %s", err)
// return errors.New(errors.RegressionError, msg)
// }
return nil
}
@ -79,7 +78,12 @@ func RegressList(output io.Writer) error {
return errors.New(errors.PanicError, "RegressList()", "io.Writer should not be nil (use a nopWriter)")
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityReading, initDBSession)
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
}
db, err := database.StartSession(dbPth, database.ActivityReading, initDBSession)
if err != nil {
return err
}
@ -99,7 +103,12 @@ func RegressAdd(output io.Writer, reg Regressor) error {
return errors.New(errors.PanicError, "RegressAdd()", "io.Writer should not be nil (use nopWriter)")
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityCreating, initDBSession)
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
}
db, err := database.StartSession(dbPth, database.ActivityCreating, initDBSession)
if err != nil {
return err
}
@ -129,7 +138,12 @@ func RegressDelete(output io.Writer, confirmation io.Reader, key string) error {
return errors.New(errors.RegressionError, msg)
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityModifying, initDBSession)
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
}
db, err := database.StartSession(dbPth, database.ActivityModifying, initDBSession)
if err != nil {
return err
}
@ -179,7 +193,12 @@ func RegressRunTests(output io.Writer, verbose bool, failOnError bool, filterKey
return errors.New(errors.PanicError, "RegressRunEntries()", "io.Writer should not be nil (use nopWriter)")
}
db, err := database.StartSession(paths.ResourcePath(regressionDBFile), database.ActivityReading, initDBSession)
dbPth, err := paths.ResourcePath("", regressionDBFile)
if err != nil {
return errors.New(errors.RegressionError, err)
}
db, err := database.StartSession(dbPth, database.ActivityReading, initDBSession)
if err != nil {
return errors.New(errors.RegressionError, err)
}

View file

@ -22,15 +22,22 @@ package regression
import (
"fmt"
"gopher2600/cartridgeloader"
"gopher2600/errors"
"gopher2600/paths"
"time"
)
// create a unique filename from a CatridgeLoader instance. used when saving
// scripts into regressionScripts directory
func uniqueFilename(prepend string, cartload cartridgeloader.Loader) string {
func uniqueFilename(prepend string, cartload cartridgeloader.Loader) (string, error) {
n := time.Now()
timestamp := fmt.Sprintf("%04d%02d%02d_%02d%02d%02d", n.Year(), n.Month(), n.Day(), n.Hour(), n.Minute(), n.Second())
newScript := fmt.Sprintf("%s_%s_%s", prepend, cartload.ShortName(), timestamp)
return paths.ResourcePath(regressionScripts, newScript)
scrPth, err := paths.ResourcePath(regressionScripts, newScript)
if err != nil {
return "", errors.New(errors.RegressionError, err)
}
return scrPth, nil
}

View file

@ -70,7 +70,12 @@ func AttachCartridge(vcs *hardware.VCS, cartload cartridgeloader.Loader) error {
return err
}
db, err := database.StartSession(paths.ResourcePath(setupDBFile), database.ActivityReading, initDBSession)
dbPth, err := paths.ResourcePath("", setupDBFile)
if err != nil {
return errors.New(errors.SetupError, err)
}
db, err := database.StartSession(dbPth, database.ActivityReading, initDBSession)
if err != nil {
if errors.Is(err, errors.DatabaseFileUnavailable) {
// silently ignore absence of setup database