blob: f1cc2c8b396121cd821a3a62b7120c9dd8788254 [file] [log] [blame]
// 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
}