1// Copyright 2018 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 build 16 17import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "runtime" 24 "strings" 25 26 "github.com/google/blueprint/microfactory" 27 28 "android/soong/ui/build/paths" 29 "android/soong/ui/metrics" 30) 31 32func parsePathDir(dir string) []string { 33 f, err := os.Open(dir) 34 if err != nil { 35 return nil 36 } 37 defer f.Close() 38 39 if s, err := f.Stat(); err != nil || !s.IsDir() { 40 return nil 41 } 42 43 infos, err := f.Readdir(-1) 44 if err != nil { 45 return nil 46 } 47 48 ret := make([]string, 0, len(infos)) 49 for _, info := range infos { 50 if m := info.Mode(); !m.IsDir() && m&0111 != 0 { 51 ret = append(ret, info.Name()) 52 } 53 } 54 return ret 55} 56 57// A "lite" version of SetupPath used for dumpvars, or other places that need 58// minimal overhead (but at the expense of logging). If tmpDir is empty, the 59// default TMPDIR is used from config. 60func SetupLitePath(ctx Context, config Config, tmpDir string) { 61 if config.pathReplaced { 62 return 63 } 64 65 ctx.BeginTrace(metrics.RunSetupTool, "litepath") 66 defer ctx.EndTrace() 67 68 origPath, _ := config.Environment().Get("PATH") 69 70 if tmpDir == "" { 71 tmpDir, _ = config.Environment().Get("TMPDIR") 72 } 73 myPath := filepath.Join(tmpDir, "path") 74 ensureEmptyDirectoriesExist(ctx, myPath) 75 76 os.Setenv("PATH", origPath) 77 for name, pathConfig := range paths.Configuration { 78 if !pathConfig.Symlink { 79 continue 80 } 81 82 origExec, err := exec.LookPath(name) 83 if err != nil { 84 continue 85 } 86 origExec, err = filepath.Abs(origExec) 87 if err != nil { 88 continue 89 } 90 91 err = os.Symlink(origExec, filepath.Join(myPath, name)) 92 if err != nil { 93 ctx.Fatalln("Failed to create symlink:", err) 94 } 95 } 96 97 myPath, _ = filepath.Abs(myPath) 98 99 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86") 100 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath 101 102 config.Environment().Set("PATH", myPath) 103 config.pathReplaced = true 104} 105 106func SetupPath(ctx Context, config Config) { 107 if config.pathReplaced { 108 return 109 } 110 111 ctx.BeginTrace(metrics.RunSetupTool, "path") 112 defer ctx.EndTrace() 113 114 origPath, _ := config.Environment().Get("PATH") 115 myPath := filepath.Join(config.OutDir(), ".path") 116 interposer := myPath + "_interposer" 117 118 var cfg microfactory.Config 119 cfg.Map("android/soong", "build/soong") 120 cfg.TrimPath, _ = filepath.Abs(".") 121 if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil { 122 ctx.Fatalln("Failed to build path interposer:", err) 123 } 124 125 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil { 126 ctx.Fatalln("Failed to write original path:", err) 127 } 128 129 entries, err := paths.LogListener(ctx.Context, interposer+"_log") 130 if err != nil { 131 ctx.Fatalln("Failed to listen for path logs:", err) 132 } 133 134 go func() { 135 for log := range entries { 136 curPid := os.Getpid() 137 for i, proc := range log.Parents { 138 if proc.Pid == curPid { 139 log.Parents = log.Parents[i:] 140 break 141 } 142 } 143 procPrints := []string{ 144 "See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.", 145 } 146 if len(log.Parents) > 0 { 147 procPrints = append(procPrints, "Process tree:") 148 for i, proc := range log.Parents { 149 procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command)) 150 } 151 } 152 153 config := paths.GetConfig(log.Basename) 154 if config.Error { 155 ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args) 156 for _, line := range procPrints { 157 ctx.Println(line) 158 } 159 } else { 160 ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args) 161 for _, line := range procPrints { 162 ctx.Verboseln(line) 163 } 164 } 165 } 166 }() 167 168 ensureEmptyDirectoriesExist(ctx, myPath) 169 170 var execs []string 171 for _, pathEntry := range filepath.SplitList(origPath) { 172 if pathEntry == "" { 173 // Ignore the current directory 174 continue 175 } 176 // TODO(dwillemsen): remove path entries under TOP? or anything 177 // that looks like an android source dir? They won't exist on 178 // the build servers, since they're added by envsetup.sh. 179 // (Except for the JDK, which is configured in ui/build/config.go) 180 181 execs = append(execs, parsePathDir(pathEntry)...) 182 } 183 184 if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") { 185 ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.") 186 } 187 188 for _, name := range execs { 189 if !paths.GetConfig(name).Symlink { 190 continue 191 } 192 193 err := os.Symlink("../.path_interposer", filepath.Join(myPath, name)) 194 // Intentionally ignore existing files -- that means that we 195 // just created it, and the first one should win. 196 if err != nil && !os.IsExist(err) { 197 ctx.Fatalln("Failed to create symlink:", err) 198 } 199 } 200 201 myPath, _ = filepath.Abs(myPath) 202 203 // We put some prebuilts in $PATH, since it's infeasible to add dependencies for all of 204 // them. 205 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86") 206 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath 207 208 config.Environment().Set("PATH", myPath) 209 config.pathReplaced = true 210} 211