// 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 }