blob: 96d2bc7a8e1a9edfe21a6d5864b170069559d5d3 [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package build
import (
"fmt"
"path"
"path/filepath"
"strings"
"dawn.googlesource.com/dawn/tools/src/cmd/gen/common"
"dawn.googlesource.com/dawn/tools/src/container"
"dawn.googlesource.com/dawn/tools/src/fileutils"
)
// Project holds information about all the directories, targets and source files
// that makes up a project.
type Project struct {
// The command line config
cfg *common.Config
// The absolute path to the root of the project
Root string
// A map of project-relative path to File.
Files container.Map[string, *File]
// A map of project-relative path to Directory.
Directories container.Map[string, *Directory]
// A map of target name to target.
Targets container.Map[TargetName, *Target]
// A list of external project dependencies used by the project
externals container.Map[ExternalDependencyName, ExternalDependency]
// Path to the 'externals.json' file
externalsJsonPath string
}
// NewProject returns a newly initialized Project
func NewProject(root string, cfg *common.Config) *Project {
return &Project{
cfg: cfg,
Root: root,
Files: container.NewMap[string, *File](),
Directories: container.NewMap[string, *Directory](),
Targets: container.NewMap[TargetName, *Target](),
externals: container.NewMap[ExternalDependencyName, ExternalDependency](),
externalsJsonPath: filepath.Join(fileutils.DawnRoot(), "src", "tint", "externals.json"),
}
}
// AddFile gets or creates a File with the given project-relative path
func (p *Project) AddFile(filepath string) *File {
file := p.Files.GetOrCreate(filepath, func() *File {
dir, name := path.Split(filepath)
return &File{
Directory: p.Directory(dir),
Name: name,
TransitiveDependencies: NewDependencies(p),
}
})
if file.IsGenerated {
panic("AddFile() called with path that already exists for generated file")
}
return file
}
// AddGeneratedFile gets or creates a generated File with the given project-relative path
func (p *Project) AddGeneratedFile(filepath string) *File {
file := p.Files.GetOrCreate(filepath, func() *File {
dir, name := path.Split(filepath)
return &File{
IsGenerated: true,
Directory: p.Directory(dir),
Name: name,
TransitiveDependencies: NewDependencies(p),
}
})
if !file.IsGenerated {
panic("AddGeneratedFile() called with path that already exists for non-generated file")
}
return file
}
// File returns the File with the given project-relative path
func (p *Project) File(file string) *File {
return p.Files[file]
}
// AddTarget gets or creates a Target with the given Directory and TargetKind
func (p *Project) AddTarget(dir *Directory, kind TargetKind) *Target {
name := p.TargetName(dir, kind)
return p.Targets.GetOrCreate(name, func() *Target {
t := &Target{
Name: name,
Directory: dir,
Kind: kind,
SourceFileSet: container.NewSet[string](),
GeneratedFileSet: container.NewSet[string](),
Dependencies: NewDependencies(p),
}
dir.TargetNames.Add(name)
p.Targets.Add(name, t)
return t
})
}
// Target returns the Target with the given Directory and TargetKind
func (p *Project) Target(dir *Directory, kind TargetKind) *Target {
return p.Targets[p.TargetName(dir, kind)]
}
// TargetName returns the TargetName of a target in dir with the given kind
func (p *Project) TargetName(dir *Directory, kind TargetKind) TargetName {
name := TargetName(dir.Path)
if kind != targetLib {
name += TargetName(fmt.Sprintf(":%v", kind))
}
return name
}
// AddDirectory gets or creates a Directory with the given project-relative path
func (p *Project) AddDirectory(path string) *Directory {
path = CanonicalizePath(path)
return p.Directories.GetOrCreate(path, func() *Directory {
split := strings.Split(path, "/")
d := &Directory{
Project: p,
Name: split[len(split)-1],
Path: path,
TargetNames: container.NewSet[TargetName](),
SubdirectoryNames: container.NewSet[string](),
}
p.Directories[path] = d
if path != "" {
d.Parent = p.AddDirectory(strings.Join(split[:len(split)-1], "/"))
d.Parent.SubdirectoryNames.Add(d.Name)
}
return d
})
}
// Directory returns the Directory with the given project-relative path
func (p *Project) Directory(path string) *Directory {
return p.Directories[CanonicalizePath(path)]
}
// CanonicalizePath canonicalizes the given path by changing path delimiters to
// '/' and removing any trailing slash
func CanonicalizePath(path string) string {
return strings.TrimSuffix(filepath.ToSlash(path), "/")
}