]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/pack: add 'c' command to create archive
authorRuss Cox <rsc@golang.org>
Wed, 19 Feb 2014 22:08:44 +0000 (17:08 -0500)
committerRuss Cox <rsc@golang.org>
Wed, 19 Feb 2014 22:08:44 +0000 (17:08 -0500)
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

src/cmd/pack/doc.go
src/cmd/pack/pack.go
src/cmd/pack/pack_test.go

index cebcb36a3359038442611835139fd22fceb45804..1529e07e906b66d79fd982320f98b7633de190c9 100644 (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.
 
index 9996ec975398f2f3d9b6f30f715d451361a27049..99e22ed7ea3367923a93e8e29c322188b76980fb 100644 (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,12 +344,76 @@ 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,
index 53fdc18a6a4913d8a585e90a7d7af2b7fa495538..427ba8b30da7cef0a2ef65c268905fbef014afb6 100644 (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{