1// Copyright 2015 Google Inc. All rights reserved. 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// http://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 android 16 17import ( 18 "fmt" 19 "path/filepath" 20 "reflect" 21 "strings" 22 23 "github.com/google/blueprint" 24 "github.com/google/blueprint/pathtools" 25) 26 27// PathContext is the subset of a (Module|Singleton)Context required by the 28// Path methods. 29type PathContext interface { 30 Fs() pathtools.FileSystem 31 Config() interface{} 32 AddNinjaFileDeps(deps ...string) 33} 34 35type PathGlobContext interface { 36 GlobWithDeps(globPattern string, excludes []string) ([]string, error) 37} 38 39var _ PathContext = blueprint.SingletonContext(nil) 40var _ PathContext = blueprint.ModuleContext(nil) 41 42// errorfContext is the interface containing the Errorf method matching the 43// Errorf method in blueprint.SingletonContext. 44type errorfContext interface { 45 Errorf(format string, args ...interface{}) 46} 47 48var _ errorfContext = blueprint.SingletonContext(nil) 49 50// moduleErrorf is the interface containing the ModuleErrorf method matching 51// the ModuleErrorf method in blueprint.ModuleContext. 52type moduleErrorf interface { 53 ModuleErrorf(format string, args ...interface{}) 54} 55 56var _ moduleErrorf = blueprint.ModuleContext(nil) 57 58// pathConfig returns the android Config interface associated to the context. 59// Panics if the context isn't affiliated with an android build. 60func pathConfig(ctx PathContext) Config { 61 if ret, ok := ctx.Config().(Config); ok { 62 return ret 63 } 64 panic("Paths may only be used on Soong builds") 65} 66 67// reportPathError will register an error with the attached context. It 68// attempts ctx.ModuleErrorf for a better error message first, then falls 69// back to ctx.Errorf. 70func reportPathError(ctx PathContext, format string, args ...interface{}) { 71 if mctx, ok := ctx.(moduleErrorf); ok { 72 mctx.ModuleErrorf(format, args...) 73 } else if ectx, ok := ctx.(errorfContext); ok { 74 ectx.Errorf(format, args...) 75 } else { 76 panic(fmt.Sprintf(format, args...)) 77 } 78} 79 80type Path interface { 81 // Returns the path in string form 82 String() string 83 84 // Ext returns the extension of the last element of the path 85 Ext() string 86 87 // Base returns the last element of the path 88 Base() string 89 90 // Rel returns the portion of the path relative to the directory it was created from. For 91 // example, Rel on a PathsForModuleSrc would return the path relative to the module source 92 // directory. 93 Rel() string 94} 95 96// WritablePath is a type of path that can be used as an output for build rules. 97type WritablePath interface { 98 Path 99 100 writablePath() 101} 102 103type genPathProvider interface { 104 genPathWithExt(ctx ModuleContext, subdir, ext string) ModuleGenPath 105} 106type objPathProvider interface { 107 objPathWithExt(ctx ModuleContext, subdir, ext string) ModuleObjPath 108} 109type resPathProvider interface { 110 resPathWithName(ctx ModuleContext, name string) ModuleResPath 111} 112 113// GenPathWithExt derives a new file path in ctx's generated sources directory 114// from the current path, but with the new extension. 115func GenPathWithExt(ctx ModuleContext, subdir string, p Path, ext string) ModuleGenPath { 116 if path, ok := p.(genPathProvider); ok { 117 return path.genPathWithExt(ctx, subdir, ext) 118 } 119 reportPathError(ctx, "Tried to create generated file from unsupported path: %s(%s)", reflect.TypeOf(p).Name(), p) 120 return PathForModuleGen(ctx) 121} 122 123// ObjPathWithExt derives a new file path in ctx's object directory from the 124// current path, but with the new extension. 125func ObjPathWithExt(ctx ModuleContext, subdir string, p Path, ext string) ModuleObjPath { 126 if path, ok := p.(objPathProvider); ok { 127 return path.objPathWithExt(ctx, subdir, ext) 128 } 129 reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p) 130 return PathForModuleObj(ctx) 131} 132 133// ResPathWithName derives a new path in ctx's output resource directory, using 134// the current path to create the directory name, and the `name` argument for 135// the filename. 136func ResPathWithName(ctx ModuleContext, p Path, name string) ModuleResPath { 137 if path, ok := p.(resPathProvider); ok { 138 return path.resPathWithName(ctx, name) 139 } 140 reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p) 141 return PathForModuleRes(ctx) 142} 143 144// OptionalPath is a container that may or may not contain a valid Path. 145type OptionalPath struct { 146 valid bool 147 path Path 148} 149 150// OptionalPathForPath returns an OptionalPath containing the path. 151func OptionalPathForPath(path Path) OptionalPath { 152 if path == nil { 153 return OptionalPath{} 154 } 155 return OptionalPath{valid: true, path: path} 156} 157 158// Valid returns whether there is a valid path 159func (p OptionalPath) Valid() bool { 160 return p.valid 161} 162 163// Path returns the Path embedded in this OptionalPath. You must be sure that 164// there is a valid path, since this method will panic if there is not. 165func (p OptionalPath) Path() Path { 166 if !p.valid { 167 panic("Requesting an invalid path") 168 } 169 return p.path 170} 171 172// String returns the string version of the Path, or "" if it isn't valid. 173func (p OptionalPath) String() string { 174 if p.valid { 175 return p.path.String() 176 } else { 177 return "" 178 } 179} 180 181// Paths is a slice of Path objects, with helpers to operate on the collection. 182type Paths []Path 183 184// PathsForSource returns Paths rooted from SrcDir 185func PathsForSource(ctx PathContext, paths []string) Paths { 186 if pathConfig(ctx).AllowMissingDependencies() { 187 if modCtx, ok := ctx.(ModuleContext); ok { 188 ret := make(Paths, 0, len(paths)) 189 intermediates := filepath.Join(modCtx.ModuleDir(), modCtx.ModuleName(), modCtx.ModuleSubDir(), "missing") 190 for _, path := range paths { 191 p := OptionalPathForSource(ctx, intermediates, path) 192 if p.Valid() { 193 ret = append(ret, p.Path()) 194 } else { 195 modCtx.AddMissingDependencies([]string{path}) 196 } 197 } 198 return ret 199 } 200 } 201 ret := make(Paths, len(paths)) 202 for i, path := range paths { 203 ret[i] = PathForSource(ctx, path) 204 } 205 return ret 206} 207 208// PathsForOptionalSource returns a list of Paths rooted from SrcDir that are 209// found in the tree. If any are not found, they are omitted from the list, 210// and dependencies are added so that we're re-run when they are added. 211func PathsForOptionalSource(ctx PathContext, intermediates string, paths []string) Paths { 212 ret := make(Paths, 0, len(paths)) 213 for _, path := range paths { 214 p := OptionalPathForSource(ctx, intermediates, path) 215 if p.Valid() { 216 ret = append(ret, p.Path()) 217 } 218 } 219 return ret 220} 221 222// PathsForModuleSrc returns Paths rooted from the module's local source 223// directory 224func PathsForModuleSrc(ctx ModuleContext, paths []string) Paths { 225 ret := make(Paths, len(paths)) 226 for i, path := range paths { 227 ret[i] = PathForModuleSrc(ctx, path) 228 } 229 return ret 230} 231 232// pathsForModuleSrcFromFullPath returns Paths rooted from the module's local 233// source directory, but strip the local source directory from the beginning of 234// each string. 235func pathsForModuleSrcFromFullPath(ctx ModuleContext, paths []string) Paths { 236 prefix := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir()) + "/" 237 ret := make(Paths, 0, len(paths)) 238 for _, p := range paths { 239 path := filepath.Clean(p) 240 if !strings.HasPrefix(path, prefix) { 241 reportPathError(ctx, "Path '%s' is not in module source directory '%s'", p, prefix) 242 continue 243 } 244 ret = append(ret, PathForModuleSrc(ctx, path[len(prefix):])) 245 } 246 return ret 247} 248 249// PathsWithOptionalDefaultForModuleSrc returns Paths rooted from the module's 250// local source directory. If none are provided, use the default if it exists. 251func PathsWithOptionalDefaultForModuleSrc(ctx ModuleContext, input []string, def string) Paths { 252 if len(input) > 0 { 253 return PathsForModuleSrc(ctx, input) 254 } 255 // Use Glob so that if the default doesn't exist, a dependency is added so that when it 256 // is created, we're run again. 257 path := filepath.Join(ctx.AConfig().srcDir, ctx.ModuleDir(), def) 258 return ctx.Glob(path, []string{}) 259} 260 261// Strings returns the Paths in string form 262func (p Paths) Strings() []string { 263 if p == nil { 264 return nil 265 } 266 ret := make([]string, len(p)) 267 for i, path := range p { 268 ret[i] = path.String() 269 } 270 return ret 271} 272 273// WritablePaths is a slice of WritablePaths, used for multiple outputs. 274type WritablePaths []WritablePath 275 276// Strings returns the string forms of the writable paths. 277func (p WritablePaths) Strings() []string { 278 if p == nil { 279 return nil 280 } 281 ret := make([]string, len(p)) 282 for i, path := range p { 283 ret[i] = path.String() 284 } 285 return ret 286} 287 288type basePath struct { 289 path string 290 config Config 291 rel string 292} 293 294func (p basePath) Ext() string { 295 return filepath.Ext(p.path) 296} 297 298func (p basePath) Base() string { 299 return filepath.Base(p.path) 300} 301 302func (p basePath) Rel() string { 303 if p.rel != "" { 304 return p.rel 305 } 306 return p.path 307} 308 309// SourcePath is a Path representing a file path rooted from SrcDir 310type SourcePath struct { 311 basePath 312} 313 314var _ Path = SourcePath{} 315 316// safePathForSource is for paths that we expect are safe -- only for use by go 317// code that is embedding ninja variables in paths 318func safePathForSource(ctx PathContext, path string) SourcePath { 319 p := validateSafePath(ctx, path) 320 ret := SourcePath{basePath{p, pathConfig(ctx), ""}} 321 322 abs, err := filepath.Abs(ret.String()) 323 if err != nil { 324 reportPathError(ctx, "%s", err.Error()) 325 return ret 326 } 327 buildroot, err := filepath.Abs(pathConfig(ctx).buildDir) 328 if err != nil { 329 reportPathError(ctx, "%s", err.Error()) 330 return ret 331 } 332 if strings.HasPrefix(abs, buildroot) { 333 reportPathError(ctx, "source path %s is in output", abs) 334 return ret 335 } 336 337 return ret 338} 339 340// PathForSource returns a SourcePath for the provided paths... (which are 341// joined together with filepath.Join). This also validates that the path 342// doesn't escape the source dir, or is contained in the build dir. On error, it 343// will return a usable, but invalid SourcePath, and report a ModuleError. 344func PathForSource(ctx PathContext, paths ...string) SourcePath { 345 p := validatePath(ctx, paths...) 346 ret := SourcePath{basePath{p, pathConfig(ctx), ""}} 347 348 abs, err := filepath.Abs(ret.String()) 349 if err != nil { 350 reportPathError(ctx, "%s", err.Error()) 351 return ret 352 } 353 buildroot, err := filepath.Abs(pathConfig(ctx).buildDir) 354 if err != nil { 355 reportPathError(ctx, "%s", err.Error()) 356 return ret 357 } 358 if strings.HasPrefix(abs, buildroot) { 359 reportPathError(ctx, "source path %s is in output", abs) 360 return ret 361 } 362 363 if exists, _, err := ctx.Fs().Exists(ret.String()); err != nil { 364 reportPathError(ctx, "%s: %s", ret, err.Error()) 365 } else if !exists { 366 reportPathError(ctx, "source path %s does not exist", ret) 367 } 368 return ret 369} 370 371// OptionalPathForSource returns an OptionalPath with the SourcePath if the 372// path exists, or an empty OptionalPath if it doesn't exist. Dependencies are added 373// so that the ninja file will be regenerated if the state of the path changes. 374func OptionalPathForSource(ctx PathContext, intermediates string, paths ...string) OptionalPath { 375 if len(paths) == 0 { 376 // For when someone forgets the 'intermediates' argument 377 panic("Missing path(s)") 378 } 379 380 p := validatePath(ctx, paths...) 381 path := SourcePath{basePath{p, pathConfig(ctx), ""}} 382 383 abs, err := filepath.Abs(path.String()) 384 if err != nil { 385 reportPathError(ctx, "%s", err.Error()) 386 return OptionalPath{} 387 } 388 buildroot, err := filepath.Abs(pathConfig(ctx).buildDir) 389 if err != nil { 390 reportPathError(ctx, "%s", err.Error()) 391 return OptionalPath{} 392 } 393 if strings.HasPrefix(abs, buildroot) { 394 reportPathError(ctx, "source path %s is in output", abs) 395 return OptionalPath{} 396 } 397 398 if pathtools.IsGlob(path.String()) { 399 reportPathError(ctx, "path may not contain a glob: %s", path.String()) 400 return OptionalPath{} 401 } 402 403 if gctx, ok := ctx.(PathGlobContext); ok { 404 // Use glob to produce proper dependencies, even though we only want 405 // a single file. 406 files, err := gctx.GlobWithDeps(path.String(), nil) 407 if err != nil { 408 reportPathError(ctx, "glob: %s", err.Error()) 409 return OptionalPath{} 410 } 411 412 if len(files) == 0 { 413 return OptionalPath{} 414 } 415 } else { 416 // We cannot add build statements in this context, so we fall back to 417 // AddNinjaFileDeps 418 files, dirs, err := pathtools.Glob(path.String(), nil) 419 if err != nil { 420 reportPathError(ctx, "glob: %s", err.Error()) 421 return OptionalPath{} 422 } 423 424 ctx.AddNinjaFileDeps(dirs...) 425 426 if len(files) == 0 { 427 return OptionalPath{} 428 } 429 430 ctx.AddNinjaFileDeps(path.String()) 431 } 432 return OptionalPathForPath(path) 433} 434 435func (p SourcePath) String() string { 436 return filepath.Join(p.config.srcDir, p.path) 437} 438 439// Join creates a new SourcePath with paths... joined with the current path. The 440// provided paths... may not use '..' to escape from the current path. 441func (p SourcePath) Join(ctx PathContext, paths ...string) SourcePath { 442 path := validatePath(ctx, paths...) 443 return PathForSource(ctx, p.path, path) 444} 445 446// OverlayPath returns the overlay for `path' if it exists. This assumes that the 447// SourcePath is the path to a resource overlay directory. 448func (p SourcePath) OverlayPath(ctx ModuleContext, path Path) OptionalPath { 449 var relDir string 450 if moduleSrcPath, ok := path.(ModuleSrcPath); ok { 451 relDir = moduleSrcPath.path 452 } else if srcPath, ok := path.(SourcePath); ok { 453 relDir = srcPath.path 454 } else { 455 reportPathError(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path) 456 return OptionalPath{} 457 } 458 dir := filepath.Join(p.config.srcDir, p.path, relDir) 459 // Use Glob so that we are run again if the directory is added. 460 if pathtools.IsGlob(dir) { 461 reportPathError(ctx, "Path may not contain a glob: %s", dir) 462 } 463 paths, err := ctx.GlobWithDeps(dir, []string{}) 464 if err != nil { 465 reportPathError(ctx, "glob: %s", err.Error()) 466 return OptionalPath{} 467 } 468 if len(paths) == 0 { 469 return OptionalPath{} 470 } 471 relPath, err := filepath.Rel(p.config.srcDir, paths[0]) 472 if err != nil { 473 reportPathError(ctx, "%s", err.Error()) 474 return OptionalPath{} 475 } 476 return OptionalPathForPath(PathForSource(ctx, relPath)) 477} 478 479// OutputPath is a Path representing a file path rooted from the build directory 480type OutputPath struct { 481 basePath 482} 483 484var _ Path = OutputPath{} 485 486// PathForOutput returns an OutputPath for the provided paths... (which are 487// joined together with filepath.Join). This also validates that the path 488// does not escape the build dir. On error, it will return a usable, but invalid 489// OutputPath, and report a ModuleError. 490func PathForOutput(ctx PathContext, paths ...string) OutputPath { 491 path := validatePath(ctx, paths...) 492 return OutputPath{basePath{path, pathConfig(ctx), ""}} 493} 494 495func (p OutputPath) writablePath() {} 496 497func (p OutputPath) String() string { 498 return filepath.Join(p.config.buildDir, p.path) 499} 500 501func (p OutputPath) RelPathString() string { 502 return p.path 503} 504 505// Join creates a new OutputPath with paths... joined with the current path. The 506// provided paths... may not use '..' to escape from the current path. 507func (p OutputPath) Join(ctx PathContext, paths ...string) OutputPath { 508 path := validatePath(ctx, paths...) 509 return PathForOutput(ctx, p.path, path) 510} 511 512// PathForIntermediates returns an OutputPath representing the top-level 513// intermediates directory. 514func PathForIntermediates(ctx PathContext, paths ...string) OutputPath { 515 path := validatePath(ctx, paths...) 516 return PathForOutput(ctx, ".intermediates", path) 517} 518 519// ModuleSrcPath is a Path representing a file rooted from a module's local source dir 520type ModuleSrcPath struct { 521 SourcePath 522} 523 524var _ Path = ModuleSrcPath{} 525var _ genPathProvider = ModuleSrcPath{} 526var _ objPathProvider = ModuleSrcPath{} 527var _ resPathProvider = ModuleSrcPath{} 528 529// PathForModuleSrc returns a ModuleSrcPath representing the paths... under the 530// module's local source directory. 531func PathForModuleSrc(ctx ModuleContext, paths ...string) ModuleSrcPath { 532 p := validatePath(ctx, paths...) 533 path := ModuleSrcPath{PathForSource(ctx, ctx.ModuleDir(), p)} 534 path.basePath.rel = p 535 return path 536} 537 538// OptionalPathForModuleSrc returns an OptionalPath. The OptionalPath contains a 539// valid path if p is non-nil. 540func OptionalPathForModuleSrc(ctx ModuleContext, p *string) OptionalPath { 541 if p == nil { 542 return OptionalPath{} 543 } 544 return OptionalPathForPath(PathForModuleSrc(ctx, *p)) 545} 546 547func (p ModuleSrcPath) genPathWithExt(ctx ModuleContext, subdir, ext string) ModuleGenPath { 548 return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) 549} 550 551func (p ModuleSrcPath) objPathWithExt(ctx ModuleContext, subdir, ext string) ModuleObjPath { 552 return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) 553} 554 555func (p ModuleSrcPath) resPathWithName(ctx ModuleContext, name string) ModuleResPath { 556 // TODO: Use full directory if the new ctx is not the current ctx? 557 return PathForModuleRes(ctx, p.path, name) 558} 559 560func (p ModuleSrcPath) WithSubDir(ctx ModuleContext, subdir string) ModuleSrcPath { 561 subdir = PathForModuleSrc(ctx, subdir).String() 562 var err error 563 rel, err := filepath.Rel(subdir, p.path) 564 if err != nil { 565 ctx.ModuleErrorf("source file %q is not under path %q", p.path, subdir) 566 return p 567 } 568 p.rel = rel 569 return p 570} 571 572// ModuleOutPath is a Path representing a module's output directory. 573type ModuleOutPath struct { 574 OutputPath 575} 576 577var _ Path = ModuleOutPath{} 578 579// PathForVndkRefDump returns an OptionalPath representing the path of the reference 580// abi dump for the given module. This is not guaranteed to be valid. 581func PathForVndkRefAbiDump(ctx ModuleContext, version, fileName string, vndkOrNdk, isSourceDump bool) OptionalPath { 582 archName := ctx.Arch().ArchType.Name 583 var sourceOrBinaryDir string 584 var vndkOrNdkDir string 585 var ext string 586 if isSourceDump { 587 ext = ".lsdump.gz" 588 sourceOrBinaryDir = "source-based" 589 } else { 590 ext = ".bdump.gz" 591 sourceOrBinaryDir = "binary-based" 592 } 593 if vndkOrNdk { 594 vndkOrNdkDir = "vndk" 595 } else { 596 vndkOrNdkDir = "ndk" 597 } 598 refDumpFileStr := "prebuilts/abi-dumps/" + vndkOrNdkDir + "/" + version + "/" + 599 archName + "/" + sourceOrBinaryDir + "/" + fileName + ext 600 return OptionalPathForSource(ctx, "", refDumpFileStr) 601} 602 603// PathForModuleOut returns a Path representing the paths... under the module's 604// output directory. 605func PathForModuleOut(ctx ModuleContext, paths ...string) ModuleOutPath { 606 p := validatePath(ctx, paths...) 607 return ModuleOutPath{PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir(), p)} 608} 609 610// ModuleGenPath is a Path representing the 'gen' directory in a module's output 611// directory. Mainly used for generated sources. 612type ModuleGenPath struct { 613 ModuleOutPath 614 path string 615} 616 617var _ Path = ModuleGenPath{} 618var _ genPathProvider = ModuleGenPath{} 619var _ objPathProvider = ModuleGenPath{} 620 621// PathForModuleGen returns a Path representing the paths... under the module's 622// `gen' directory. 623func PathForModuleGen(ctx ModuleContext, paths ...string) ModuleGenPath { 624 p := validatePath(ctx, paths...) 625 return ModuleGenPath{ 626 PathForModuleOut(ctx, "gen", p), 627 p, 628 } 629} 630 631func (p ModuleGenPath) genPathWithExt(ctx ModuleContext, subdir, ext string) ModuleGenPath { 632 // TODO: make a different path for local vs remote generated files? 633 return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) 634} 635 636func (p ModuleGenPath) objPathWithExt(ctx ModuleContext, subdir, ext string) ModuleObjPath { 637 return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext)) 638} 639 640// ModuleObjPath is a Path representing the 'obj' directory in a module's output 641// directory. Used for compiled objects. 642type ModuleObjPath struct { 643 ModuleOutPath 644} 645 646var _ Path = ModuleObjPath{} 647 648// PathForModuleObj returns a Path representing the paths... under the module's 649// 'obj' directory. 650func PathForModuleObj(ctx ModuleContext, paths ...string) ModuleObjPath { 651 p := validatePath(ctx, paths...) 652 return ModuleObjPath{PathForModuleOut(ctx, "obj", p)} 653} 654 655// ModuleResPath is a a Path representing the 'res' directory in a module's 656// output directory. 657type ModuleResPath struct { 658 ModuleOutPath 659} 660 661var _ Path = ModuleResPath{} 662 663// PathForModuleRes returns a Path representing the paths... under the module's 664// 'res' directory. 665func PathForModuleRes(ctx ModuleContext, paths ...string) ModuleResPath { 666 p := validatePath(ctx, paths...) 667 return ModuleResPath{PathForModuleOut(ctx, "res", p)} 668} 669 670// PathForModuleInstall returns a Path representing the install path for the 671// module appended with paths... 672func PathForModuleInstall(ctx ModuleContext, paths ...string) OutputPath { 673 var outPaths []string 674 if ctx.Device() { 675 partition := "system" 676 if ctx.Vendor() { 677 partition = ctx.DeviceConfig().VendorPath() 678 } 679 680 if ctx.InstallInSanitizerDir() { 681 partition = "data/asan/" + partition 682 } else if ctx.InstallInData() { 683 partition = "data" 684 } 685 outPaths = []string{"target", "product", ctx.AConfig().DeviceName(), partition} 686 } else { 687 outPaths = []string{"host", ctx.Os().String() + "-x86"} 688 } 689 if ctx.Debug() { 690 outPaths = append([]string{"debug"}, outPaths...) 691 } 692 outPaths = append(outPaths, paths...) 693 return PathForOutput(ctx, outPaths...) 694} 695 696// validateSafePath validates a path that we trust (may contain ninja variables). 697// Ensures that each path component does not attempt to leave its component. 698func validateSafePath(ctx PathContext, paths ...string) string { 699 for _, path := range paths { 700 path := filepath.Clean(path) 701 if path == ".." || strings.HasPrefix(path, "../") || strings.HasPrefix(path, "/") { 702 reportPathError(ctx, "Path is outside directory: %s", path) 703 return "" 704 } 705 } 706 // TODO: filepath.Join isn't necessarily correct with embedded ninja 707 // variables. '..' may remove the entire ninja variable, even if it 708 // will be expanded to multiple nested directories. 709 return filepath.Join(paths...) 710} 711 712// validatePath validates that a path does not include ninja variables, and that 713// each path component does not attempt to leave its component. Returns a joined 714// version of each path component. 715func validatePath(ctx PathContext, paths ...string) string { 716 for _, path := range paths { 717 if strings.Contains(path, "$") { 718 reportPathError(ctx, "Path contains invalid character($): %s", path) 719 return "" 720 } 721 } 722 return validateSafePath(ctx, paths...) 723} 724