| // Copyright 2020 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package workspace |
| |
| import ( |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "android.googlesource.com/platform/tools/treble.git/hacksaw/bind" |
| "android.googlesource.com/platform/tools/treble.git/hacksaw/git" |
| ) |
| |
| type Composer struct { |
| pathBinder bind.PathBinder |
| } |
| |
| func NewComposer(bm bind.PathBinder) Composer { |
| return Composer{bm} |
| } |
| |
| func isDirEmpty(name string) (bool, error) { |
| dir, err := os.Open(name) |
| if err != nil { |
| return false, err |
| } |
| defer dir.Close() |
| _, err = dir.Readdirnames(1) |
| if err == io.EOF { |
| return true, nil |
| } |
| return false, err |
| } |
| |
| //Compose a workspace from a codebase |
| //Returns a list of path binds in the order they |
| //were bound |
| func (m Composer) Compose(codebasePath string, workspacePath string) ([]string, error) { |
| lister := git.NewRepoLister() |
| gitProjects, err := lister.List(codebasePath) |
| if err != nil { |
| return nil, err |
| } |
| fmt.Print("Composing") |
| var bindList []string |
| //Sorting the list of projects in alphabetical |
| //order ensures that parent projects are bound |
| //before their nested child projects, which is important |
| //to avoid bind conflicts |
| sort.Strings(gitProjects) |
| for _, project := range gitProjects { |
| fmt.Print(".") //Display some progress |
| //skip empty project names |
| if project == "" { |
| continue |
| } |
| source := filepath.Join(codebasePath, project) |
| destination := filepath.Join(workspacePath, project) |
| if err = os.MkdirAll(destination, os.ModePerm); err != nil { |
| fmt.Print("\n") |
| return bindList, err |
| } |
| isEmpty, err := isDirEmpty(destination) |
| if err != nil { |
| return bindList, err |
| } |
| if !isEmpty { |
| // If the destination dir already existed and |
| // was not empty then assume we are recreating |
| // a workspace and the current path already |
| // existed in the workspace |
| continue |
| } |
| if err = m.pathBinder.BindReadOnly(source, destination); err != nil { |
| fmt.Print("\n") |
| return bindList, err |
| } |
| bindList = append(bindList, destination) |
| } |
| fmt.Print("\n") |
| fmt.Println("Workspace composed") |
| copier := NewFileCopier() |
| return bindList, copier.Copy(codebasePath, gitProjects, workspacePath) |
| } |
| |
| //Dismantle a workspace |
| //Returns a list of path unbinds in the order they |
| //were unbound |
| func (m Composer) Dismantle(dismantlePath string) ([]string, error) { |
| bindList, err := m.List(dismantlePath) |
| if err != nil { |
| return nil, err |
| } |
| //Sorting the list of binds in reverse alphabetical |
| //order ensures that nested child projects are unbound |
| //before their parent projects, which is important |
| //to avoid unbind conflicts |
| sort.Sort(sort.Reverse(sort.StringSlice(bindList))) |
| fmt.Print("Dismantling") |
| var unbindList []string |
| for _, bindPath := range bindList { |
| fmt.Print(".") //Display some progress |
| if err = m.pathBinder.Unbind(bindPath); err != nil { |
| fmt.Print("\n") |
| return unbindList, err |
| } |
| unbindList = append(unbindList, bindPath) |
| } |
| fmt.Print("\n") |
| fmt.Println("Workspace dismantled") |
| return unbindList, err |
| } |
| |
| //Unbind a project |
| func (m Composer) Unbind(unbindPath string) error { |
| return m.pathBinder.Unbind(unbindPath) |
| } |
| |
| //List all binds attached under a directory |
| func (m Composer) List(listPath string) ([]string, error) { |
| listPath, err := filepath.EvalSymlinks(listPath) |
| if err != nil { |
| return nil, err |
| } |
| fullBindList, err := m.pathBinder.List() |
| if err != nil { |
| return nil, err |
| } |
| var matchBindList []string |
| for _, bindPath := range fullBindList { |
| if strings.HasPrefix(bindPath+"/", listPath+"/") { |
| matchBindList = append(matchBindList, bindPath) |
| } |
| } |
| return matchBindList, err |
| } |