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