1// Copyright 2020 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package workspace 16 17import ( 18 "fmt" 19 "io" 20 "os" 21 "path/filepath" 22 "sort" 23 "strings" 24 25 "android.googlesource.com/platform/tools/treble.git/hacksaw/bind" 26 "android.googlesource.com/platform/tools/treble.git/hacksaw/git" 27) 28 29type Composer struct { 30 pathBinder bind.PathBinder 31} 32 33func NewComposer(bm bind.PathBinder) Composer { 34 return Composer{bm} 35} 36 37func isDirEmpty(name string) (bool, error) { 38 dir, err := os.Open(name) 39 if err != nil { 40 return false, err 41 } 42 defer dir.Close() 43 _, err = dir.Readdirnames(1) 44 if err == io.EOF { 45 return true, nil 46 } 47 return false, err 48} 49 50//Compose a workspace from a codebase 51//Returns a list of path binds in the order they 52//were bound 53func (m Composer) Compose(codebasePath string, workspacePath string) ([]string, error) { 54 lister := git.NewRepoLister() 55 gitProjects, err := lister.List(codebasePath) 56 if err != nil { 57 return nil, err 58 } 59 fmt.Print("Composing") 60 var bindList []string 61 //Sorting the list of projects in alphabetical 62 //order ensures that parent projects are bound 63 //before their nested child projects, which is important 64 //to avoid bind conflicts 65 sort.Strings(gitProjects) 66 for _, project := range gitProjects { 67 fmt.Print(".") //Display some progress 68 //skip empty project names 69 if project == "" { 70 continue 71 } 72 source := filepath.Join(codebasePath, project) 73 destination := filepath.Join(workspacePath, project) 74 if err = os.MkdirAll(destination, os.ModePerm); err != nil { 75 fmt.Print("\n") 76 return bindList, err 77 } 78 isEmpty, err := isDirEmpty(destination) 79 if err != nil { 80 return bindList, err 81 } 82 if !isEmpty { 83 // If the destination dir already existed and 84 // was not empty then assume we are recreating 85 // a workspace and the current path already 86 // existed in the workspace 87 continue 88 } 89 if err = m.pathBinder.BindReadOnly(source, destination); err != nil { 90 fmt.Print("\n") 91 return bindList, err 92 } 93 bindList = append(bindList, destination) 94 } 95 fmt.Print("\n") 96 fmt.Println("Workspace composed") 97 copier := NewFileCopier() 98 return bindList, copier.Copy(codebasePath, gitProjects, workspacePath) 99} 100 101//Dismantle a workspace 102//Returns a list of path unbinds in the order they 103//were unbound 104func (m Composer) Dismantle(dismantlePath string) ([]string, error) { 105 bindList, err := m.List(dismantlePath) 106 if err != nil { 107 return nil, err 108 } 109 //Sorting the list of binds in reverse alphabetical 110 //order ensures that nested child projects are unbound 111 //before their parent projects, which is important 112 //to avoid unbind conflicts 113 sort.Sort(sort.Reverse(sort.StringSlice(bindList))) 114 fmt.Print("Dismantling") 115 var unbindList []string 116 for _, bindPath := range bindList { 117 fmt.Print(".") //Display some progress 118 if err = m.pathBinder.Unbind(bindPath); err != nil { 119 fmt.Print("\n") 120 return unbindList, err 121 } 122 unbindList = append(unbindList, bindPath) 123 } 124 fmt.Print("\n") 125 fmt.Println("Workspace dismantled") 126 return unbindList, err 127} 128 129//Unbind a project 130func (m Composer) Unbind(unbindPath string) error { 131 return m.pathBinder.Unbind(unbindPath) 132} 133 134//List all binds attached under a directory 135func (m Composer) List(listPath string) ([]string, error) { 136 listPath, err := filepath.EvalSymlinks(listPath) 137 if err != nil { 138 return nil, err 139 } 140 fullBindList, err := m.pathBinder.List() 141 if err != nil { 142 return nil, err 143 } 144 var matchBindList []string 145 for _, bindPath := range fullBindList { 146 if strings.HasPrefix(bindPath+"/", listPath+"/") { 147 matchBindList = append(matchBindList, bindPath) 148 } 149 } 150 return matchBindList, err 151} 152