github.com/0xKiwi/rules_go@v0.24.3/go/tools/builders/pack.go (about) 1 // Copyright 2017 The Bazel Authors. 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 15 package main 16 17 import ( 18 "bufio" 19 "bytes" 20 "errors" 21 "flag" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "runtime" 28 "strconv" 29 "strings" 30 ) 31 32 // pack copies an .a file and appends a list of .o files to the copy using 33 // go tool pack. It is invoked by the Go rules as an action. 34 // 35 // pack can also append .o files contained in a static library passed in 36 // with the -arc option. That archive may be in BSD or SysV / GNU format. 37 // pack has a primitive parser for these formats, since cmd/pack can't 38 // handle them, and ar may not be available (cpp.ar_executable is libtool 39 // on darwin). 40 func pack(args []string) error { 41 args, err := expandParamsFiles(args) 42 if err != nil { 43 return err 44 } 45 flags := flag.NewFlagSet("GoPack", flag.ExitOnError) 46 goenv := envFlags(flags) 47 inArchive := flags.String("in", "", "Path to input archive") 48 outArchive := flags.String("out", "", "Path to output archive") 49 objects := multiFlag{} 50 flags.Var(&objects, "obj", "Object to append (may be repeated)") 51 archives := multiFlag{} 52 flags.Var(&archives, "arc", "Archives to append") 53 if err := flags.Parse(args); err != nil { 54 return err 55 } 56 if err := goenv.checkFlags(); err != nil { 57 return err 58 } 59 60 if err := copyFile(abs(*inArchive), abs(*outArchive)); err != nil { 61 return err 62 } 63 64 dir, err := ioutil.TempDir("", "go-pack") 65 if err != nil { 66 return err 67 } 68 defer os.RemoveAll(dir) 69 70 names := map[string]struct{}{} 71 for _, archive := range archives { 72 archiveObjects, err := extractFiles(archive, dir, names) 73 if err != nil { 74 return err 75 } 76 objects = append(objects, archiveObjects...) 77 } 78 79 return appendFiles(goenv, abs(*outArchive), objects) 80 } 81 82 func copyFile(inPath, outPath string) error { 83 inFile, err := os.Open(inPath) 84 if err != nil { 85 return err 86 } 87 defer inFile.Close() 88 outFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) 89 if err != nil { 90 return err 91 } 92 defer outFile.Close() 93 _, err = io.Copy(outFile, inFile) 94 return err 95 } 96 97 func linkFile(inPath, outPath string) error { 98 inPath, err := filepath.Abs(inPath) 99 if err != nil { 100 return err 101 } 102 return os.Symlink(inPath, outPath) 103 } 104 105 func copyOrLinkFile(inPath, outPath string) error { 106 if runtime.GOOS == "windows" { 107 return copyFile(inPath, outPath) 108 } else { 109 return linkFile(inPath, outPath) 110 } 111 } 112 113 const ( 114 // arHeader appears at the beginning of archives created by "ar" and 115 // "go tool pack" on all platforms. 116 arHeader = "!<arch>\n" 117 118 // entryLength is the size in bytes of the metadata preceding each file 119 // in an archive. 120 entryLength = 60 121 122 // pkgDef is the name of the export data file within an archive 123 pkgDef = "__.PKGDEF" 124 125 // nogoFact is the name of the nogo fact file 126 nogoFact = "nogo.out" 127 ) 128 129 var zeroBytes = []byte("0 ") 130 131 type bufioReaderWithCloser struct { 132 // bufio.Reader is needed to skip bytes in archives 133 *bufio.Reader 134 io.Closer 135 } 136 137 func extractFiles(archive, dir string, names map[string]struct{}) (files []string, err error) { 138 rc, err := openArchive(archive) 139 if err != nil { 140 return nil, err 141 } 142 defer rc.Close() 143 144 var nameData []byte 145 bufReader := rc.Reader 146 for { 147 name, size, err := readMetadata(bufReader, &nameData) 148 if err == io.EOF { 149 return files, nil 150 } 151 if err != nil { 152 return nil, err 153 } 154 if !isObjectFile(name) { 155 if err := skipFile(bufReader, size); err != nil { 156 return nil, err 157 } 158 continue 159 } 160 name, err = simpleName(name, names) 161 if err != nil { 162 return nil, err 163 } 164 name = filepath.Join(dir, name) 165 if err := extractFile(bufReader, name, size); err != nil { 166 return nil, err 167 } 168 files = append(files, name) 169 } 170 } 171 172 func openArchive(archive string) (bufioReaderWithCloser, error) { 173 f, err := os.Open(archive) 174 if err != nil { 175 return bufioReaderWithCloser{}, err 176 } 177 r := bufio.NewReader(f) 178 header := make([]byte, len(arHeader)) 179 if _, err := io.ReadFull(r, header); err != nil || string(header) != arHeader { 180 f.Close() 181 return bufioReaderWithCloser{}, fmt.Errorf("%s: bad header", archive) 182 } 183 return bufioReaderWithCloser{r, f}, nil 184 } 185 186 // readMetadata reads the relevant fields of an entry. Before calling, 187 // r must be positioned at the beginning of an entry. Afterward, r will 188 // be positioned at the beginning of the file data. io.EOF is returned if 189 // there are no more files in the archive. 190 // 191 // Both BSD and GNU / SysV naming conventions are supported. 192 func readMetadata(r *bufio.Reader, nameData *[]byte) (name string, size int64, err error) { 193 retry: 194 // Each file is preceded by a 60-byte header that contains its metadata. 195 // We only care about two fields, name and size. Other fields (mtime, 196 // owner, group, mode) are ignored because they don't affect compilation. 197 var entry [entryLength]byte 198 if _, err := io.ReadFull(r, entry[:]); err != nil { 199 return "", 0, err 200 } 201 202 sizeField := strings.TrimSpace(string(entry[48:58])) 203 size, err = strconv.ParseInt(sizeField, 10, 64) 204 if err != nil { 205 return "", 0, err 206 } 207 208 nameField := strings.TrimRight(string(entry[:16]), " ") 209 switch { 210 case strings.HasPrefix(nameField, "#1/"): 211 // BSD-style name. The number of bytes in the name is written here in 212 // ASCII, right-padded with spaces. The actual name is stored at the 213 // beginning of the file data, left-padded with NUL bytes. 214 nameField = nameField[len("#1/"):] 215 nameLen, err := strconv.ParseInt(nameField, 10, 64) 216 if err != nil { 217 return "", 0, err 218 } 219 nameBuf := make([]byte, nameLen) 220 if _, err := io.ReadFull(r, nameBuf); err != nil { 221 return "", 0, err 222 } 223 name = strings.TrimRight(string(nameBuf), "\x00") 224 size -= nameLen 225 226 case nameField == "//": 227 // GNU / SysV-style name data. This is a fake file that contains names 228 // for files with long names. We read this into nameData, then read 229 // the next entry. 230 *nameData = make([]byte, size) 231 if _, err := io.ReadFull(r, *nameData); err != nil { 232 return "", 0, err 233 } 234 if size%2 != 0 { 235 // Files are aligned at 2-byte offsets. Discard the padding byte if the 236 // size was odd. 237 if _, err := r.ReadByte(); err != nil { 238 return "", 0, err 239 } 240 } 241 goto retry 242 243 case nameField == "/": 244 // GNU / SysV-style symbol lookup table. Skip. 245 if err := skipFile(r, size); err != nil { 246 return "", 0, err 247 } 248 goto retry 249 250 case strings.HasPrefix(nameField, "/"): 251 // GNU / SysV-style long file name. The number that follows the slash is 252 // an offset into the name data that should have been read earlier. 253 // The file name ends with a slash. 254 nameField = nameField[1:] 255 nameOffset, err := strconv.Atoi(nameField) 256 if err != nil { 257 return "", 0, err 258 } 259 if nameData == nil || nameOffset < 0 || nameOffset >= len(*nameData) { 260 return "", 0, fmt.Errorf("invalid name length: %d", nameOffset) 261 } 262 i := bytes.IndexByte((*nameData)[nameOffset:], '/') 263 if i < 0 { 264 return "", 0, errors.New("file name does not end with '/'") 265 } 266 name = string((*nameData)[nameOffset : nameOffset+i]) 267 268 case strings.HasSuffix(nameField, "/"): 269 // GNU / SysV-style short file name. 270 name = nameField[:len(nameField)-1] 271 272 default: 273 // Common format name. 274 name = nameField 275 } 276 277 return name, size, err 278 } 279 280 // extractFile reads size bytes from r and writes them to a new file, name. 281 func extractFile(r *bufio.Reader, name string, size int64) error { 282 w, err := os.Create(name) 283 if err != nil { 284 return err 285 } 286 defer w.Close() 287 _, err = io.CopyN(w, r, size) 288 if err != nil { 289 return err 290 } 291 if size%2 != 0 { 292 // Files are aligned at 2-byte offsets. Discard the padding byte if the 293 // size was odd. 294 if _, err := r.ReadByte(); err != nil { 295 return err 296 } 297 } 298 return nil 299 } 300 301 func skipFile(r *bufio.Reader, size int64) error { 302 if size%2 != 0 { 303 // Files are aligned at 2-byte offsets. Discard the padding byte if the 304 // size was odd. 305 size += 1 306 } 307 _, err := r.Discard(int(size)) 308 return err 309 } 310 311 func isObjectFile(name string) bool { 312 return strings.HasSuffix(name, ".o") 313 } 314 315 // simpleName returns a file name which is at most 15 characters 316 // and doesn't conflict with other names. If it is not possible to choose 317 // such a name, simpleName will truncate the given name to 15 characters. 318 // The original file extension will be preserved. 319 func simpleName(name string, names map[string]struct{}) (string, error) { 320 if _, ok := names[name]; !ok && len(name) < 16 { 321 names[name] = struct{}{} 322 return name, nil 323 } 324 var stem, ext string 325 if i := strings.LastIndexByte(name, '.'); i < 0 { 326 stem = name 327 } else { 328 stem = strings.Replace(name[:i], ".", "_", -1) 329 ext = name[i:] 330 } 331 for n := 0; n < len(names)+1; n++ { 332 ns := strconv.Itoa(n) 333 stemLen := 15 - len(ext) - len(ns) 334 if stemLen < 0 { 335 break 336 } 337 if stemLen > len(stem) { 338 stemLen = len(stem) 339 } 340 candidate := stem[:stemLen] + ns + ext 341 if _, ok := names[candidate]; !ok { 342 names[candidate] = struct{}{} 343 return candidate, nil 344 } 345 } 346 return "", fmt.Errorf("cannot shorten file name: %q", name) 347 } 348 349 func appendFiles(goenv *env, archive string, files []string) error { 350 args := goenv.goTool("pack", "r", archive) 351 args = append(args, files...) 352 return goenv.runCommand(args) 353 } 354 355 type readWithCloser struct { 356 io.Reader 357 io.Closer 358 } 359 360 func readFileInArchive(fileName, archive string) (io.ReadCloser, error) { 361 rc, err := openArchive(archive) 362 if err != nil { 363 return nil, err 364 } 365 var nameData []byte 366 bufReader := rc.Reader 367 for err == nil { 368 // avoid shadowing err in the loop it can be returned correctly in the end 369 var ( 370 name string 371 size int64 372 ) 373 name, size, err = readMetadata(bufReader, &nameData) 374 if err != nil { 375 break 376 } 377 if name == fileName { 378 return readWithCloser{ 379 Reader: io.LimitReader(rc, size), 380 Closer: rc, 381 }, nil 382 } 383 err = skipFile(bufReader, size) 384 } 385 if err == io.EOF { 386 err = os.ErrNotExist 387 } 388 rc.Close() 389 return nil, err 390 } 391 392 func extractFileFromArchive(archive, dir, name string) (err error) { 393 archiveReader, err := readFileInArchive(name, archive) 394 if err != nil { 395 return fmt.Errorf("error reading %s from %s: %v", name, archive, err) 396 } 397 defer func() { 398 e := archiveReader.Close() 399 if e != nil && err == nil { 400 err = fmt.Errorf("error closing %q: %v", archive, e) 401 } 402 }() 403 outPath := filepath.Join(dir, pkgDef) 404 outFile, err := os.Create(outPath) 405 if err != nil { 406 return fmt.Errorf("error creating %s: %v", outPath, err) 407 } 408 defer func() { 409 e := outFile.Close() 410 if e != nil && err == nil { 411 err = fmt.Errorf("error closing %q: %v", outPath, e) 412 } 413 }() 414 if size, err := io.Copy(outFile, archiveReader); err != nil { 415 return fmt.Errorf("error writing %s: %v", outPath, err) 416 } else if size == 0 { 417 return fmt.Errorf("%s is empty in %s", name, archive) 418 } 419 return err 420 }