Use `Path` instead of string for file paths

This centralizes verification and common operations, like converting the
path to a source file to the path for a built object.

It also embeds the configuration knowledge into the path, so that we can
remove "${SrcDir}/path" from the ninja file. When SrcDir is '.', that
leads to paths like './path' instead of just 'path' like make is doing,
causing differences in compiled binaries.

Change-Id: Ib4e8910a6e867ce1b7b420d927c04f1142a7589e
diff --git a/common/androidmk.go b/common/androidmk.go
index 5dd422d..06aa30c 100644
--- a/common/androidmk.go
+++ b/common/androidmk.go
@@ -37,11 +37,11 @@
 
 type AndroidMkData struct {
 	Class      string
-	OutputFile string
+	OutputFile OptionalPath
 
 	Custom func(w io.Writer, name, prefix string)
 
-	Extra func(name, prefix, outputFile string, arch Arch) []string
+	Extra func(name, prefix string, outputFile Path, arch Arch) []string
 }
 
 func AndroidMkSingleton() blueprint.Singleton {
@@ -55,7 +55,7 @@
 	hasBPDir := make(map[string]bool)
 	bpDirs := []string{}
 
-	ctx.SetNinjaBuildDir(pctx, filepath.Join(ctx.Config().(Config).BuildDir(), ".."))
+	ctx.SetNinjaBuildDir(pctx, filepath.Join(ctx.Config().(Config).buildDir, ".."))
 
 	ctx.VisitAllModules(func(module blueprint.Module) {
 		if _, ok := module.(AndroidModule); ok {
@@ -72,28 +72,13 @@
 
 	// Gather list of eligible Android modules for translation
 	androidMkModules := make(map[blueprint.Module]bool)
-	srcDir := ctx.Config().(Config).SrcDir()
-	intermediatesDir := filepath.Join(ctx.Config().(Config).IntermediatesDir(), "androidmk")
 	sort.Strings(bpDirs)
 	for _, bpDir := range bpDirs {
-		mkFile := filepath.Join(srcDir, bpDir, "Android.mk")
-
-		files, err := Glob(ctx, intermediatesDir, mkFile, nil)
-		if err != nil {
-			ctx.Errorf("glob: %s", err.Error())
-			continue
-		}
-
-		// Existing Android.mk file, use that instead
-		if len(files) > 0 {
-			for _, file := range files {
-				ctx.AddNinjaFileDeps(file)
+		mkFile := OptionalPathForSource(ctx, "androidmk", bpDir, "Android.mk")
+		if !mkFile.Valid() {
+			for _, mod := range dirModules[bpDir] {
+				androidMkModules[mod] = true
 			}
-			continue
-		}
-
-		for _, mod := range dirModules[bpDir] {
-			androidMkModules[mod] = true
 		}
 	}
 
@@ -110,16 +95,19 @@
 		}
 	}
 
-	transMk := filepath.Join(ctx.Config().(Config).BuildDir(), "Android.mk")
+	transMk := PathForOutput(ctx, "Android.mk")
+	if ctx.Failed() {
+		return
+	}
 
-	err := translateAndroidMk(ctx, transMk, androidMkModulesList)
+	err := translateAndroidMk(ctx, transMk.String(), androidMkModulesList)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
 
 	ctx.Build(pctx, blueprint.BuildParams{
 		Rule:     blueprint.Phony,
-		Outputs:  []string{transMk},
+		Outputs:  []string{transMk.String()},
 		Optional: true,
 	})
 }
@@ -177,7 +165,7 @@
 
 	type archSrc struct {
 		arch  Arch
-		src   string
+		src   Path
 		extra []string
 	}
 
@@ -211,6 +199,10 @@
 			return
 		}
 
+		if !data.OutputFile.Valid() {
+			return
+		}
+
 		hC := hostClass{
 			host:     amod.HostOrDevice() == Host,
 			class:    data.Class,
@@ -219,7 +211,7 @@
 
 		src := archSrc{
 			arch: arch,
-			src:  data.OutputFile,
+			src:  data.OutputFile.Path(),
 		}
 
 		if data.Extra != nil {
@@ -242,7 +234,7 @@
 
 		printed := make(map[string]bool)
 		for _, src := range archSrcs {
-			io.WriteString(w, "LOCAL_SRC_FILES_"+src.arch.ArchType.String()+" := "+src.src+"\n")
+			io.WriteString(w, "LOCAL_SRC_FILES_"+src.arch.ArchType.String()+" := "+src.src.String()+"\n")
 
 			for _, extra := range src.extra {
 				if !printed[extra] {
diff --git a/common/config.go b/common/config.go
index c67023e..7f6ee65 100644
--- a/common/config.go
+++ b/common/config.go
@@ -20,6 +20,7 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 	"sync"
 )
 
@@ -38,8 +39,6 @@
 
 type Config struct {
 	*config
-
-	dontCreateNinjaFile bool
 }
 
 // A config object represents the entire build configuration for Android.
@@ -142,8 +141,24 @@
 		},
 	}
 
+	// Sanity check the build and source directories. This won't catch strange
+	// configurations with symlinks, but at least checks the obvious cases.
+	absBuildDir, err := filepath.Abs(buildDir)
+	if err != nil {
+		return Config{}, err
+	}
+
+	absSrcDir, err := filepath.Abs(srcDir)
+	if err != nil {
+		return Config{}, err
+	}
+
+	if strings.HasPrefix(absSrcDir, absBuildDir) {
+		return Config{}, fmt.Errorf("Build dir must not contain source directory")
+	}
+
 	// Load any configurable options from the configuration file
-	err := loadConfig(config.config)
+	err = loadConfig(config.config)
 	if err != nil {
 		return Config{}, err
 	}
@@ -159,18 +174,6 @@
 	return config, nil
 }
 
-func (c *config) SrcDir() string {
-	return c.srcDir
-}
-
-func (c *config) BuildDir() string {
-	return c.buildDir
-}
-
-func (c *config) IntermediatesDir() string {
-	return filepath.Join(c.BuildDir(), ".intermediates")
-}
-
 func (c *config) RemoveAbandonedFiles() bool {
 	return false
 }
@@ -238,37 +241,7 @@
 	return false
 }
 
-// DeviceOut returns the path to out directory for device targets
-func (c *config) DeviceOut() string {
-	return filepath.Join(c.BuildDir(), "target/product", c.DeviceName())
-}
-
-// HostOut returns the path to out directory for host targets
-func (c *config) HostOut() string {
-	return filepath.Join(c.BuildDir(), "host", c.PrebuiltOS())
-}
-
-// HostBin returns the path to bin directory for host targets
-func (c *config) HostBin() string {
-	return filepath.Join(c.HostOut(), "bin")
-}
-
-// HostBinTool returns the path to a host tool in the bin directory for host targets
-func (c *config) HostBinTool(tool string) (string, error) {
-	return filepath.Join(c.HostBin(), tool), nil
-}
-
-// HostJavaDir returns the path to framework directory for host targets
-func (c *config) HostJavaDir() string {
-	return filepath.Join(c.HostOut(), "framework")
-}
-
-// HostJavaTool returns the path to a host tool in the frameworks directory for host targets
-func (c *config) HostJavaTool(tool string) (string, error) {
-	return filepath.Join(c.HostJavaDir(), tool), nil
-}
-
-func (c *config) ResourceOverlays() []string {
+func (c *config) ResourceOverlays() []SourcePath {
 	return nil
 }
 
@@ -296,10 +269,10 @@
 	return "nosdcard"
 }
 
-func (c *config) DefaultAppCertificateDir() string {
-	return filepath.Join(c.SrcDir(), "build/target/product/security")
+func (c *config) DefaultAppCertificateDir(ctx PathContext) SourcePath {
+	return PathForSource(ctx, "build/target/product/security")
 }
 
-func (c *config) DefaultAppCertificate() string {
-	return filepath.Join(c.DefaultAppCertificateDir(), "testkey")
+func (c *config) DefaultAppCertificate(ctx PathContext) SourcePath {
+	return c.DefaultAppCertificateDir(ctx).Join(ctx, "testkey")
 }
diff --git a/common/defs.go b/common/defs.go
index 7b2a706..9e185e4 100644
--- a/common/defs.go
+++ b/common/defs.go
@@ -20,13 +20,11 @@
 )
 
 var (
-	pctx = blueprint.NewPackageContext("android/soong/common")
+	pctx = NewPackageContext("android/soong/common")
 
 	cpPreserveSymlinks = pctx.VariableConfigMethod("cpPreserveSymlinks",
 		Config.CpPreserveSymlinksFlags)
 
-	srcDir = pctx.VariableConfigMethod("srcDir", Config.SrcDir)
-
 	// A phony rule that is not the built-in Ninja phony rule.  The built-in
 	// phony rule has special behavior that is sometimes not desired.  See the
 	// Ninja docs for more details.
diff --git a/common/env.go b/common/env.go
index 8694c28..478fffc 100644
--- a/common/env.go
+++ b/common/env.go
@@ -15,8 +15,6 @@
 package common
 
 import (
-	"path/filepath"
-
 	"android/soong"
 	"android/soong/env"
 
@@ -43,12 +41,15 @@
 func (c *envSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
 	envDeps := ctx.Config().(Config).EnvDeps()
 
-	envFile := filepath.Join(ctx.Config().(Config).BuildDir(), ".soong.environment")
+	envFile := PathForOutput(ctx, ".soong.environment")
+	if ctx.Failed() {
+		return
+	}
 
-	err := env.WriteEnvFile(envFile, envDeps)
+	err := env.WriteEnvFile(envFile.String(), envDeps)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
 
-	ctx.AddNinjaFileDeps(envFile)
+	ctx.AddNinjaFileDeps(envFile.String())
 }
diff --git a/common/module.go b/common/module.go
index 113768a..36710c5 100644
--- a/common/module.go
+++ b/common/module.go
@@ -32,6 +32,19 @@
 	HostExecutable      = "host_executable"
 )
 
+type ModuleBuildParams struct {
+	Rule      blueprint.Rule
+	Output    WritablePath
+	Outputs   WritablePaths
+	Input     Path
+	Inputs    Paths
+	Implicit  Path
+	Implicits Paths
+	OrderOnly Paths
+	Default   bool
+	Args      map[string]string
+}
+
 type androidBaseContext interface {
 	Arch() Arch
 	HostOrDevice() HostOrDevice
@@ -52,12 +65,16 @@
 	blueprint.ModuleContext
 	androidBaseContext
 
-	ExpandSources(srcFiles, excludes []string) []string
-	Glob(outDir, globPattern string, excludes []string) []string
+	// Similar to Build, but takes Paths instead of []string,
+	// and performs more verification.
+	ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams)
 
-	InstallFile(installPath, srcPath string, deps ...string) string
-	InstallFileName(installPath, name, srcPath string, deps ...string) string
-	CheckbuildFile(srcPath string)
+	ExpandSources(srcFiles, excludes []string) Paths
+	Glob(outDir, globPattern string, excludes []string) Paths
+
+	InstallFile(installPath string, srcPath Path, deps ...Path) Path
+	InstallFileName(installPath, name string, srcPath Path, deps ...Path) Path
+	CheckbuildFile(srcPath Path)
 }
 
 type AndroidModule interface {
@@ -196,8 +213,8 @@
 	archProperties          []*archProperties
 
 	noAddressSanitizer bool
-	installFiles       []string
-	checkbuildFiles    []string
+	installFiles       Paths
+	checkbuildFiles    Paths
 
 	// Used by buildTargetSingleton to create checkbuild and per-directory build targets
 	// Only set on the final variant of each module
@@ -254,9 +271,9 @@
 }
 
 func (a *AndroidModuleBase) computeInstallDeps(
-	ctx blueprint.ModuleContext) []string {
+	ctx blueprint.ModuleContext) Paths {
 
-	result := []string{}
+	result := Paths{}
 	ctx.VisitDepsDepthFirstIf(isFileInstaller,
 		func(m blueprint.Module) {
 			fileInstaller := m.(fileInstaller)
@@ -267,7 +284,7 @@
 	return result
 }
 
-func (a *AndroidModuleBase) filesToInstall() []string {
+func (a *AndroidModuleBase) filesToInstall() Paths {
 	return a.installFiles
 }
 
@@ -280,8 +297,8 @@
 		return
 	}
 
-	allInstalledFiles := []string{}
-	allCheckbuildFiles := []string{}
+	allInstalledFiles := Paths{}
+	allCheckbuildFiles := Paths{}
 	ctx.VisitAllModuleVariants(func(module blueprint.Module) {
 		a := module.(AndroidModule).base()
 		allInstalledFiles = append(allInstalledFiles, a.installFiles...)
@@ -295,7 +312,7 @@
 		ctx.Build(pctx, blueprint.BuildParams{
 			Rule:      blueprint.Phony,
 			Outputs:   []string{name},
-			Implicits: allInstalledFiles,
+			Implicits: allInstalledFiles.Strings(),
 		})
 		deps = append(deps, name)
 		a.installTarget = name
@@ -306,7 +323,7 @@
 		ctx.Build(pctx, blueprint.BuildParams{
 			Rule:      blueprint.Phony,
 			Outputs:   []string{name},
-			Implicits: allCheckbuildFiles,
+			Implicits: allCheckbuildFiles.Strings(),
 			Optional:  true,
 		})
 		deps = append(deps, name)
@@ -371,9 +388,9 @@
 type androidModuleContext struct {
 	blueprint.ModuleContext
 	androidBaseContextImpl
-	installDeps     []string
-	installFiles    []string
-	checkbuildFiles []string
+	installDeps     Paths
+	installFiles    Paths
+	checkbuildFiles Paths
 }
 
 func (a *androidModuleContext) Build(pctx blueprint.PackageContext, params blueprint.BuildParams) {
@@ -381,6 +398,30 @@
 	a.ModuleContext.Build(pctx, params)
 }
 
+func (a *androidModuleContext) ModuleBuild(pctx blueprint.PackageContext, params ModuleBuildParams) {
+	bparams := blueprint.BuildParams{
+		Rule:      params.Rule,
+		Outputs:   params.Outputs.Strings(),
+		Inputs:    params.Inputs.Strings(),
+		Implicits: params.Implicits.Strings(),
+		OrderOnly: params.OrderOnly.Strings(),
+		Args:      params.Args,
+		Optional:  !params.Default,
+	}
+
+	if params.Output != nil {
+		bparams.Outputs = append(bparams.Outputs, params.Output.String())
+	}
+	if params.Input != nil {
+		bparams.Inputs = append(bparams.Inputs, params.Input.String())
+	}
+	if params.Implicit != nil {
+		bparams.Implicits = append(bparams.Implicits, params.Implicit.String())
+	}
+
+	a.ModuleContext.Build(pctx, bparams)
+}
+
 func (a *androidBaseContextImpl) Arch() Arch {
 	return a.arch
 }
@@ -413,31 +454,19 @@
 	return a.config
 }
 
-func (a *androidModuleContext) InstallFileName(installPath, name, srcPath string,
-	deps ...string) string {
+func (a *androidModuleContext) InstallFileName(installPath, name string, srcPath Path,
+	deps ...Path) Path {
 
-	config := a.AConfig()
-	var fullInstallPath string
-	if a.hod.Device() {
-		// TODO: replace unset with a device name once we have device targeting
-		fullInstallPath = filepath.Join(config.DeviceOut(), "system",
-			installPath, name)
-	} else {
-		// TODO
-		if a.ht == Windows {
-			fullInstallPath = filepath.Join(config.BuildDir(), "host", "windows-x86", installPath, name)
-		} else {
-			fullInstallPath = filepath.Join(config.HostOut(), installPath, name)
-		}
-	}
+	fullInstallPath := PathForModuleInstall(a, installPath, name)
 
 	deps = append(deps, a.installDeps...)
 
-	a.ModuleContext.Build(pctx, blueprint.BuildParams{
+	a.ModuleBuild(pctx, ModuleBuildParams{
 		Rule:      Cp,
-		Outputs:   []string{fullInstallPath},
-		Inputs:    []string{srcPath},
-		OrderOnly: deps,
+		Output:    fullInstallPath,
+		Input:     srcPath,
+		OrderOnly: Paths(deps),
+		Default:   true,
 	})
 
 	a.installFiles = append(a.installFiles, fullInstallPath)
@@ -445,16 +474,16 @@
 	return fullInstallPath
 }
 
-func (a *androidModuleContext) InstallFile(installPath, srcPath string, deps ...string) string {
-	return a.InstallFileName(installPath, filepath.Base(srcPath), srcPath, deps...)
+func (a *androidModuleContext) InstallFile(installPath string, srcPath Path, deps ...Path) Path {
+	return a.InstallFileName(installPath, filepath.Base(srcPath.String()), srcPath, deps...)
 }
 
-func (a *androidModuleContext) CheckbuildFile(srcPath string) {
+func (a *androidModuleContext) CheckbuildFile(srcPath Path) {
 	a.checkbuildFiles = append(a.checkbuildFiles, srcPath)
 }
 
 type fileInstaller interface {
-	filesToInstall() []string
+	filesToInstall() Paths
 }
 
 func isFileInstaller(m blueprint.Module) bool {
@@ -476,8 +505,8 @@
 	return -1
 }
 
-func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) []string {
-	prefix := ModuleSrcDir(ctx)
+func (ctx *androidModuleContext) ExpandSources(srcFiles, excludes []string) Paths {
+	prefix := PathForModuleSrc(ctx).String()
 	for i, e := range excludes {
 		j := findStringInSlice(e, srcFiles)
 		if j != -1 {
@@ -487,32 +516,24 @@
 		excludes[i] = filepath.Join(prefix, e)
 	}
 
-	for i, srcFile := range srcFiles {
-		srcFiles[i] = filepath.Join(prefix, srcFile)
-	}
-
-	if !hasGlob(srcFiles) {
-		return srcFiles
-	}
-
-	globbedSrcFiles := make([]string, 0, len(srcFiles))
+	globbedSrcFiles := make(Paths, 0, len(srcFiles))
 	for _, s := range srcFiles {
 		if glob.IsGlob(s) {
-			globbedSrcFiles = append(globbedSrcFiles, ctx.Glob("src_glob", s, excludes)...)
+			globbedSrcFiles = append(globbedSrcFiles, ctx.Glob("src_glob", filepath.Join(prefix, s), excludes)...)
 		} else {
-			globbedSrcFiles = append(globbedSrcFiles, s)
+			globbedSrcFiles = append(globbedSrcFiles, PathForModuleSrc(ctx, s))
 		}
 	}
 
 	return globbedSrcFiles
 }
 
-func (ctx *androidModuleContext) Glob(outDir, globPattern string, excludes []string) []string {
-	ret, err := Glob(ctx, filepath.Join(ModuleOutDir(ctx), outDir), globPattern, excludes)
+func (ctx *androidModuleContext) Glob(outDir, globPattern string, excludes []string) Paths {
+	ret, err := Glob(ctx, PathForModuleOut(ctx, outDir).String(), globPattern, excludes)
 	if err != nil {
 		ctx.ModuleErrorf("glob: %s", err.Error())
 	}
-	return ret
+	return pathsForModuleSrcFromFullPath(ctx, ret)
 }
 
 func init() {
diff --git a/common/package_ctx.go b/common/package_ctx.go
new file mode 100644
index 0000000..cd18b65
--- /dev/null
+++ b/common/package_ctx.go
@@ -0,0 +1,127 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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
+//
+//     http://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 common
+
+import (
+	"fmt"
+
+	"github.com/google/blueprint"
+)
+
+// AndroidPackageContext is a wrapper for blueprint.PackageContext that adds
+// some android-specific helper functions.
+type AndroidPackageContext struct {
+	blueprint.PackageContext
+}
+
+func NewPackageContext(pkgPath string) AndroidPackageContext {
+	return AndroidPackageContext{blueprint.NewPackageContext(pkgPath)}
+}
+
+// configErrorWrapper can be used with Path functions when a Context is not
+// available. A Config can be provided, and errors are stored as a list for
+// later retrieval.
+//
+// The most common use here will be with VariableFunc, where only a config is
+// provided, and an error should be returned.
+type configErrorWrapper struct {
+	config Config
+	errors []error
+}
+
+var _ PathContext = &configErrorWrapper{}
+var _ errorfContext = &configErrorWrapper{}
+
+func (e *configErrorWrapper) Config() interface{} {
+	return e.config
+}
+func (e *configErrorWrapper) Errorf(format string, args ...interface{}) {
+	e.errors = append(e.errors, fmt.Errorf(format, args...))
+}
+
+// SourcePathVariable returns a Variable whose value is the source directory
+// appended with the supplied path. It may only be called during a Go package's
+// initialization - either from the init() function or as part of a
+// package-scoped variable's initialization.
+func (p AndroidPackageContext) SourcePathVariable(name, path string) blueprint.Variable {
+	return p.VariableFunc(name, func(config interface{}) (string, error) {
+		ctx := &configErrorWrapper{config.(Config), []error{}}
+		p := safePathForSource(ctx, path)
+		if len(ctx.errors) > 0 {
+			return "", ctx.errors[0]
+		}
+		return p.String(), nil
+	})
+}
+
+// HostBinVariable returns a Variable whose value is the path to a host tool
+// in the bin directory for host targets. It may only be called during a Go
+// package's initialization - either from the init() function or as part of a
+// package-scoped variable's initialization.
+func (p AndroidPackageContext) HostBinToolVariable(name, path string) blueprint.Variable {
+	return p.VariableFunc(name, func(config interface{}) (string, error) {
+		ctx := &configErrorWrapper{config.(Config), []error{}}
+		p := PathForOutput(ctx, "host", ctx.config.PrebuiltOS(), "bin", path)
+		if len(ctx.errors) > 0 {
+			return "", ctx.errors[0]
+		}
+		return p.String(), nil
+	})
+}
+
+// HostJavaToolVariable returns a Variable whose value is the path to a host
+// tool in the frameworks directory for host targets. It may only be called
+// during a Go package's initialization - either from the init() function or as
+// part of a package-scoped variable's initialization.
+func (p AndroidPackageContext) HostJavaToolVariable(name, path string) blueprint.Variable {
+	return p.VariableFunc(name, func(config interface{}) (string, error) {
+		ctx := &configErrorWrapper{config.(Config), []error{}}
+		p := PathForOutput(ctx, "host", ctx.config.PrebuiltOS(), "framework", path)
+		if len(ctx.errors) > 0 {
+			return "", ctx.errors[0]
+		}
+		return p.String(), nil
+	})
+}
+
+// IntermediatesPathVariable returns a Variable whose value is the intermediate
+// directory appended with the supplied path. It may only be called during a Go
+// package's initialization - either from the init() function or as part of a
+// package-scoped variable's initialization.
+func (p AndroidPackageContext) IntermediatesPathVariable(name, path string) blueprint.Variable {
+	return p.VariableFunc(name, func(config interface{}) (string, error) {
+		ctx := &configErrorWrapper{config.(Config), []error{}}
+		p := PathForIntermediates(ctx, path)
+		if len(ctx.errors) > 0 {
+			return "", ctx.errors[0]
+		}
+		return p.String(), nil
+	})
+}
+
+// PrefixedPathsForSourceVariable returns a Variable whose value is the
+// list of source paths prefixed with the supplied prefix. It may only be
+// called during a Go package's initialization - either from the init()
+// function or as part of a package-scoped variable's initialization.
+func (p AndroidPackageContext) PrefixedPathsForSourceVariable(name, prefix string, paths []string) blueprint.Variable {
+	return p.VariableFunc(name, func(config interface{}) (string, error) {
+		ctx := &configErrorWrapper{config.(Config), []error{}}
+		paths := PathsForSource(ctx, paths)
+		if len(ctx.errors) > 0 {
+			return "", ctx.errors[0]
+		}
+		return JoinWithPrefix(paths.Strings(), prefix), nil
+	})
+}
diff --git a/common/paths.go b/common/paths.go
index d92dcf9..8a085ea 100644
--- a/common/paths.go
+++ b/common/paths.go
@@ -18,103 +18,571 @@
 	"fmt"
 	"os"
 	"path/filepath"
+	"reflect"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/pathtools"
 )
 
-// ModuleOutDir returns the path to the module-specific output directory.
-func ModuleOutDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ctx.AConfig().IntermediatesDir(),
-		ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir())
+// PathContext is the subset of a (Module|Singleton)Context required by the
+// Path methods.
+type PathContext interface {
+	Config() interface{}
 }
 
-// ModuleSrcDir returns the path of the directory that all source file paths are
-// specified relative to.
-func ModuleSrcDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ctx.AConfig().SrcDir(), ctx.ModuleDir())
+var _ PathContext = blueprint.SingletonContext(nil)
+var _ PathContext = blueprint.ModuleContext(nil)
+
+// errorfContext is the interface containing the Errorf method matching the
+// Errorf method in blueprint.SingletonContext.
+type errorfContext interface {
+	Errorf(format string, args ...interface{})
 }
 
-// ModuleBinDir returns the path to the module- and architecture-specific binary
-// output directory.
-func ModuleBinDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "bin")
+var _ errorfContext = blueprint.SingletonContext(nil)
+
+// moduleErrorf is the interface containing the ModuleErrorf method matching
+// the ModuleErrorf method in blueprint.ModuleContext.
+type moduleErrorf interface {
+	ModuleErrorf(format string, args ...interface{})
 }
 
-// ModuleLibDir returns the path to the module- and architecture-specific
-// library output directory.
-func ModuleLibDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "lib")
+var _ moduleErrorf = blueprint.ModuleContext(nil)
+
+// pathConfig returns the android Config interface associated to the context.
+// Panics if the context isn't affiliated with an android build.
+func pathConfig(ctx PathContext) Config {
+	if ret, ok := ctx.Config().(Config); ok {
+		return ret
+	}
+	panic("Paths may only be used on Soong builds")
 }
 
-// ModuleGenDir returns the module directory for generated files
-// path.
-func ModuleGenDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "gen")
-}
-
-// ModuleObjDir returns the module- and architecture-specific object directory
-// path.
-func ModuleObjDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "obj")
-}
-
-// ModuleGoPackageDir returns the module-specific package root directory path.
-// This directory is where the final package .a files are output and where
-// dependent modules search for this package via -I arguments.
-func ModuleGoPackageDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "pkg")
-}
-
-// ModuleIncludeDir returns the module-specific public include directory path.
-func ModuleIncludeDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "include")
-}
-
-// ModuleProtoDir returns the module-specific public proto include directory path.
-func ModuleProtoDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "proto")
-}
-
-func ModuleJSCompiledDir(ctx AndroidModuleContext) string {
-	return filepath.Join(ModuleOutDir(ctx), "js")
-}
-
-// CheckModuleSrcDirsExist logs an error on a property if any of the directories relative to the
-// Blueprints file don't exist.
-func CheckModuleSrcDirsExist(ctx AndroidModuleContext, dirs []string, prop string) {
-	for _, dir := range dirs {
-		fullDir := filepath.Join(ModuleSrcDir(ctx), dir)
-		if _, err := os.Stat(fullDir); err != nil {
-			if os.IsNotExist(err) {
-				ctx.PropertyErrorf(prop, "module source directory %q does not exist", dir)
-			} else {
-				ctx.PropertyErrorf(prop, "%s", err.Error())
-			}
-		}
+// reportPathError will register an error with the attached context. It
+// attempts ctx.ModuleErrorf for a better error message first, then falls
+// back to ctx.Errorf.
+func reportPathError(ctx PathContext, format string, args ...interface{}) {
+	if mctx, ok := ctx.(moduleErrorf); ok {
+		mctx.ModuleErrorf(format, args...)
+	} else if ectx, ok := ctx.(errorfContext); ok {
+		ectx.Errorf(format, args...)
+	} else {
+		panic(fmt.Sprintf(format, args...))
 	}
 }
 
-// CheckModuleSrcDirsExist logs an error on a property if any of the directories relative to the
-// top of the source tree don't exist.
-func CheckSrcDirsExist(ctx AndroidModuleContext, dirs []string, prop string) {
-	for _, dir := range dirs {
-		fullDir := filepath.Join(ctx.AConfig().SrcDir(), dir)
-		if _, err := os.Stat(fullDir); err != nil {
-			if os.IsNotExist(err) {
-				ctx.PropertyErrorf(prop, "top-level source directory %q does not exist", dir)
-			} else {
-				ctx.PropertyErrorf(prop, "%s", err.Error())
-			}
-		}
+type Path interface {
+	// Returns the path in string form
+	String() string
+
+	// Returns the current file extension of the path
+	Ext() string
+}
+
+// WritablePath is a type of path that can be used as an output for build rules.
+type WritablePath interface {
+	Path
+
+	writablePath()
+}
+
+type genPathProvider interface {
+	genPathWithExt(ctx AndroidModuleContext, ext string) ModuleGenPath
+}
+type objPathProvider interface {
+	objPathWithExt(ctx AndroidModuleContext, subdir, ext string) ModuleObjPath
+}
+type resPathProvider interface {
+	resPathWithName(ctx AndroidModuleContext, name string) ModuleResPath
+}
+
+// GenPathWithExt derives a new file path in ctx's generated sources directory
+// from the current path, but with the new extension.
+func GenPathWithExt(ctx AndroidModuleContext, p Path, ext string) ModuleGenPath {
+	if path, ok := p.(genPathProvider); ok {
+		return path.genPathWithExt(ctx, ext)
+	}
+	reportPathError(ctx, "Tried to create generated file from unsupported path: %s(%s)", reflect.TypeOf(p).Name(), p)
+	return PathForModuleGen(ctx)
+}
+
+// ObjPathWithExt derives a new file path in ctx's object directory from the
+// current path, but with the new extension.
+func ObjPathWithExt(ctx AndroidModuleContext, p Path, subdir, ext string) ModuleObjPath {
+	if path, ok := p.(objPathProvider); ok {
+		return path.objPathWithExt(ctx, subdir, ext)
+	}
+	reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p)
+	return PathForModuleObj(ctx)
+}
+
+// ResPathWithName derives a new path in ctx's output resource directory, using
+// the current path to create the directory name, and the `name` argument for
+// the filename.
+func ResPathWithName(ctx AndroidModuleContext, p Path, name string) ModuleResPath {
+	if path, ok := p.(resPathProvider); ok {
+		return path.resPathWithName(ctx, name)
+	}
+	reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p)
+	return PathForModuleRes(ctx)
+}
+
+// OptionalPath is a container that may or may not contain a valid Path.
+type OptionalPath struct {
+	valid bool
+	path  Path
+}
+
+// OptionalPathForPath returns an OptionalPath containing the path.
+func OptionalPathForPath(path Path) OptionalPath {
+	if path == nil {
+		return OptionalPath{}
+	}
+	return OptionalPath{valid: true, path: path}
+}
+
+// Valid returns whether there is a valid path
+func (p OptionalPath) Valid() bool {
+	return p.valid
+}
+
+// Path returns the Path embedded in this OptionalPath. You must be sure that
+// there is a valid path, since this method will panic if there is not.
+func (p OptionalPath) Path() Path {
+	if !p.valid {
+		panic("Requesting an invalid path")
+	}
+	return p.path
+}
+
+// String returns the string version of the Path, or "" if it isn't valid.
+func (p OptionalPath) String() string {
+	if p.valid {
+		return p.path.String()
+	} else {
+		return ""
 	}
 }
 
-// Returns a path relative to the top level source directory.  Panics if path is not inside the
-// top level source directory.
-func SrcDirRelPath(ctx AndroidModuleContext, path string) string {
-	srcDir := ctx.AConfig().SrcDir()
-	relPath, err := filepath.Rel(srcDir, path)
+// Paths is a slice of Path objects, with helpers to operate on the collection.
+type Paths []Path
+
+// PathsForSource returns Paths rooted from SrcDir
+func PathsForSource(ctx PathContext, paths []string) Paths {
+	ret := make(Paths, len(paths))
+	for i, path := range paths {
+		ret[i] = PathForSource(ctx, path)
+	}
+	return ret
+}
+
+// PathsForModuleSrc returns Paths rooted from the module's local source
+// directory
+func PathsForModuleSrc(ctx AndroidModuleContext, paths []string) Paths {
+	ret := make(Paths, len(paths))
+	for i, path := range paths {
+		ret[i] = PathForModuleSrc(ctx, path)
+	}
+	return ret
+}
+
+// pathsForModuleSrcFromFullPath returns Paths rooted from the module's local
+// source directory, but strip the local source directory from the beginning of
+// each string.
+func pathsForModuleSrcFromFullPath(ctx AndroidModuleContext, paths []string) Paths {
+	prefix := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir()) + "/"
+	ret := make(Paths, 0, len(paths))
+	for _, p := range paths {
+		path := filepath.Clean(p)
+		if !strings.HasPrefix(path, prefix) {
+			reportPathError(ctx, "Path '%s' is not in module source directory '%s'", p, prefix)
+			continue
+		}
+		ret = append(ret, PathForModuleSrc(ctx, path[len(prefix):]))
+	}
+	return ret
+}
+
+// PathsWithOptionalDefaultForModuleSrc returns Paths rooted from the module's
+// local source directory. If none are provided, use the default if it exists.
+func PathsWithOptionalDefaultForModuleSrc(ctx AndroidModuleContext, input []string, def string) Paths {
+	if len(input) > 0 {
+		return PathsForModuleSrc(ctx, input)
+	}
+	// Use Glob so that if the default doesn't exist, a dependency is added so that when it
+	// is created, we're run again.
+	path := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir(), def)
+	return ctx.Glob("default", path, []string{})
+}
+
+// Strings returns the Paths in string form
+func (p Paths) Strings() []string {
+	if p == nil {
+		return nil
+	}
+	ret := make([]string, len(p))
+	for i, path := range p {
+		ret[i] = path.String()
+	}
+	return ret
+}
+
+// WritablePaths is a slice of WritablePaths, used for multiple outputs.
+type WritablePaths []WritablePath
+
+// Strings returns the string forms of the writable paths.
+func (p WritablePaths) Strings() []string {
+	if p == nil {
+		return nil
+	}
+	ret := make([]string, len(p))
+	for i, path := range p {
+		ret[i] = path.String()
+	}
+	return ret
+}
+
+type basePath struct {
+	path   string
+	config Config
+}
+
+func (p basePath) Ext() string {
+	return filepath.Ext(p.path)
+}
+
+// SourcePath is a Path representing a file path rooted from SrcDir
+type SourcePath struct {
+	basePath
+}
+
+var _ Path = SourcePath{}
+
+// safePathForSource is for paths that we expect are safe -- only for use by go
+// code that is embedding ninja variables in paths
+func safePathForSource(ctx PathContext, path string) SourcePath {
+	p := validateSafePath(ctx, path)
+	ret := SourcePath{basePath{p, pathConfig(ctx)}}
+
+	abs, err := filepath.Abs(ret.String())
 	if err != nil {
-		panic(fmt.Errorf("%q is not inside %q: %s", path, srcDir, err.Error()))
+		reportPathError(ctx, "%s", err.Error())
+		return ret
+	}
+	buildroot, err := filepath.Abs(pathConfig(ctx).buildDir)
+	if err != nil {
+		reportPathError(ctx, "%s", err.Error())
+		return ret
+	}
+	if strings.HasPrefix(abs, buildroot) {
+		reportPathError(ctx, "source path %s is in output", abs)
+		return ret
 	}
 
-	return relPath
+	return ret
+}
+
+// PathForSource returns a SourcePath for the provided paths... (which are
+// joined together with filepath.Join). This also validates that the path
+// doesn't escape the source dir, or is contained in the build dir. On error, it
+// will return a usable, but invalid SourcePath, and report a ModuleError.
+func PathForSource(ctx PathContext, paths ...string) SourcePath {
+	p := validatePath(ctx, paths...)
+	ret := SourcePath{basePath{p, pathConfig(ctx)}}
+
+	abs, err := filepath.Abs(ret.String())
+	if err != nil {
+		reportPathError(ctx, "%s", err.Error())
+		return ret
+	}
+	buildroot, err := filepath.Abs(pathConfig(ctx).buildDir)
+	if err != nil {
+		reportPathError(ctx, "%s", err.Error())
+		return ret
+	}
+	if strings.HasPrefix(abs, buildroot) {
+		reportPathError(ctx, "source path %s is in output", abs)
+		return ret
+	}
+
+	if _, err = os.Stat(ret.String()); err != nil {
+		if os.IsNotExist(err) {
+			reportPathError(ctx, "source path %s does not exist", ret)
+		} else {
+			reportPathError(ctx, "%s: %s", ret, err.Error())
+		}
+	}
+	return ret
+}
+
+// OptionalPathForSource returns an OptionalPath with the SourcePath if the
+// path exists, or an empty OptionalPath if it doesn't exist. Dependencies are added
+// so that the ninja file will be regenerated if the state of the path changes.
+func OptionalPathForSource(ctx blueprint.SingletonContext, intermediates string, paths ...string) OptionalPath {
+	p := validatePath(ctx, paths...)
+	path := SourcePath{basePath{p, pathConfig(ctx)}}
+
+	abs, err := filepath.Abs(path.String())
+	if err != nil {
+		reportPathError(ctx, "%s", err.Error())
+		return OptionalPath{}
+	}
+	buildroot, err := filepath.Abs(pathConfig(ctx).buildDir)
+	if err != nil {
+		reportPathError(ctx, "%s", err.Error())
+		return OptionalPath{}
+	}
+	if strings.HasPrefix(abs, buildroot) {
+		reportPathError(ctx, "source path %s is in output", abs)
+		return OptionalPath{}
+	}
+
+	// Use glob to produce proper dependencies, even though we only want
+	// a single file.
+	files, err := Glob(ctx, PathForIntermediates(ctx, intermediates).String(), path.String(), nil)
+	if err != nil {
+		reportPathError(ctx, "glob: %s", err.Error())
+		return OptionalPath{}
+	}
+
+	if len(files) == 0 {
+		return OptionalPath{}
+	}
+	return OptionalPathForPath(path)
+}
+
+func (p SourcePath) String() string {
+	return filepath.Join(p.config.srcDir, p.path)
+}
+
+// Join creates a new SourcePath with paths... joined with the current path. The
+// provided paths... may not use '..' to escape from the current path.
+func (p SourcePath) Join(ctx PathContext, paths ...string) SourcePath {
+	path := validatePath(ctx, paths...)
+	return PathForSource(ctx, p.path, path)
+}
+
+// OverlayPath returns the overlay for `path' if it exists. This assumes that the
+// SourcePath is the path to a resource overlay directory.
+func (p SourcePath) OverlayPath(ctx AndroidModuleContext, path Path) OptionalPath {
+	var relDir string
+	if moduleSrcPath, ok := path.(ModuleSrcPath); ok {
+		relDir = moduleSrcPath.sourcePath.path
+	} else if srcPath, ok := path.(SourcePath); ok {
+		relDir = srcPath.path
+	} else {
+		reportPathError(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
+		return OptionalPath{}
+	}
+	dir := filepath.Join(p.config.srcDir, p.path, relDir)
+	// Use Glob so that we are run again if the directory is added.
+	paths, err := Glob(ctx, PathForModuleOut(ctx, "overlay").String(), dir, []string{})
+	if err != nil {
+		reportPathError(ctx, "glob: %s", err.Error())
+		return OptionalPath{}
+	}
+	if len(paths) == 0 {
+		return OptionalPath{}
+	}
+	relPath, err := filepath.Rel(p.config.srcDir, paths[0])
+	if err != nil {
+		reportPathError(ctx, "%s", err.Error())
+		return OptionalPath{}
+	}
+	return OptionalPathForPath(PathForSource(ctx, relPath))
+}
+
+// OutputPath is a Path representing a file path rooted from the build directory
+type OutputPath struct {
+	basePath
+}
+
+var _ Path = OutputPath{}
+
+// PathForOutput returns an OutputPath for the provided paths... (which are
+// joined together with filepath.Join). This also validates that the path
+// does not escape the build dir. On error, it will return a usable, but invalid
+// OutputPath, and report a ModuleError.
+func PathForOutput(ctx PathContext, paths ...string) OutputPath {
+	path := validatePath(ctx, paths...)
+	return OutputPath{basePath{path, pathConfig(ctx)}}
+}
+
+func (p OutputPath) writablePath() {}
+
+func (p OutputPath) String() string {
+	return filepath.Join(p.config.buildDir, p.path)
+}
+
+// Join creates a new OutputPath with paths... joined with the current path. The
+// provided paths... may not use '..' to escape from the current path.
+func (p OutputPath) Join(ctx PathContext, paths ...string) OutputPath {
+	path := validatePath(ctx, paths...)
+	return PathForOutput(ctx, p.path, path)
+}
+
+// PathForIntermediates returns an OutputPath representing the top-level
+// intermediates directory.
+func PathForIntermediates(ctx PathContext, paths ...string) OutputPath {
+	path := validatePath(ctx, paths...)
+	return PathForOutput(ctx, ".intermediates", path)
+}
+
+// ModuleSrcPath is a Path representing a file rooted from a module's local source dir
+type ModuleSrcPath struct {
+	basePath
+	sourcePath SourcePath
+	moduleDir  string
+}
+
+var _ Path = ModuleSrcPath{}
+var _ genPathProvider = ModuleSrcPath{}
+var _ objPathProvider = ModuleSrcPath{}
+var _ resPathProvider = ModuleSrcPath{}
+
+// PathForModuleSrc returns a ModuleSrcPath representing the paths... under the
+// module's local source directory.
+func PathForModuleSrc(ctx AndroidModuleContext, paths ...string) ModuleSrcPath {
+	path := validatePath(ctx, paths...)
+	return ModuleSrcPath{basePath{path, ctx.AConfig()}, PathForSource(ctx, ctx.ModuleDir(), path), ctx.ModuleDir()}
+}
+
+// OptionalPathForModuleSrc returns an OptionalPath. The OptionalPath contains a
+// valid path if p is non-nil.
+func OptionalPathForModuleSrc(ctx AndroidModuleContext, p *string) OptionalPath {
+	if p == nil {
+		return OptionalPath{}
+	}
+	return OptionalPathForPath(PathForModuleSrc(ctx, *p))
+}
+
+func (p ModuleSrcPath) String() string {
+	return p.sourcePath.String()
+}
+
+func (p ModuleSrcPath) genPathWithExt(ctx AndroidModuleContext, ext string) ModuleGenPath {
+	return PathForModuleGen(ctx, p.moduleDir, pathtools.ReplaceExtension(p.path, ext))
+}
+
+func (p ModuleSrcPath) objPathWithExt(ctx AndroidModuleContext, subdir, ext string) ModuleObjPath {
+	return PathForModuleObj(ctx, subdir, p.moduleDir, pathtools.ReplaceExtension(p.path, ext))
+}
+
+func (p ModuleSrcPath) resPathWithName(ctx AndroidModuleContext, name string) ModuleResPath {
+	// TODO: Use full directory if the new ctx is not the current ctx?
+	return PathForModuleRes(ctx, p.path, name)
+}
+
+// ModuleOutPath is a Path representing a module's output directory.
+type ModuleOutPath struct {
+	OutputPath
+}
+
+var _ Path = ModuleOutPath{}
+
+// PathForModuleOut returns a Path representing the paths... under the module's
+// output directory.
+func PathForModuleOut(ctx AndroidModuleContext, paths ...string) ModuleOutPath {
+	p := validatePath(ctx, paths...)
+	return ModuleOutPath{PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir(), p)}
+}
+
+// ModuleGenPath is a Path representing the 'gen' directory in a module's output
+// directory. Mainly used for generated sources.
+type ModuleGenPath struct {
+	ModuleOutPath
+	path string
+}
+
+var _ Path = ModuleGenPath{}
+var _ genPathProvider = ModuleGenPath{}
+var _ objPathProvider = ModuleGenPath{}
+
+// PathForModuleGen returns a Path representing the paths... under the module's
+// `gen' directory.
+func PathForModuleGen(ctx AndroidModuleContext, paths ...string) ModuleGenPath {
+	p := validatePath(ctx, paths...)
+	return ModuleGenPath{
+		PathForModuleOut(ctx, "gen", p),
+		p,
+	}
+}
+
+func (p ModuleGenPath) genPathWithExt(ctx AndroidModuleContext, ext string) ModuleGenPath {
+	// TODO: make a different path for local vs remote generated files?
+	return PathForModuleGen(ctx, pathtools.ReplaceExtension(p.path, ext))
+}
+
+func (p ModuleGenPath) objPathWithExt(ctx AndroidModuleContext, subdir, ext string) ModuleObjPath {
+	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
+}
+
+// ModuleObjPath is a Path representing the 'obj' directory in a module's output
+// directory. Used for compiled objects.
+type ModuleObjPath struct {
+	ModuleOutPath
+}
+
+var _ Path = ModuleObjPath{}
+
+// PathForModuleObj returns a Path representing the paths... under the module's
+// 'obj' directory.
+func PathForModuleObj(ctx AndroidModuleContext, paths ...string) ModuleObjPath {
+	p := validatePath(ctx, paths...)
+	return ModuleObjPath{PathForModuleOut(ctx, "obj", p)}
+}
+
+// ModuleResPath is a a Path representing the 'res' directory in a module's
+// output directory.
+type ModuleResPath struct {
+	ModuleOutPath
+}
+
+var _ Path = ModuleResPath{}
+
+// PathForModuleRes returns a Path representing the paths... under the module's
+// 'res' directory.
+func PathForModuleRes(ctx AndroidModuleContext, paths ...string) ModuleResPath {
+	p := validatePath(ctx, paths...)
+	return ModuleResPath{PathForModuleOut(ctx, "res", p)}
+}
+
+// PathForModuleInstall returns a Path representing the install path for the
+// module appended with paths...
+func PathForModuleInstall(ctx AndroidModuleContext, paths ...string) OutputPath {
+	var outPaths []string
+	if ctx.Device() {
+		outPaths = []string{"target", "product", ctx.AConfig().DeviceName(), "system"}
+	} else {
+		outPaths = []string{"host", ctx.HostType().String() + "-x86"}
+	}
+	outPaths = append(outPaths, paths...)
+	return PathForOutput(ctx, outPaths...)
+}
+
+// validateSafePath validates a path that we trust (may contain ninja variables).
+// Ensures that it does not attempt to leave the containing directory.
+func validateSafePath(ctx PathContext, paths ...string) string {
+	// TODO: filepath.Join isn't necessarily correct with embedded ninja
+	// variables. '..' may remove the entire ninja variable, even if it
+	// will be expanded to multiple nested directories.
+	p := filepath.Join(paths...)
+	if p == ".." || strings.HasPrefix(p, "../") || strings.HasPrefix(p, "/") {
+		reportPathError(ctx, "Path is outside directory: %s", p)
+		return ""
+	}
+	return p
+}
+
+// validatePath validates that a path does not include ninja variables, and does
+// not attempt to leave the containing directory.
+func validatePath(ctx PathContext, paths ...string) string {
+	for _, path := range paths {
+		if strings.Contains(path, "$") {
+			reportPathError(ctx, "Path contains invalid character($): %s", path)
+			return ""
+		}
+	}
+	return validateSafePath(ctx, paths...)
 }
diff --git a/common/paths_test.go b/common/paths_test.go
new file mode 100644
index 0000000..16ede0d
--- /dev/null
+++ b/common/paths_test.go
@@ -0,0 +1,167 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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
+//
+//     http://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 common
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+type strsTestCase struct {
+	in  []string
+	out string
+	err []error
+}
+
+var commonValidatePathTestCases = []strsTestCase{
+	{
+		in:  []string{""},
+		out: "",
+	},
+	{
+		in:  []string{"a/b"},
+		out: "a/b",
+	},
+	{
+		in:  []string{"a/b", "c"},
+		out: "a/b/c",
+	},
+	{
+		in:  []string{"a/.."},
+		out: ".",
+	},
+	{
+		in:  []string{"."},
+		out: ".",
+	},
+	{
+		in:  []string{".."},
+		out: "",
+		err: []error{errors.New("Path is outside directory: ..")},
+	},
+	{
+		in:  []string{"../a"},
+		out: "",
+		err: []error{errors.New("Path is outside directory: ../a")},
+	},
+	{
+		in:  []string{"b/../../a"},
+		out: "",
+		err: []error{errors.New("Path is outside directory: ../a")},
+	},
+	{
+		in:  []string{"/a"},
+		out: "",
+		err: []error{errors.New("Path is outside directory: /a")},
+	},
+}
+
+var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
+	{
+		in:  []string{"$host/../$a"},
+		out: "$a",
+	},
+}...)
+
+var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
+	{
+		in:  []string{"$host/../$a"},
+		out: "",
+		err: []error{errors.New("Path contains invalid character($): $host/../$a")},
+	},
+	{
+		in:  []string{"$host/.."},
+		out: "",
+		err: []error{errors.New("Path contains invalid character($): $host/..")},
+	},
+}...)
+
+func TestValidateSafePath(t *testing.T) {
+	for _, testCase := range validateSafePathTestCases {
+		ctx := &configErrorWrapper{}
+		out := validateSafePath(ctx, testCase.in...)
+		check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
+	}
+}
+
+func TestValidatePath(t *testing.T) {
+	for _, testCase := range validatePathTestCases {
+		ctx := &configErrorWrapper{}
+		out := validatePath(ctx, testCase.in...)
+		check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
+	}
+}
+
+func TestOptionalPath(t *testing.T) {
+	var path OptionalPath
+	checkInvalidOptionalPath(t, path)
+
+	path = OptionalPathForPath(nil)
+	checkInvalidOptionalPath(t, path)
+}
+
+func checkInvalidOptionalPath(t *testing.T, path OptionalPath) {
+	if path.Valid() {
+		t.Errorf("Uninitialized OptionalPath should not be valid")
+	}
+	if path.String() != "" {
+		t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String())
+	}
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath")
+		}
+	}()
+	path.Path()
+}
+
+func check(t *testing.T, testType, testString string,
+	got interface{}, err []error,
+	expected interface{}, expectedErr []error) {
+
+	printedTestCase := false
+	e := func(s string, expected, got interface{}) {
+		if !printedTestCase {
+			t.Errorf("test case %s: %s", testType, testString)
+			printedTestCase = true
+		}
+		t.Errorf("incorrect %s", s)
+		t.Errorf("  expected: %s", p(expected))
+		t.Errorf("       got: %s", p(got))
+	}
+
+	if !reflect.DeepEqual(expectedErr, err) {
+		e("errors:", expectedErr, err)
+	}
+
+	if !reflect.DeepEqual(expected, got) {
+		e("output:", expected, got)
+	}
+}
+
+func p(in interface{}) string {
+	if v, ok := in.([]interface{}); ok {
+		s := make([]string, len(v))
+		for i := range v {
+			s[i] = fmt.Sprintf("%#v", v[i])
+		}
+		return "[" + strings.Join(s, ", ") + "]"
+	} else {
+		return fmt.Sprintf("%#v", in)
+	}
+}