diff --git a/docs/_pages/docs/documentation/config.md b/docs/_pages/docs/documentation/config.md index 78f999fe..cdbe84d1 100644 --- a/docs/_pages/docs/documentation/config.md +++ b/docs/_pages/docs/documentation/config.md @@ -36,7 +36,10 @@ Example config, with many options explained: // These fields are for Regolith specifically "regolith": { - + // "useAppData" determines whether or not to use the app data folder, regolith should save its cache + // in user app data folder (true) or in the project folder in ".regolith" (false). This setting is + // optional and defaults to false. + "useAppData": false, // Profiles are a list of filters and export information, which can be run with 'regolith run ' "profiles": { // 'default' is the default profile. You can add more. @@ -51,6 +54,7 @@ Example config, with many options explained: // Settings object, which configure how name_ninja will run (optional) "settings": { "language": "en_GB.lang" + } }, { // A second filter, which will run after 'name_ninja' diff --git a/main.go b/main.go index 51313bed..82907cf6 100644 --- a/main.go +++ b/main.go @@ -147,18 +147,28 @@ func main() { }, { Name: "clean", - Usage: "Cleans cache from the .regolith folder.", + Usage: "Cleans Regolith cache.", Action: func(c *cli.Context) error { clearPathStates := c.Bool("path-states") - return regolith.Clean(debug, clearPathStates) + userCache := c.Bool("user-cache") + return regolith.Clean(debug, userCache, clearPathStates) }, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "path-states", Aliases: []string{"p"}, Usage: "Deletes file used for caching contents of " + - "paths used by Regolith (useful when Regolith " + - "doesn't export files propertly).", + "paths used by Regolith. This is useful when you " + + "work in --recycled mode and Regolith doesn't " + + "export files propertly).", + }, + &cli.BoolFlag{ + Name: "user-cache", + Aliases: []string{}, + Usage: "Clears data of the projects cached in the " + + "user app data folder. This is useful to clean " + + "up leftover files from old projects that use " + + "the \"useAppData\" option.", }, }, }, diff --git a/regolith/config.go b/regolith/config.go index 0b6c6576..eb366904 100644 --- a/regolith/config.go +++ b/regolith/config.go @@ -4,16 +4,6 @@ const StandardLibraryUrl = "github.com/Bedrock-OSS/regolith-filters" const ConfigFilePath = "config.json" const GitIgnore = "/build\n/.regolith" -var ConfigurationFolders = []string{ - "packs", - "packs/data", - "packs/BP", - "packs/RP", - ".regolith", - ".regolith/cache", - ".regolith/cache/venvs", -} - // Config represents the full configuration file of Regolith, as saved in // "config.json". type Config struct { @@ -47,6 +37,7 @@ type RegolithProject struct { Profiles map[string]Profile `json:"profiles,omitempty"` FilterDefinitions map[string]FilterInstaller `json:"filterDefinitions"` DataPath string `json:"dataPath,omitempty"` + UseAppData bool `json:"useAppData,omitempty"` } // ConfigFromObject creates a "Config" object from map[string]interface{} @@ -160,6 +151,16 @@ func RegolithProjectFromObject( } result.Profiles[profileName] = profileValue } + // UseAppData (optional, false by default) + useAppData := false + if _, ok := obj["useAppData"]; ok { + useAppData, ok = obj["useAppData"].(bool) + if !ok { + return result, WrappedError( + "The \"useAppData\" property is not a boolean.") + } + } + result.UseAppData = useAppData return result, nil } diff --git a/regolith/config_unparsed.go b/regolith/config_unparsed.go index 2e0f672c..11df84f6 100644 --- a/regolith/config_unparsed.go +++ b/regolith/config_unparsed.go @@ -58,3 +58,22 @@ func filterDefinitionsFromConfigMap( } return filterDefinitions, nil } + +// useAppDataFromConfigMap returns the useAppData value from the config file +// map, without parsing it to a Config object. +func useAppDataFromConfigMap(config map[string]interface{}) (bool, error) { + regolith, ok := config["regolith"].(map[string]interface{}) + if !ok { + return false, WrappedError("Missing \"regolith\" property.") + } + filterDefinitionsInterface, ok := regolith["useAppData"] + if !ok { // false by default + return false, nil + } + filterDefinitions, ok := filterDefinitionsInterface.(bool) + if !ok { + return false, WrappedError("\"regolith\"->\"useAppData\" property " + + "must be a boolean.") + } + return filterDefinitions, nil +} diff --git a/regolith/export.go b/regolith/export.go index e910b142..81a8837b 100644 --- a/regolith/export.go +++ b/regolith/export.go @@ -85,7 +85,9 @@ func GetExportPaths( // into the project's export target. The paths are generated with // GetExportPaths. The function uses cached data about the state of the project // files to reduce the number of file system operations. -func RecycledExportProject(profile Profile, name string, dataPath string) error { +func RecycledExportProject( + profile Profile, name, dataPath, dotRegolithPath string, +) error { exportTarget := profile.ExportTarget bpPath, rpPath, err := GetExportPaths(exportTarget, name) if err != nil { @@ -94,7 +96,7 @@ func RecycledExportProject(profile Profile, name string, dataPath string) error } // Loading edited_files.json or creating empty object - editedFiles := LoadEditedFiles() + editedFiles := LoadEditedFiles(dotRegolithPath) err = editedFiles.CheckDeletionSafety(rpPath, bpPath) if err != nil { return WrapErrorf( @@ -109,9 +111,8 @@ func RecycledExportProject(profile Profile, name string, dataPath string) error } Logger.Infof("Exporting behavior pack to \"%s\".", bpPath) - // err = MoveOrCopy(".regolith/tmp/BP", bpPath, exportTarget.ReadOnly, true) err = FullRecycledMoveOrCopy( - ".regolith/tmp/BP", bpPath, + filepath.Join(dotRegolithPath, "tmp/BP"), bpPath, RecycledMoveOrCopySettings{ canMove: true, saveSourceHashes: true, @@ -124,9 +125,8 @@ func RecycledExportProject(profile Profile, name string, dataPath string) error return WrapError(err, "Failed to export behavior pack.") } Logger.Infof("Exporting project to \"%s\".", rpPath) - // err = MoveOrCopy(".regolith/tmp/RP", rpPath, exportTarget.ReadOnly, true) err = FullRecycledMoveOrCopy( - ".regolith/tmp/RP", rpPath, + filepath.Join(dotRegolithPath, "tmp/RP"), rpPath, RecycledMoveOrCopySettings{ canMove: true, saveSourceHashes: true, @@ -138,9 +138,8 @@ func RecycledExportProject(profile Profile, name string, dataPath string) error if err != nil { return WrapError(err, "Failed to export resource pack.") } - // err = MoveOrCopy(".regolith/tmp/data", dataPath, false, false) err = FullRecycledMoveOrCopy( - ".regolith/tmp/data", dataPath, + filepath.Join(dotRegolithPath, "tmp/data"), dataPath, RecycledMoveOrCopySettings{ canMove: true, saveSourceHashes: true, @@ -162,7 +161,7 @@ func RecycledExportProject(profile Profile, name string, dataPath string) error err, "Failed to create a list of files edited by this 'regolith run'") } - err = editedFiles.Dump() + err = editedFiles.Dump(dotRegolithPath) if err != nil { return WrapError( err, "Failed to update the list of the files edited by Regolith."+ @@ -173,7 +172,9 @@ func RecycledExportProject(profile Profile, name string, dataPath string) error // ExportProject copies files from the tmp paths (tmp/BP and tmp/RP) into // the project's export target. The paths are generated with GetExportPaths. -func ExportProject(profile Profile, name string, dataPath string) error { +func ExportProject( + profile Profile, name, dataPath, dotRegolithPath string, +) error { exportTarget := profile.ExportTarget bpPath, rpPath, err := GetExportPaths(exportTarget, name) if err != nil { @@ -182,7 +183,7 @@ func ExportProject(profile Profile, name string, dataPath string) error { } // Loading edited_files.json or creating empty object - editedFiles := LoadEditedFiles() + editedFiles := LoadEditedFiles(dotRegolithPath) err = editedFiles.CheckDeletionSafety(rpPath, bpPath) if err != nil { return WrapErrorf( @@ -230,16 +231,16 @@ func ExportProject(profile Profile, name string, dataPath string) error { } Logger.Infof("Exporting behavior pack to \"%s\".", bpPath) - err = MoveOrCopy(".regolith/tmp/BP", bpPath, exportTarget.ReadOnly, true) + err = MoveOrCopy(filepath.Join(dotRegolithPath, "tmp/BP"), bpPath, exportTarget.ReadOnly, true) if err != nil { return WrapError(err, "Failed to export behavior pack.") } Logger.Infof("Exporting project to \"%s\".", rpPath) - err = MoveOrCopy(".regolith/tmp/RP", rpPath, exportTarget.ReadOnly, true) + err = MoveOrCopy(filepath.Join(dotRegolithPath, "tmp/RP"), rpPath, exportTarget.ReadOnly, true) if err != nil { return WrapError(err, "Failed to export resource pack.") } - err = MoveOrCopy(".regolith/tmp/data", dataPath, false, false) + err = MoveOrCopy(filepath.Join(dotRegolithPath, "tmp/data"), dataPath, false, false) if err != nil { return WrapError( err, "Failed to move the filter data back to the project's "+ @@ -253,7 +254,7 @@ func ExportProject(profile Profile, name string, dataPath string) error { err, "Failed to create a list of files edited by this 'regolith run'") } - err = editedFiles.Dump() + err = editedFiles.Dump(dotRegolithPath) if err != nil { return WrapError( err, "Failed to update the list of the files edited by Regolith."+ diff --git a/regolith/file_protection.go b/regolith/file_protection.go index 15d0ec28..1da2abf9 100644 --- a/regolith/file_protection.go +++ b/regolith/file_protection.go @@ -7,7 +7,7 @@ import ( "path/filepath" ) -const EditedFilesPath = ".regolith/cache/edited_files.json" +const EditedFilesPath = "cache/edited_files.json" // PathList is an alias for []string. It's used to store a list of file paths. type filesList = []string @@ -21,8 +21,8 @@ type EditedFiles struct { // LoadEditedFiles data from edited_files.json or returns an empty object // if file doesn't exist. -func LoadEditedFiles() EditedFiles { - data, err := os.ReadFile(EditedFilesPath) +func LoadEditedFiles(dotRegolithPath string) EditedFiles { + data, err := os.ReadFile(filepath.Join(dotRegolithPath, EditedFilesPath)) if err != nil { return NewEditedFiles() } @@ -35,24 +35,24 @@ func LoadEditedFiles() EditedFiles { } // Dump dumps EditedFiles to EditedFilesPath in JSON format. -func (f *EditedFiles) Dump() error { +func (f *EditedFiles) Dump(dotRegolithPath string) error { result, err := json.MarshalIndent(f, "", "\t") if err != nil { return WrapError(err, "Failed to marshal edited files list JSON.") } // Create parent directory of EditedFilesPath - parentDir := filepath.Dir(EditedFilesPath) + efp := filepath.Join(dotRegolithPath, EditedFilesPath) + parentDir := filepath.Dir(efp) err = os.MkdirAll(parentDir, 0666) if err != nil { return WrapErrorf( err, "Failed to create \"%s\" directory for edited files list.", parentDir) } - err = os.WriteFile(EditedFilesPath, result, 0666) + err = os.WriteFile(efp, result, 0666) if err != nil { return WrapErrorf( - err, "Failed to save edited files list in \"%s\".", - EditedFilesPath) + err, "Failed to save edited files list in \"%s\".", efp) } return nil } diff --git a/regolith/filter.go b/regolith/filter.go index 070bd85b..8562999f 100644 --- a/regolith/filter.go +++ b/regolith/filter.go @@ -17,6 +17,7 @@ type RunContext struct { Config *Config Profile string Parent *RunContext + DotRegolithPath string // interruptionChannel is a channel that is used to notify about changes // in the sourec files, in order to trigger a restart of the program in @@ -145,7 +146,7 @@ func FilterFromObject(obj map[string]interface{}) (*Filter, error) { } type FilterInstaller interface { - InstallDependencies(parent *RemoteFilterDefinition) error + InstallDependencies(parent *RemoteFilterDefinition, dotRegolithPath string) error Check(context RunContext) error CreateFilterRunner(runConfiguration map[string]interface{}) (FilterRunner, error) } diff --git a/regolith/filter_deno.go b/regolith/filter_deno.go index e17bdf39..618ef9e8 100644 --- a/regolith/filter_deno.go +++ b/regolith/filter_deno.go @@ -40,7 +40,7 @@ func (f *DenoFilter) run(context RunContext) error { f.Arguments..., ), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -56,7 +56,7 @@ func (f *DenoFilter) run(context RunContext) error { f.Definition.Script, string(jsonSettings)}, f.Arguments...), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -102,11 +102,11 @@ func (f *DenoFilterDefinition) Check(context RunContext) error { } func (f *DenoFilterDefinition) InstallDependencies( - parent *RemoteFilterDefinition, + parent *RemoteFilterDefinition, dotRegolithPath string, ) error { return nil } func (f *DenoFilter) Check(context RunContext) error { return f.Definition.Check(context) -} \ No newline at end of file +} diff --git a/regolith/filter_exe.go b/regolith/filter_exe.go index b49ece5a..befc3963 100644 --- a/regolith/filter_exe.go +++ b/regolith/filter_exe.go @@ -31,7 +31,7 @@ func ExeFilterDefinitionFromObject( } func (f *ExeFilter) Run(context RunContext) (bool, error) { - if err := f.run(f.Settings, context.AbsoluteLocation); err != nil { + if err := f.run(f.Settings, context); err != nil { return false, PassError(err) } return context.IsInterrupted(), nil @@ -52,7 +52,7 @@ func (f *ExeFilterDefinition) CreateFilterRunner( } func (f *ExeFilterDefinition) InstallDependencies( - parent *RemoteFilterDefinition, + *RemoteFilterDefinition, string, ) error { return nil } @@ -67,20 +67,21 @@ func (f *ExeFilter) Check(context RunContext) error { func (f *ExeFilter) run( settings map[string]interface{}, - absoluteLocation string, + context RunContext, ) error { var err error = nil if len(settings) == 0 { err = executeExeFile(f.Id, f.Definition.Exe, - f.Arguments, absoluteLocation, - GetAbsoluteWorkingDirectory()) + f.Arguments, context.AbsoluteLocation, + GetAbsoluteWorkingDirectory(context.DotRegolithPath)) } else { jsonSettings, _ := json.Marshal(settings) err = executeExeFile(f.Id, f.Definition.Exe, append([]string{string(jsonSettings)}, f.Arguments...), - absoluteLocation, GetAbsoluteWorkingDirectory()) + context.AbsoluteLocation, GetAbsoluteWorkingDirectory( + context.DotRegolithPath)) } if err != nil { return WrapError(err, "Failed to run shell filter.") diff --git a/regolith/filter_java.go b/regolith/filter_java.go index f63a8cb3..333498df 100644 --- a/regolith/filter_java.go +++ b/regolith/filter_java.go @@ -52,7 +52,7 @@ func (f *JavaFilter) run(context RunContext) error { f.Arguments..., ), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -69,7 +69,7 @@ func (f *JavaFilter) run(context RunContext) error { f.Arguments..., ), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -91,7 +91,7 @@ func (f *JavaFilterDefinition) CreateFilterRunner(runConfiguration map[string]in return filter, nil } -func (f *JavaFilterDefinition) InstallDependencies(*RemoteFilterDefinition) error { +func (f *JavaFilterDefinition) InstallDependencies(*RemoteFilterDefinition, string) error { return nil } diff --git a/regolith/filter_nim.go b/regolith/filter_nim.go index ddb4c780..c27131fc 100644 --- a/regolith/filter_nim.go +++ b/regolith/filter_nim.go @@ -42,7 +42,7 @@ func (f *NimFilter) run(context RunContext) error { f.Arguments..., ), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -59,7 +59,7 @@ func (f *NimFilter) run(context RunContext) error { string(jsonSettings)}, f.Arguments...), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -88,11 +88,13 @@ func (f *NimFilterDefinition) CreateFilterRunner(runConfiguration map[string]int return filter, nil } -func (f *NimFilterDefinition) InstallDependencies(parent *RemoteFilterDefinition) error { +func (f *NimFilterDefinition) InstallDependencies( + parent *RemoteFilterDefinition, dotRegolithPath string, +) error { installLocation := "" // Install dependencies if parent != nil { - installLocation = parent.GetDownloadPath() + installLocation = parent.GetDownloadPath(dotRegolithPath) } Logger.Infof("Downloading dependencies for %s...", f.Id) scriptPath, err := filepath.Abs(filepath.Join(installLocation, f.Script)) diff --git a/regolith/filter_nodejs.go b/regolith/filter_nodejs.go index 3d154ea4..f68e5b24 100644 --- a/regolith/filter_nodejs.go +++ b/regolith/filter_nodejs.go @@ -41,7 +41,7 @@ func (f *NodeJSFilter) run(context RunContext) error { f.Arguments..., ), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -56,7 +56,7 @@ func (f *NodeJSFilter) run(context RunContext) error { f.Definition.Script, string(jsonSettings)}, f.Arguments...), context.AbsoluteLocation, - GetAbsoluteWorkingDirectory(), + GetAbsoluteWorkingDirectory(context.DotRegolithPath), ShortFilterName(f.Id), ) if err != nil { @@ -85,11 +85,11 @@ func (f *NodeJSFilterDefinition) CreateFilterRunner(runConfiguration map[string] return filter, nil } -func (f *NodeJSFilterDefinition) InstallDependencies(parent *RemoteFilterDefinition) error { +func (f *NodeJSFilterDefinition) InstallDependencies(parent *RemoteFilterDefinition, dotRegolithPath string) error { installLocation := "" // Install dependencies if parent != nil { - installLocation = parent.GetDownloadPath() + installLocation = parent.GetDownloadPath(dotRegolithPath) } Logger.Infof("Downloading dependencies for %s...", f.Id) scriptPath, err := filepath.Abs(filepath.Join(installLocation, f.Script)) diff --git a/regolith/filter_profile.go b/regolith/filter_profile.go index 5d97de08..c8a4dea3 100644 --- a/regolith/filter_profile.go +++ b/regolith/filter_profile.go @@ -13,6 +13,7 @@ func (f *ProfileFilter) Run(context RunContext) (bool, error) { Config: context.Config, Parent: &context, interruptionChannel: context.interruptionChannel, + DotRegolithPath: context.DotRegolithPath, }) } @@ -30,5 +31,7 @@ func (f *ProfileFilter) Check(context RunContext) error { } parent = parent.Parent } - return CheckProfileImpl(profile, f.Profile, *context.Config, &context) + return CheckProfileImpl( + profile, f.Profile, *context.Config, &context, + context.DotRegolithPath) } diff --git a/regolith/filter_python.go b/regolith/filter_python.go index b06bf290..cf3a1d02 100644 --- a/regolith/filter_python.go +++ b/regolith/filter_python.go @@ -41,7 +41,7 @@ func (f *PythonFilter) run(context RunContext) error { } scriptPath := filepath.Join(context.AbsoluteLocation, f.Definition.Script) if needsVenv(filepath.Dir(scriptPath)) { - venvPath, err := f.Definition.resolveVenvPath() + venvPath, err := f.Definition.resolveVenvPath(context.DotRegolithPath) if err != nil { return WrapError(err, "Failed to resolve venv path.") } @@ -60,7 +60,9 @@ func (f *PythonFilter) run(context RunContext) error { ) } err = RunSubProcess( - pythonCommand, args, context.AbsoluteLocation, GetAbsoluteWorkingDirectory(), ShortFilterName(f.Id)) + pythonCommand, args, context.AbsoluteLocation, + GetAbsoluteWorkingDirectory(context.DotRegolithPath), + ShortFilterName(f.Id)) if err != nil { return WrapError(err, "Failed to run Python script.") } @@ -86,11 +88,13 @@ func (f *PythonFilterDefinition) CreateFilterRunner(runConfiguration map[string] return filter, nil } -func (f *PythonFilterDefinition) InstallDependencies(parent *RemoteFilterDefinition) error { +func (f *PythonFilterDefinition) InstallDependencies( + parent *RemoteFilterDefinition, dotRegolithPath string, +) error { installLocation := "" // Install dependencies if parent != nil { - installLocation = parent.GetDownloadPath() + installLocation = parent.GetDownloadPath(dotRegolithPath) } Logger.Infof("Downloading dependencies for %s...", f.Id) scriptPath, err := filepath.Abs(filepath.Join(installLocation, f.Script)) @@ -101,7 +105,7 @@ func (f *PythonFilterDefinition) InstallDependencies(parent *RemoteFilterDefinit // Install the filter dependencies filterPath := filepath.Dir(scriptPath) if needsVenv(filterPath) { - venvPath, err := f.resolveVenvPath() + venvPath, err := f.resolveVenvPath(dotRegolithPath) if err != nil { return WrapError(err, "Failed to resolve venv path.") } @@ -165,9 +169,9 @@ func (f *PythonFilter) CopyArguments(parent *RemoteFilter) { f.Definition.VenvSlot = parent.Definition.VenvSlot } -func (f *PythonFilterDefinition) resolveVenvPath() (string, error) { +func (f *PythonFilterDefinition) resolveVenvPath(dotRegolithPath string) (string, error) { resolvedPath, err := filepath.Abs( - filepath.Join(".regolith/cache/venvs", strconv.Itoa(f.VenvSlot))) + filepath.Join(filepath.Join(dotRegolithPath, "cache/venvs"), strconv.Itoa(f.VenvSlot))) if err != nil { return "", WrapErrorf( err, "Unable to create venv for VenvSlot %v.", f.VenvSlot) diff --git a/regolith/filter_remote.go b/regolith/filter_remote.go index 6ac41b59..881bea4b 100644 --- a/regolith/filter_remote.go +++ b/regolith/filter_remote.go @@ -47,19 +47,19 @@ func RemoteFilterDefinitionFromObject(id string, obj map[string]interface{}) (*R func (f *RemoteFilter) run(context RunContext) error { // All other filters require safe mode to be turned off - if f.Definition.Url != StandardLibraryUrl && !IsUnlocked() { + if f.Definition.Url != StandardLibraryUrl && !IsUnlocked(context.DotRegolithPath) { return WrappedErrorf( "Safe mode is on, it protects you from potentially unsafe " + "code.\nYou may turn it off using \"regolith unlock\".", ) } Logger.Debugf("RunRemoteFilter \"%s\"", f.Definition.Url) - if !f.IsCached() { + if !f.IsCached(context.DotRegolithPath) { return WrappedErrorf( "Filter is not downloaded. Please run \"regolith install %s\".", f.Id) } - version, err := f.GetCachedVersion() + version, err := f.GetCachedVersion(context.DotRegolithPath) if err != nil { return WrapErrorf(err, "Failed to get the cached version of filter %s!", f.Id) } @@ -67,9 +67,9 @@ func (f *RemoteFilter) run(context RunContext) error { return VersionMismatchError(f.Id, f.Definition.Version, *version) } - path := f.GetDownloadPath() + path := f.GetDownloadPath(context.DotRegolithPath) absolutePath, _ := filepath.Abs(path) - filterCollection, err := f.SubfilterCollection() + filterCollection, err := f.SubfilterCollection(context.DotRegolithPath) if err != nil { return WrapErrorf( err, "Failed to get subfilters of \"%s\" filter.", @@ -89,6 +89,7 @@ func (f *RemoteFilter) run(context RunContext) error { AbsoluteLocation: absolutePath, Profile: context.Profile, Parent: context.Parent, + DotRegolithPath: context.DotRegolithPath, }) if err != nil { return WrapErrorf( @@ -120,8 +121,8 @@ func (f *RemoteFilterDefinition) CreateFilterRunner(runConfiguration map[string] // TODO - this code is almost a duplicate of the code in the // (f *RemoteFilter) SubfilterCollection() -func (f *RemoteFilterDefinition) InstallDependencies(*RemoteFilterDefinition) error { - path := filepath.Join(f.GetDownloadPath(), "filter.json") +func (f *RemoteFilterDefinition) InstallDependencies(_ *RemoteFilterDefinition, dotRegolithPath string) error { + path := filepath.Join(f.GetDownloadPath(dotRegolithPath), "filter.json") file, err := ioutil.ReadFile(path) if err != nil { @@ -152,7 +153,7 @@ func (f *RemoteFilterDefinition) InstallDependencies(*RemoteFilterDefinition) er return WrapErrorf( err, "Could not parse filter \"%s\", subfilter %v", f.Id, i) } - err = filterInstaller.InstallDependencies(f) + err = filterInstaller.InstallDependencies(f, dotRegolithPath) if err != nil { return WrapErrorf( err, @@ -176,7 +177,8 @@ func (f *RemoteFilterDefinition) Check(context RunContext) error { return WrappedErrorf( "Failed to convert \"%s\" to RemoteFilter. This is a bug.", f.Id) } - filterCollection, err := dummyFilterRunnerConverted.SubfilterCollection() + filterCollection, err := dummyFilterRunnerConverted.SubfilterCollection( + context.DotRegolithPath) if err != nil { return WrapErrorf( err, "Failed to get subfilters of \"%s\" filter.", f.Id) @@ -197,12 +199,12 @@ func (f *RemoteFilter) Check(context RunContext) error { } // CopyFilterData copies the filter's data to the data folder. -func (f *RemoteFilterDefinition) CopyFilterData(dataPath string) { +func (f *RemoteFilterDefinition) CopyFilterData(dataPath string, dotRegolithPath string) { // Move filters 'data' folder contents into 'data' // If the localDataPath already exists, we must not overwrite // Additionally, if the remote data path doesn't exist, we don't need // to do anything - remoteDataPath := path.Join(f.GetDownloadPath(), "data") + remoteDataPath := path.Join(f.GetDownloadPath(dotRegolithPath), "data") localDataPath := path.Join(dataPath, f.Id) if _, err := os.Stat(localDataPath); err == nil { Logger.Warnf( @@ -234,20 +236,20 @@ func (f *RemoteFilterDefinition) CopyFilterData(dataPath string) { } // GetDownloadPath returns the path location where the filter can be found. -func (f *RemoteFilter) GetDownloadPath() string { - return filepath.Join(".regolith/cache/filters", f.Id) +func (f *RemoteFilter) GetDownloadPath(dotRegolithPath string) string { + return filepath.Join(filepath.Join(dotRegolithPath, "cache/filters"), f.Id) } // IsCached checks whether the filter of given URL is already saved // in cache. -func (f *RemoteFilter) IsCached() bool { - _, err := os.Stat(f.GetDownloadPath()) +func (f *RemoteFilter) IsCached(dotRegolithPath string) bool { + _, err := os.Stat(f.GetDownloadPath(dotRegolithPath)) return err == nil } // GetCachedVersion returns cached version of the remote filter. -func (f *RemoteFilter) GetCachedVersion() (*string, error) { - path := filepath.Join(f.GetDownloadPath(), "filter.json") +func (f *RemoteFilter) GetCachedVersion(dotRegolithPath string) (*string, error) { + path := filepath.Join(f.GetDownloadPath(dotRegolithPath), "filter.json") file, err := ioutil.ReadFile(path) if err != nil { @@ -290,8 +292,10 @@ func FilterDefinitionFromTheInternet( } // Download -func (i *RemoteFilterDefinition) Download(isForced bool) error { - if _, err := os.Stat(i.GetDownloadPath()); err == nil { +func (i *RemoteFilterDefinition) Download( + isForced bool, dotRegolithPath string, +) error { + if _, err := os.Stat(i.GetDownloadPath(dotRegolithPath)); err == nil { if !isForced { Logger.Warnf( "The download path of the \"%s\" already exists.This should "+ @@ -300,7 +304,7 @@ func (i *RemoteFilterDefinition) Download(isForced bool) error { "passing the \"-force\" flag.", i.Id) return nil } else { - i.Uninstall() + i.Uninstall(dotRegolithPath) } } @@ -316,7 +320,7 @@ func (i *RemoteFilterDefinition) Download(isForced bool) error { err, "Unable to get download URL for filter \"%s\".", i.Id) } url := fmt.Sprintf("%s//%s?ref=%s", i.Url, i.Id, repoVersion) - downloadPath := i.GetDownloadPath() + downloadPath := i.GetDownloadPath(dotRegolithPath) _, err = os.Stat(downloadPath) downloadPathIsNew := os.IsNotExist(err) @@ -330,7 +334,7 @@ func (i *RemoteFilterDefinition) Download(isForced bool) error { "Does that filter exist?", url) } // Save the version of the filter we downloaded - i.SaveVerssionInfo(trimFilterPrefix(repoVersion, i.Id)) + i.SaveVerssionInfo(trimFilterPrefix(repoVersion, i.Id), dotRegolithPath) // Remove 'test' folder, which we never want to use (saves space on disk) testFolder := path.Join(downloadPath, "test") if _, err := os.Stat(testFolder); err == nil { @@ -343,15 +347,15 @@ func (i *RemoteFilterDefinition) Download(isForced bool) error { // SaveVersionInfo saves puts the specified version string into the // filter.json of the remote fileter. -func (i *RemoteFilterDefinition) SaveVerssionInfo(version string) error { - filterJsonMap, err := i.LoadFilterJson() +func (i *RemoteFilterDefinition) SaveVerssionInfo(version, dotRegolithPath string) error { + filterJsonMap, err := i.LoadFilterJson(dotRegolithPath) if err != nil { return WrapErrorf( err, "Could not load filter.json for \"%s\" filter.", i.Id) } filterJsonMap["version"] = version filterJson, _ := json.MarshalIndent(filterJsonMap, "", "\t") // no error - filterJsonPath := path.Join(i.GetDownloadPath(), "filter.json") + filterJsonPath := path.Join(i.GetDownloadPath(dotRegolithPath), "filter.json") err = os.WriteFile(filterJsonPath, filterJson, 0666) if err != nil { return WrapErrorf( @@ -361,8 +365,8 @@ func (i *RemoteFilterDefinition) SaveVerssionInfo(version string) error { } // LoadFilterJson loads the filter.json file of the remote filter to a map. -func (f *RemoteFilterDefinition) LoadFilterJson() (map[string]interface{}, error) { - downloadPath := f.GetDownloadPath() +func (f *RemoteFilterDefinition) LoadFilterJson(dotRegolithPath string) (map[string]interface{}, error) { + downloadPath := f.GetDownloadPath(dotRegolithPath) filterJsonPath := path.Join(downloadPath, "filter.json") filterJson, err1 := ioutil.ReadFile(filterJsonPath) var filterJsonMap map[string]interface{} @@ -374,8 +378,8 @@ func (f *RemoteFilterDefinition) LoadFilterJson() (map[string]interface{}, error } // GetInstalledVersion reads the version seaved in the filter.json -func (f *RemoteFilterDefinition) InstalledVersion() (string, error) { - filterJsonMap, err := f.LoadFilterJson() +func (f *RemoteFilterDefinition) InstalledVersion(dotRegolithPath string) (string, error) { + filterJsonMap, err := f.LoadFilterJson(dotRegolithPath) if err != nil { return "", WrapErrorf( err, "Could not load filter.json for %q filter.", f.Id) @@ -390,8 +394,8 @@ func (f *RemoteFilterDefinition) InstalledVersion() (string, error) { return versionStr, nil } -func (f *RemoteFilterDefinition) Update() error { - installedVersion, err := f.InstalledVersion() +func (f *RemoteFilterDefinition) Update(dotRegolithPath string) error { + installedVersion, err := f.InstalledVersion(dotRegolithPath) installedVersion = trimFilterPrefix(installedVersion, f.Id) if err != nil { Logger.Warnf("Unable to get installed version of filter %q.", f.Id) @@ -406,11 +410,11 @@ func (f *RemoteFilterDefinition) Update() error { Logger.Infof( "Updating filter %q to new version: %q->%q.", f.Id, installedVersion, version) - err = f.Download(true) + err = f.Download(true, dotRegolithPath) if err != nil { return PassError(err) } - err = f.InstallDependencies(f) + err = f.InstallDependencies(f, dotRegolithPath) if err != nil { return PassError(err) } @@ -424,12 +428,12 @@ func (f *RemoteFilterDefinition) Update() error { } // GetDownloadPath returns the path location where the filter can be found. -func (i *RemoteFilterDefinition) GetDownloadPath() string { - return filepath.Join(".regolith/cache/filters", i.Id) +func (i *RemoteFilterDefinition) GetDownloadPath(dotRegolithPath string) string { + return filepath.Join(filepath.Join(dotRegolithPath, "cache/filters"), i.Id) } -func (i *RemoteFilterDefinition) Uninstall() { - err := os.RemoveAll(i.GetDownloadPath()) +func (i *RemoteFilterDefinition) Uninstall(dotRegolithPath string) { + err := os.RemoveAll(i.GetDownloadPath(dotRegolithPath)) if err != nil { Logger.Error( WrapErrorf(err, "Could not remove installed filter %s.", i.Id)) diff --git a/regolith/filter_shell.go b/regolith/filter_shell.go index b71b3f11..02ee35a4 100644 --- a/regolith/filter_shell.go +++ b/regolith/filter_shell.go @@ -32,7 +32,7 @@ func ShellFilterDefinitionFromObject( } func (f *ShellFilter) Run(context RunContext) (bool, error) { - if err := f.run(f.Settings, context.AbsoluteLocation); err != nil { + if err := f.run(f.Settings, context); err != nil { return false, err } return context.IsInterrupted(), nil @@ -52,9 +52,7 @@ func (f *ShellFilterDefinition) CreateFilterRunner( return filter, nil } -func (f *ShellFilterDefinition) InstallDependencies( - parent *RemoteFilterDefinition, -) error { +func (f *ShellFilterDefinition) InstallDependencies(*RemoteFilterDefinition, string) error { return nil } @@ -76,20 +74,21 @@ var shells = [][]string{ func (f *ShellFilter) run( settings map[string]interface{}, - absoluteLocation string, + context RunContext, ) error { var err error = nil if len(settings) == 0 { err = executeCommand(f.Id, f.Definition.Command, - f.Arguments, absoluteLocation, - GetAbsoluteWorkingDirectory()) + f.Arguments, context.AbsoluteLocation, + GetAbsoluteWorkingDirectory(context.DotRegolithPath)) } else { jsonSettings, _ := json.Marshal(settings) err = executeCommand(f.Id, f.Definition.Command, append([]string{string(jsonSettings)}, f.Arguments...), - absoluteLocation, GetAbsoluteWorkingDirectory()) + context.AbsoluteLocation, + GetAbsoluteWorkingDirectory(context.DotRegolithPath)) } if err != nil { return WrapError(err, "Failed to run shell filter.") diff --git a/regolith/install_add.go b/regolith/install_add.go index c3bc739c..e2ee1c9b 100644 --- a/regolith/install_add.go +++ b/regolith/install_add.go @@ -3,6 +3,7 @@ package regolith import ( "os/exec" + "path/filepath" "strings" "golang.org/x/mod/semver" @@ -24,13 +25,13 @@ type parsedInstallFilterArg struct { // it returns an error unless the force flag is set. func installFilters( filterDefinitions map[string]FilterInstaller, force bool, - dataPath string, + dataPath, dotRegolithPath string, ) error { - err := CreateDirectoryIfNotExists(".regolith/cache/filters", true) + err := CreateDirectoryIfNotExists(filepath.Join(dotRegolithPath, "cache/filters"), true) if err != nil { return PassError(err) } - err = CreateDirectoryIfNotExists(".regolith/cache/venvs", true) + err = CreateDirectoryIfNotExists(filepath.Join(dotRegolithPath, "cache/venvs"), true) if err != nil { return PassError(err) } @@ -49,17 +50,17 @@ func installFilters( resolverUpdated = true } // Download the remote filter - err := remoteFilter.Download(force) + err := remoteFilter.Download(force, dotRegolithPath) if err != nil { return WrapErrorf( err, "Could not download %q!", name) } // Copy the data of the remote filter to the data path - remoteFilter.CopyFilterData(dataPath) + remoteFilter.CopyFilterData(dataPath, dotRegolithPath) } // Install the dependencies of the filter Logger.Infof("Installing %q filter dependencies...", name) - err = filterDefinition.InstallDependencies(nil) + err = filterDefinition.InstallDependencies(nil, dotRegolithPath) if err != nil { return WrapErrorf( err, "Failed to install dependencies for %q filter.", name) @@ -70,13 +71,13 @@ func installFilters( // updateFilters updates the filters from the list. func updateFilters( - remoteFilterDefinitions map[string]FilterInstaller, + remoteFilterDefinitions map[string]FilterInstaller, dotRegolithPath string, ) error { - err := CreateDirectoryIfNotExists(".regolith/cache/filters", true) + err := CreateDirectoryIfNotExists(filepath.Join(dotRegolithPath, "cache/filters"), true) if err != nil { return PassError(err) } - err = CreateDirectoryIfNotExists(".regolith/cache/venvs", true) + err = CreateDirectoryIfNotExists(filepath.Join(dotRegolithPath, "cache/venvs"), true) if err != nil { return PassError(err) } @@ -94,7 +95,7 @@ func updateFilters( resolverUpdated = true } // Update the filter - err := remoteFilter.Update() + err := remoteFilter.Update(dotRegolithPath) if err != nil { return WrapErrorf( err, "Could not update %q!", name) diff --git a/regolith/main_functions.go b/regolith/main_functions.go index 2d8d0df8..c2c10ebf 100644 --- a/regolith/main_functions.go +++ b/regolith/main_functions.go @@ -50,6 +50,13 @@ func Install(filters []string, force, debug bool) error { return WrapError( err, "Failed to get filter definitions from config file.") } + useAppData, err := useAppDataFromConfigMap(config) + if err != nil { + return WrapError( + err, "Failed to get the value of useAppData property from the "+ + "config file.", + ) + } // Check if the filters are already installed if force mode is disabled if !force { for _, parsedArg := range parsedArgs { @@ -88,8 +95,14 @@ func Install(filters []string, force, debug bool) error { } filterInstallers[parsedArg.name] = remoteFilterDefinition } + // Get the dotRegolithPath + dotRegolithPath, err := GetDotRegolith(useAppData, false, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } // Download the filter definitions - err = installFilters(filterInstallers, force, dataPath) + err = installFilters(filterInstallers, force, dataPath, dotRegolithPath) if err != nil { return WrapError(err, "Failed to install filters.") } @@ -133,7 +146,15 @@ func InstallAll(force, debug bool) error { if err := firstErr(err1, err2); err != nil { return WrapError(err, "Failed to load config.json.") } - err := installFilters(config.FilterDefinitions, force, config.DataPath) + // Get dotRegolithPath + dotRegolithPath, err := GetDotRegolith( + config.RegolithProject.UseAppData, false, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } + err = installFilters( + config.FilterDefinitions, force, config.DataPath, dotRegolithPath) if err != nil { return WrapError(err, "Could not install filters.") } @@ -173,8 +194,15 @@ func Update(filters []string, debug bool) error { } filterInstallers[filterName] = filterInstaller } + // Get dotRegolithPath + dotRegolithPath, err := GetDotRegolith( + config.RegolithProject.UseAppData, false, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } // Update the filters from the list - err := updateFilters(filterInstallers) + err = updateFilters(filterInstallers, dotRegolithPath) if err != nil { return WrapError(err, "Could not update filters.") } @@ -199,7 +227,14 @@ func UpdateAll(debug bool) error { if err := firstErr(err1, err2); err != nil { return WrapError(err, "Failed to load config.json.") } - err := updateFilters(config.FilterDefinitions) + // Get dotRegolithPath + dotRegolithPath, err := GetDotRegolith( + config.RegolithProject.UseAppData, false, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } + err = updateFilters(config.FilterDefinitions, dotRegolithPath) if err != nil { return WrapError(err, "Could not install filters.") } @@ -235,8 +270,15 @@ func runOrWatch(profileName string, recycled, debug, watch bool) error { return WrappedErrorf( "Profile %q does not exist in the configuration.", profileName) } + // Get dotRegolithPath + dotRegolithPath, err := GetDotRegolith( + config.RegolithProject.UseAppData, false, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } // Check the filters of the profile - err = CheckProfileImpl(profile, profileName, *config, nil) + err = CheckProfileImpl(profile, profileName, *config, nil, dotRegolithPath) if err != nil { return err } @@ -246,6 +288,7 @@ func runOrWatch(profileName string, recycled, debug, watch bool) error { Config: config, Parent: nil, Profile: profileName, + DotRegolithPath: dotRegolithPath, } if watch { // Loop until program termination (CTRL+C) context.StartWatchingSrouceFiles() @@ -338,9 +381,15 @@ func Init(debug bool) error { if err != nil { return WrapErrorf(err, "Failed to write data to %q", ConfigFilePath) } - + var ConfigurationFolders = []string{ + "packs", + "packs/data", + "packs/BP", + "packs/RP", + filepath.Join(".regolith", "cache/venvs"), + } for _, folder := range ConfigurationFolders { - err = os.Mkdir(folder, 0666) + err = os.MkdirAll(folder, 0666) if err != nil { Logger.Error("Could not create folder: %s", folder, err) } @@ -350,33 +399,127 @@ func Init(debug bool) error { return nil } -// Clean handles the "regolith clean" command. It cleans the cache from the -// ".regolith" directory. -// -// The "debug" parameter is a boolean that determines if the debug messages -// should be printed. -func Clean(debug bool, cachedStatesOnly bool) error { - InitLogging(debug) - Logger.Infof("Cleaning cache...") +// Cleans the cache folder of regolith (.regolith in normal mode or a path in +// AppData). The path to clean is determined by the dotRegolithPath parameter. +// leaveEmptyPath determines if regolith should leave an empty folder at +// dotRegolithPath +func clean(cachedStatesOnly bool, dotRegolithPath string) error { if cachedStatesOnly { err := ClearCachedStates() if err != nil { return WrapError(err, "Failed to remove cached path states.") } } else { - err := os.RemoveAll(".regolith") + err := os.RemoveAll(dotRegolithPath) if err != nil { - return WrapError(err, "failed to remove .regolith folder") + return WrapErrorf(err, "failed to remove %q folder", dotRegolithPath) } - err = os.Mkdir(".regolith", 0666) + // if leaveEmptyPath { + // err = os.MkdirAll(dotRegolithPath, 0666) + // if err != nil { + // return WrapErrorf(err, "failed to recreate %q folder", dotRegolithPath) + // } + // } + } + + return nil +} + +func CleanCurrentProject(cachedStatesOnly bool) error { + Logger.Infof("Cleaning cache...") + // Load the useAppData property form config + config, err := LoadConfigAsMap() + if err != nil { + return WrapError(err, "Unable to load config file.") + } + useAppData, err := useAppDataFromConfigMap(config) + if err != nil { + return WrapError( + err, "Failed to get the value of useAppData property from the "+ + "config file.", + ) + } + // Regolith always tries to clean the cache from AppData and from .regolith + // but the useAppData flag is used to determine which action must succeed. + // If useAppData: + // - Cleaning .regolith can silently fail + // - Cleaning AppData must succeeed + // If not useAppData: + // - Cleaning .regolith must succeeed + // - Cleaning AppData can silently fail + if useAppData { + // Can fail + Logger.Infof("Trying to clean \".regolith\" if it exists...") + clean(cachedStatesOnly, ".regolith") + // Can't fail + Logger.Infof("Cleaning the cache in application data folder...") + dotRegolithPath, err := GetDotRegolith(true, true, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } + Logger.Infof("Regolith cache folder is: %s", dotRegolithPath) + err = clean(cachedStatesOnly, dotRegolithPath) + if err != nil { + return WrapErrorf( + err, "Failed to clean the cache from %q.", dotRegolithPath) + } + } else { + // Can fail + Logger.Infof( + "Trying to clean the Regolith cache from app data folder if it exists...") + dotRegolithPath, err := GetDotRegolith(true, true, ".") + if err != nil { + clean(cachedStatesOnly, dotRegolithPath) + } + // Can't fail + Logger.Infof("Cleaning \".regolith\"...") + clean(cachedStatesOnly, ".regolith") if err != nil { - return WrapError(err, "failed to recreate .regolith folder") + return WrapErrorf( + err, "Failed to clean the cache from \".regolith\".") } } Logger.Infof("Cache cleaned.") return nil } +func CleanUserCache() error { + Logger.Infof("Cleaning all Regolith cache files from user app data...") + // App data enabled - use user cache dir + userCache, err := os.UserCacheDir() + if err != nil { + return WrappedError("Unable to get user cache dir") + } + regolithCacheFiles := filepath.Join(userCache, appDataCachePath) + Logger.Infof("Regolith cache files are located in: %s", regolithCacheFiles) + err = os.RemoveAll(regolithCacheFiles) + if err != nil { + return WrapErrorf(err, "failed to remove %q folder", regolithCacheFiles) + } + os.MkdirAll(regolithCacheFiles, 0666) + Logger.Infof("All regolith files cached in user app data cleaned.") + return nil +} + +// Clean handles the "regolith clean" command. It cleans the cache from the +// dotRegolithPath directory. +// +// The "debug" parameter is a boolean that determines if the debug messages +// should be printed. +func Clean(debug, userCache, cachedStatesOnly bool) error { + InitLogging(debug) + if userCache { + if cachedStatesOnly { + return WrappedError( + "Cannot mix --user-cache and --cached-states-only flags.") + } + return CleanUserCache() + } else { + return CleanCurrentProject(cachedStatesOnly) + } +} + // Unlock handles the "regolith unlock". It unlocks safe mode, by signing the // machine ID into lockfile.txt. // @@ -394,13 +537,25 @@ func Unlock(debug bool) error { "unable to load the \"config.json\" file.\nEvery regolith"+ "project requires a valid config file.") } - + // Get dotRegolithPath + useAppData, err := useAppDataFromConfigMap(configMap) + if err != nil { + return WrapError( + err, "Failed to get the value of useAppData property from the "+ + "config file.", + ) + } + dotRegolithPath, err := GetDotRegolith(useAppData, false, ".") + if err != nil { + return WrapError( + err, "Unable to get the path to regolith cache folder.") + } id, err := GetMachineId() if err != nil { return WrappedError("Failed to get machine ID for the lock file.") } - lockfilePath := ".regolith/cache/lockfile.txt" + lockfilePath := filepath.Join(dotRegolithPath, "cache/lockfile.txt") Logger.Infof("Creating the lock file in %s...", lockfilePath) if _, err := os.Stat(lockfilePath); err == nil { return WrappedErrorf( diff --git a/regolith/profile.go b/regolith/profile.go index 706b7c39..86d7b6fb 100644 --- a/regolith/profile.go +++ b/regolith/profile.go @@ -14,18 +14,19 @@ import ( // RecycledSetupTmpFiles set up the workspace for the filters. The function // uses cached data about the state of the project files to reduce the number // of file system operations. -func RecycledSetupTmpFiles(config Config, profile Profile) error { +func RecycledSetupTmpFiles(config Config, profile Profile, dotRegolithPath string) error { start := time.Now() - err := os.MkdirAll(".regolith/tmp", 0666) + tmpPath := filepath.Join(dotRegolithPath, "tmp") + err := os.MkdirAll(tmpPath, 0666) if err != nil { - return WrapError( - err, "Unable to prepare temporary directory: \"./regolith/tmp\".") + return WrapErrorf( + err, "Unable to prepare temporary directory: \"%s\".", tmpPath) } - // Copy the contents of the 'regolith' folder to '.regolith/tmp' + // Copy the contents of the 'regolith' folder to '[dotRegolith]/tmp' if config.ResourceFolder != "" { - Logger.Debug("Copying project files to .regolith/tmp") + Logger.Debugf("Copying project files to \"%s\"", tmpPath) err = FullRecycledMoveOrCopy( - config.ResourceFolder, ".regolith/tmp/RP", + config.ResourceFolder, filepath.Join(tmpPath, "RP"), RecycledMoveOrCopySettings{ canMove: false, saveSourceHashes: false, @@ -40,7 +41,7 @@ func RecycledSetupTmpFiles(config Config, profile Profile) error { } if config.BehaviorFolder != "" { err = FullRecycledMoveOrCopy( - config.BehaviorFolder, ".regolith/tmp/BP", + config.BehaviorFolder, filepath.Join(tmpPath, "BP"), RecycledMoveOrCopySettings{ canMove: false, saveSourceHashes: false, @@ -55,7 +56,7 @@ func RecycledSetupTmpFiles(config Config, profile Profile) error { } if config.DataPath != "" { err = FullRecycledMoveOrCopy( - config.DataPath, ".regolith/tmp/data", + config.DataPath, filepath.Join(tmpPath, "data"), RecycledMoveOrCopySettings{ canMove: false, saveSourceHashes: false, @@ -74,66 +75,67 @@ func RecycledSetupTmpFiles(config Config, profile Profile) error { } // SetupTmpFiles set up the workspace for the filters. -func SetupTmpFiles(config Config, profile Profile) error { +func SetupTmpFiles(config Config, profile Profile, dotRegolithPath string) error { start := time.Now() // Setup Directories - Logger.Debug("Cleaning .regolith/tmp") - err := os.RemoveAll(".regolith/tmp") + tmpPath := filepath.Join(dotRegolithPath, "tmp") + Logger.Debugf("Cleaning \"%s\"", tmpPath) + err := os.RemoveAll(tmpPath) if err != nil { - return WrapError( - err, "Unable to clean temporary directory: \".regolith/tmp\".") + return WrapErrorf( + err, "Unable to clean temporary directory: \"%s\".", tmpPath) } - err = os.MkdirAll(".regolith/tmp", 0666) + err = os.MkdirAll(tmpPath, 0666) if err != nil { - return WrapError( - err, "Unable to prepare temporary directory: \"./regolith/tmp\".") + return WrapErrorf( + err, "Unable to prepare temporary directory: \"%s\".", tmpPath) } - // Copy the contents of the 'regolith' folder to '.regolith/tmp' - Logger.Debug("Copying project files to .regolith/tmp") + // Copy the contents of the 'regolith' folder to '[dotRegolithPath]/tmp' + Logger.Debugf("Copying project files to \"%s\"", tmpPath) // Avoid repetetive code of preparing ResourceFolder, BehaviorFolder // and DataPath with a closure setup_tmp_directory := func( - path, short_name, descriptive_name string, + path, shortName, descriptiveName string, ) error { + p := filepath.Join(tmpPath, shortName) if path != "" { stats, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { Logger.Warnf( - "%s %q does not exist", descriptive_name, path) - err = os.MkdirAll( - fmt.Sprintf(".regolith/tmp/%s", short_name), 0666) + "%s %q does not exist", descriptiveName, path) + err = os.MkdirAll(p, 0666) if err != nil { return WrapErrorf( err, - "Failed to create \".regolith/tmp/%s\" directory.", - short_name) + "Failed to create \"%s\" directory.", + p) } } } else if stats.IsDir() { err = copy.Copy( - path, fmt.Sprintf(".regolith/tmp/%s", short_name), + path, + p, copy.Options{PreserveTimes: false, Sync: false}) if err != nil { return WrapErrorf( err, - "Failed to copy %s %q to \".regolith/tmp/%s\".", - descriptive_name, path, short_name) + "Failed to copy %s %q to %q.", + descriptiveName, path, p) } } else { // The folder paths leads to a file return WrappedErrorf( - "%s path %q is not a directory", descriptive_name, path) + "%s path %q is not a directory", descriptiveName, path) } } else { - err = os.MkdirAll( - fmt.Sprintf(".regolith/tmp/%s", short_name), 0666) + err = os.MkdirAll(p, 0666) if err != nil { return WrapErrorf( err, - "Failed to create \".regolith/tmp/%s\" directory.", - short_name) + "Failed to create %q directory.", + p) } } return nil @@ -159,10 +161,18 @@ func SetupTmpFiles(config Config, profile Profile) error { return nil } -func CheckProfileImpl(profile Profile, profileName string, config Config, parentContext *RunContext) error { +func CheckProfileImpl( + profile Profile, profileName string, config Config, + parentContext *RunContext, dotRegolithPath string, +) error { // Check whether every filter, uses a supported filter type for _, f := range profile.Filters { - err := f.Check(RunContext{Config: &config, Parent: parentContext, Profile: profileName}) + err := f.Check(RunContext{ + Config: &config, + Parent: parentContext, + Profile: profileName, + DotRegolithPath: dotRegolithPath, + }) if err != nil { return WrapErrorf(err, "Filter check failed.") } @@ -179,9 +189,9 @@ func RecycledRunProfile(context RunContext) error { // saveTmp saves the state of the tmp files. This is useful only if runnig // in the watch mode. saveTmp := func() error { - err1 := SaveStateInDefaultCache(".regolith/tmp/RP") - err2 := SaveStateInDefaultCache(".regolith/tmp/BP") - err3 := SaveStateInDefaultCache(".regolith/tmp/data") + err1 := SaveStateInDefaultCache(filepath.Join(context.DotRegolithPath, "tmp/RP")) + err2 := SaveStateInDefaultCache(filepath.Join(context.DotRegolithPath, "tmp/BP")) + err3 := SaveStateInDefaultCache(filepath.Join(context.DotRegolithPath, "tmp/data")) if err := firstErr(err1, err2, err3); err != nil { err1 := ClearCachedStates() // Just to be safe - clear cached states if err1 != nil { @@ -202,7 +212,7 @@ start: if !ok { return WrappedErrorf("Unable to get profile %s", context.Profile) } - err := RecycledSetupTmpFiles(*context.Config, profile) + err := RecycledSetupTmpFiles(*context.Config, profile, context.DotRegolithPath) if err != nil { err1 := ClearCachedStates() // Just to be safe clear cached states if err1 != nil { @@ -233,7 +243,7 @@ start: Logger.Info("Moving files to target directory.") start := time.Now() err = RecycledExportProject( - profile, context.Config.Name, context.Config.DataPath) + profile, context.Config.Name, context.Config.DataPath, context.DotRegolithPath) if err != nil { err1 := ClearCachedStates() // Just to be safe clear cached states if err1 != nil { @@ -266,7 +276,7 @@ start: if !ok { return WrappedErrorf("Unable to get profile %s", context.Profile) } - err := SetupTmpFiles(*context.Config, profile) + err := SetupTmpFiles(*context.Config, profile, context.DotRegolithPath) if err != nil { return WrapError(err, "Unable to setup profile.") } @@ -285,7 +295,7 @@ start: Logger.Info("Moving files to target directory.") start := time.Now() err = ExportProject( - profile, context.Config.Name, context.Config.DataPath) + profile, context.Config.Name, context.Config.DataPath, context.DotRegolithPath) if err != nil { return WrapError(err, "Exporting project failed.") } @@ -338,8 +348,8 @@ func WatchProfileImpl(context RunContext) (bool, error) { // SubfilterCollection returns a collection of filters from a // "filter.json" file of a remote filter. -func (f *RemoteFilter) SubfilterCollection() (*FilterCollection, error) { - path := filepath.Join(f.GetDownloadPath(), "filter.json") +func (f *RemoteFilter) SubfilterCollection(dotRegolithPath string) (*FilterCollection, error) { + path := filepath.Join(f.GetDownloadPath(dotRegolithPath), "filter.json") result := &FilterCollection{Filters: []FilterRunner{}} file, err := ioutil.ReadFile(path) diff --git a/regolith/safe_mode.go b/regolith/safe_mode.go index 0f231843..9dc6b7a0 100644 --- a/regolith/safe_mode.go +++ b/regolith/safe_mode.go @@ -2,12 +2,13 @@ package regolith import ( "io/ioutil" + "path/filepath" "github.com/denisbrodbeck/machineid" ) // Returns true if safe mode is unlocked -func IsUnlocked() bool { +func IsUnlocked(dotRegolithPath string) bool { // TODO - maybe consider caching this result to avoid reading the file every time id, err := GetMachineId() if err != nil { @@ -15,7 +16,7 @@ func IsUnlocked() bool { return false } - lockedId, err := ioutil.ReadFile(".regolith/cache/lockfile.txt") + lockedId, err := ioutil.ReadFile(filepath.Join(dotRegolithPath, "cache/lockfile.txt")) if err != nil { return false } diff --git a/regolith/utils.go b/regolith/utils.go index e5c634b2..0d2f5a17 100644 --- a/regolith/utils.go +++ b/regolith/utils.go @@ -2,6 +2,8 @@ package regolith import ( "bufio" + "crypto/md5" + "encoding/hex" "errors" "fmt" "io" @@ -23,6 +25,10 @@ const ( "filters.\n You can download Git from https://git-scm.com/downloads" ) +// appDataCachePath is a path to the cache directory relative to the user's +// app data +const appDataCachePath = "regolith/project-cache" + func StringArrayContains(arr []string, str string) bool { for _, a := range arr { if a == str { @@ -172,9 +178,9 @@ func CreateDirectoryIfNotExists(directory string, mustSucceed bool) error { return nil } -// GetAbsoluteWorkingDirectory returns an absolute path to .regolith/tmp -func GetAbsoluteWorkingDirectory() string { - absoluteWorkingDir, _ := filepath.Abs(".regolith/tmp") +// GetAbsoluteWorkingDirectory returns an absolute path to [dotRegolithPath]/tmp +func GetAbsoluteWorkingDirectory(dotRegolithPath string) string { + absoluteWorkingDir, _ := filepath.Abs(filepath.Join(dotRegolithPath, "tmp")) return absoluteWorkingDir } @@ -297,3 +303,41 @@ func MoveOrCopy( } return nil } + +// GetDotRegolith returns the path to the directory where Regolith stores +// its cached data (like filters, Python venvs, etc.). If useAppData is set to +// false it returns relative director: ".regolith" otherwise it returns path +// inside the AppData directory. Based on the hash value of the +// project's root directory. If the path isn't .regolith it also logs a message +// which tells where the data is stored unless the silent flag is set to true. +// The projectRoot path can be relative or absolute and is resolved to an +// absolute path. +func GetDotRegolith(useAppData, silent bool, projectRoot string) (string, error) { + // App data diabled - use .regolith + if !useAppData { + return ".regolith", nil + } + // App data enabled - use user cache dir + userCache, err := os.UserCacheDir() + if err != nil { + return "", WrappedError("Unable to get user cache dir") + } + // Make sure that projectsRoot is an absolute path + absoluteProjectRoot, err := filepath.Abs(projectRoot) + if err != nil { + return "", WrapErrorf( + err, "Unable to get absolute of %q.", projectRoot) + } + // Get the md5 of the project path + hash := md5.New() + hash.Write([]byte(absoluteProjectRoot)) + hashInBytes := hash.Sum(nil) + projectPathHash := hex.EncodeToString(hashInBytes) + // %userprofile%/AppData/Local/regolith/ + dotRegolithPath := filepath.Join( + userCache, appDataCachePath, projectPathHash) + if !silent { + Logger.Infof("Regolith cache will be stored in: %s", dotRegolithPath) + } + return dotRegolithPath, nil +}