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  }