1
0
mirror of https://github.com/golang/go synced 2024-11-13 17:40:23 -07:00

cmd/pack: add 'c' command to create archive

When Go 1.3 is released, this will keep existing
Go 1.2 build scripts that use 'go tool pack grc' working.
For efficiency, such scripts should be changed to
use 6g -pack instead, but keeping the old behavior
available enables a more graceful transition.

LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/66130043
This commit is contained in:
Russ Cox 2014-02-19 17:08:44 -05:00
parent 15c1ab646d
commit 0649a73606
3 changed files with 135 additions and 10 deletions

View File

@ -14,6 +14,7 @@ Pack applies the operation to the archive, using the names as arguments to the o
The operation op is given by one of these letters:
c append files (from the file system) to a new archive
p print files from the archive
r append files (from the file system) to the archive
t list files from the archive
@ -27,8 +28,8 @@ even if a file with the given name already exists in the archive. In this way
pack's r operation is more like Unix ar's rq operation.
Adding the letter v to an operation, as in pv or rv, enables verbose operation:
For the c and r commands, names are printed as files are added.
For the p command, each file is prefixed by the name on a line by itself.
For the r command, names are printed as files are added.
For the t command, the listing includes additional file metadata.
For the x command, names are printed as files are extracted.

View File

@ -5,6 +5,9 @@
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"log"
@ -32,7 +35,10 @@ the name, and if shorter, space padded on the right.
*/
const usageMessage = `Usage: pack op file.a [name....]
Where op is one of prtx optionally followed by v for verbose output.
Where op is one of cprtx optionally followed by v for verbose output.
For compatibility with old Go build environments the op string grc is
accepted as a synonym for c.
For more information, run
godoc cmd/pack`
@ -58,6 +64,10 @@ func main() {
ar = archive(os.Args[2], os.O_RDWR, os.Args[3:])
ar.scan(ar.skipContents)
ar.addFiles()
case 'c':
ar = archive(os.Args[2], os.O_RDWR|os.O_TRUNC, os.Args[3:])
ar.addPkgdef()
ar.addFiles()
case 't':
ar = archive(os.Args[2], os.O_RDONLY, os.Args[3:])
ar.scan(ar.tableOfContents)
@ -83,9 +93,17 @@ var (
// setOp parses the operation string (first argument).
func setOp(arg string) {
// Recognize 'go tool pack grc' because that was the
// formerly canonical way to build a new archive
// from a set of input files. Accepting it keeps old
// build systems working with both Go 1.2 and Go 1.3.
if arg == "grc" {
arg = "c"
}
for _, r := range arg {
switch r {
case 'p', 'r', 't', 'x':
case 'c', 'p', 'r', 't', 'x':
if op != 0 {
// At most one can be set.
usage()
@ -116,12 +134,13 @@ const (
type Archive struct {
fd *os.File // Open file descriptor.
files []string // Explicit list of files to be processed.
pad int // Padding bytes required at end of current archive file
}
// archive opens (or if necessary creates) the named archive.
func archive(name string, mode int, files []string) *Archive {
fd, err := os.OpenFile(name, mode, 0)
if err != nil && mode == os.O_RDWR && os.IsNotExist(err) {
if err != nil && mode&^os.O_TRUNC == os.O_RDWR && os.IsNotExist(err) {
fd, err = create(name)
}
if err != nil {
@ -317,10 +336,7 @@ func (ar *Archive) addFile(fd FileLike) {
mtime := int64(0)
uid := 0
gid := 0
n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(info.Name()), mtime, uid, gid, info.Mode(), info.Size())
if err != nil || n != entryLen {
log.Fatal("writing entry header: ", err)
}
ar.startFile(info.Name(), mtime, uid, gid, info.Mode(), info.Size())
n64, err := io.Copy(ar.fd, fd)
if err != nil {
log.Fatal("writing file: ", err)
@ -328,14 +344,78 @@ func (ar *Archive) addFile(fd FileLike) {
if n64 != info.Size() {
log.Fatal("writing file: wrote %d bytes; file is size %d", n64, info.Size())
}
if info.Size()&1 == 1 {
_, err = ar.fd.Write([]byte{0})
ar.endFile()
}
// startFile writes the archive entry header.
func (ar *Archive) startFile(name string, mtime int64, uid, gid int, mode os.FileMode, size int64) {
n, err := fmt.Fprintf(ar.fd, entryHeader, exactly16Bytes(name), mtime, uid, gid, mode, size)
if err != nil || n != entryLen {
log.Fatal("writing entry header: ", err)
}
ar.pad = int(size & 1)
}
// endFile writes the archive entry tail (a single byte of padding, if the file size was odd).
func (ar *Archive) endFile() {
if ar.pad != 0 {
_, err := ar.fd.Write([]byte{0})
if err != nil {
log.Fatal("writing archive: ", err)
}
ar.pad = 0
}
}
// addPkgdef adds the __.PKGDEF file to the archive, copied
// from the first Go object file on the file list, if any.
// The archive is known to be empty.
func (ar *Archive) addPkgdef() {
for _, file := range ar.files {
pkgdef, err := readPkgdef(file)
if err != nil {
continue
}
if verbose {
fmt.Printf("__.PKGDEF # %s\n", file)
}
ar.startFile("__.PKGDEF", 0, 0, 0, 0644, int64(len(pkgdef)))
_, err = ar.fd.Write(pkgdef)
if err != nil {
log.Fatal("writing __.PKGDEF: ", err)
}
ar.endFile()
break
}
}
// readPkgdef extracts the __.PKGDEF data from a Go object file.
func readPkgdef(file string) (data []byte, err error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
// Read from file, collecting header for __.PKGDEF.
// The header is from the beginning of the file until a line
// containing just "!". The first line must begin with "go object ".
var buf bytes.Buffer
scan := bufio.NewScanner(f)
for scan.Scan() {
line := scan.Text()
if buf.Len() == 0 && !strings.HasPrefix(line, "go object ") {
return nil, errors.New("not a Go object file")
}
if line == "!" {
break
}
buf.WriteString(line)
buf.WriteString("\n")
}
return buf.Bytes(), nil
}
// exactly16Bytes truncates the string if necessary so it is at most 16 bytes long,
// then pads the result with spaces to be exactly 16 bytes.
// Fmt uses runes for its width calculation, but we need bytes in the entry header.

View File

@ -10,7 +10,9 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"unicode/utf8"
@ -164,6 +166,48 @@ func TestExtract(t *testing.T) {
}
}
// Test that pack-created archives can be understood by the tools.
func TestHello(t *testing.T) {
dir := tmpDir(t)
defer os.RemoveAll(dir)
hello := filepath.Join(dir, "hello.go")
prog := `
package main
func main() {
println("hello world")
}
`
err := ioutil.WriteFile(hello, []byte(prog), 0666)
if err != nil {
t.Fatal(err)
}
run := func(args ...string) string {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("%v: %v\n%s", args, err, string(out))
}
return string(out)
}
out := run("go", "env")
i := strings.Index(out, "GOCHAR=\"")
if i < 0 {
t.Fatal("cannot find GOCHAR in 'go env' output")
}
char := out[i+8 : i+9]
run("go", "build", "cmd/pack") // writes pack binary to dir
run("go", "tool", char+"g", "hello.go")
run("./pack", "grc", "hello.a", "hello."+char)
run("go", "tool", char+"l", "-o", "a.out", "hello.a")
out = run("./a.out")
if out != "hello world\n" {
t.Fatal("incorrect output: %q, want %q", out, "hello world\n")
}
}
// Fake implementation of files.
var helloFile = &FakeFile{