From 4534da0db664a867bf123f34e1c0551f2a7902ee Mon Sep 17 00:00:00 2001 From: steve Date: Tue, 7 Jan 2020 17:30:50 +0000 Subject: [PATCH] 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 --- Makefile | 7 ++++++- errors/messages.go | 5 ++--- gopher2600.go | 27 ++++++++++++++---------- patch/patch.go | 8 +++++-- paths/dev_path.go | 27 ++++++++++++++++++++++++ paths/doc.go | 27 +++++++++++++----------- paths/paths.go | 45 +++++++++++++--------------------------- paths/release_path.go | 33 +++++++++++++++++++++++++++++ regression/digest.go | 5 ++++- regression/playback.go | 5 ++++- regression/regression.go | 37 +++++++++++++++++++++++++-------- regression/unique.go | 11 ++++++++-- setup/setup.go | 7 ++++++- 13 files changed, 170 insertions(+), 74 deletions(-) create mode 100644 paths/dev_path.go create mode 100644 paths/release_path.go diff --git a/Makefile b/Makefile index b027c2d6..76bb583e 100644 --- a/Makefile +++ b/Makefile @@ -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' . diff --git a/errors/messages.go b/errors/messages.go index 936ea6de..eac18e77 100644 --- a/errors/messages.go +++ b/errors/messages.go @@ -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" diff --git a/gopher2600.go b/gopher2600.go index 2bec7d66..4426179a 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -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()) { diff --git a/patch/patch.go b/patch/patch.go index 2cab1dd6..dd32b607 100644 --- a/patch/patch.go +++ b/patch/patch.go @@ -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) } diff --git a/paths/dev_path.go b/paths/dev_path.go new file mode 100644 index 00000000..d375f57d --- /dev/null +++ b/paths/dev_path.go @@ -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 +} diff --git a/paths/doc.go b/paths/doc.go index 092b48fa..9bfa518e 100644 --- a/paths/doc.go +++ b/paths/doc.go @@ -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 diff --git a/paths/paths.go b/paths/paths.go index c31f7b83..7aeddb5e 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -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 } diff --git a/paths/release_path.go b/paths/release_path.go new file mode 100644 index 00000000..087214c0 --- /dev/null +++ b/paths/release_path.go @@ -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 +} diff --git a/regression/digest.go b/regression/digest.go index de240b63..1823aa34 100644 --- a/regression/digest.go +++ b/regression/digest.go @@ -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) diff --git a/regression/playback.go b/regression/playback.go index 80ac7116..0f752ecf 100644 --- a/regression/playback.go +++ b/regression/playback.go @@ -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) diff --git a/regression/regression.go b/regression/regression.go index 0e91c0b7..ec03a382 100644 --- a/regression/regression.go +++ b/regression/regression.go @@ -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) } diff --git a/regression/unique.go b/regression/unique.go index 24d4b994..ca0c27df 100644 --- a/regression/unique.go +++ b/regression/unique.go @@ -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 } diff --git a/setup/setup.go b/setup/setup.go index 07dde6f3..9b16085c 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -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