forked from bazelbuild/rules_go
-
Notifications
You must be signed in to change notification settings - Fork 1
/
pack.go
332 lines (302 loc) · 8.88 KB
/
pack.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// Copyright 2017 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
)
// pack copies an .a file and appends a list of .o files to the copy using
// go tool pack. It is invoked by the Go rules as an action.
//
// pack can also append .o files contained in a static library passed in
// with the -arc option. That archive may be in BSD or SysV / GNU format.
// pack has a primitive parser for these formats, since cmd/pack can't
// handle them, and ar may not be available (cpp.ar_executable is libtool
// on darwin).
func pack(args []string) error {
args, err := readParamsFiles(args)
if err != nil {
return err
}
flags := flag.NewFlagSet("GoPack", flag.ExitOnError)
goenv := envFlags(flags)
inArchive := flags.String("in", "", "Path to input archive")
outArchive := flags.String("out", "", "Path to output archive")
objects := multiFlag{}
flags.Var(&objects, "obj", "Object to append (may be repeated)")
archives := multiFlag{}
flags.Var(&archives, "arc", "Archives to append")
if err := flags.Parse(args); err != nil {
return err
}
if err := goenv.checkFlags(); err != nil {
return err
}
if err := copyFile(abs(*inArchive), abs(*outArchive)); err != nil {
return err
}
dir, err := ioutil.TempDir("", "go-pack")
if err != nil {
return err
}
defer os.RemoveAll(dir)
names := map[string]struct{}{}
for _, archive := range archives {
archiveObjects, err := extractFiles(archive, dir, names)
if err != nil {
return err
}
objects = append(objects, archiveObjects...)
}
return appendFiles(goenv, abs(*outArchive), objects)
}
func copyFile(inPath, outPath string) error {
inFile, err := os.Open(inPath)
if err != nil {
return err
}
defer inFile.Close()
outFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return err
}
defer outFile.Close()
_, err = io.Copy(outFile, inFile)
return err
}
func linkFile(inPath, outPath string) error {
inPath, err := filepath.Abs(inPath)
if err != nil {
return err
}
return os.Symlink(inPath, outPath)
}
func copyOrLinkFile(inPath, outPath string) error {
if runtime.GOOS == "windows" {
return copyFile(inPath, outPath)
} else {
return linkFile(inPath, outPath)
}
}
const (
// arHeader appears at the beginning of archives created by "ar" and
// "go tool pack" on all platforms.
arHeader = "!<arch>\n"
// entryLength is the size in bytes of the metadata preceding each file
// in an archive.
entryLength = 60
)
var zeroBytes = []byte("0 ")
func extractFiles(archive, dir string, names map[string]struct{}) (files []string, err error) {
f, err := os.Open(archive)
if err != nil {
return nil, err
}
defer f.Close()
r := bufio.NewReader(f)
header := make([]byte, len(arHeader))
if _, err := io.ReadFull(r, header); err != nil || string(header) != arHeader {
return nil, fmt.Errorf("%s: bad header", archive)
}
var nameData []byte
for {
name, size, err := readMetadata(r, &nameData)
if err == io.EOF {
return files, nil
}
if err != nil {
return nil, err
}
if !isObjectFile(name) {
if err := skipFile(r, size); err != nil {
return nil, err
}
continue
}
name, err = simpleName(name, names)
if err != nil {
return nil, err
}
name = filepath.Join(dir, name)
if err := extractFile(r, name, size); err != nil {
return nil, err
}
files = append(files, name)
}
}
// readMetadata reads the relevant fields of an entry. Before calling,
// r must be positioned at the beginning of an entry. Afterward, r will
// be positioned at the beginning of the file data. io.EOF is returned if
// there are no more files in the archive.
//
// Both BSD and GNU / SysV naming conventions are supported.
func readMetadata(r *bufio.Reader, nameData *[]byte) (name string, size int64, err error) {
retry:
// Each file is preceded by a 60-byte header that contains its metadata.
// We only care about two fields, name and size. Other fields (mtime,
// owner, group, mode) are ignored because they don't affect compilation.
var entry [entryLength]byte
if _, err := io.ReadFull(r, entry[:]); err != nil {
return "", 0, err
}
sizeField := strings.TrimSpace(string(entry[48:58]))
size, err = strconv.ParseInt(sizeField, 10, 64)
if err != nil {
return "", 0, err
}
nameField := strings.TrimRight(string(entry[:16]), " ")
switch {
case strings.HasPrefix(nameField, "#1/"):
// BSD-style name. The number of bytes in the name is written here in
// ASCII, right-padded with spaces. The actual name is stored at the
// beginning of the file data, left-padded with NUL bytes.
nameField = nameField[len("#1/"):]
nameLen, err := strconv.ParseInt(nameField, 10, 64)
if err != nil {
return "", 0, err
}
nameBuf := make([]byte, nameLen)
if _, err := io.ReadFull(r, nameBuf); err != nil {
return "", 0, err
}
name = strings.TrimRight(string(nameBuf), "\x00")
size -= nameLen
case nameField == "//":
// GNU / SysV-style name data. This is a fake file that contains names
// for files with long names. We read this into nameData, then read
// the next entry.
*nameData = make([]byte, size)
if _, err := io.ReadFull(r, *nameData); err != nil {
return "", 0, err
}
if size%2 != 0 {
// Files are aligned at 2-byte offsets. Discard the padding byte if the
// size was odd.
if _, err := r.ReadByte(); err != nil {
return "", 0, err
}
}
goto retry
case nameField == "/":
// GNU / SysV-style symbol lookup table. Skip.
if err := skipFile(r, size); err != nil {
return "", 0, err
}
goto retry
case strings.HasPrefix(nameField, "/"):
// GNU / SysV-style long file name. The number that follows the slash is
// an offset into the name data that should have been read earlier.
// The file name ends with a slash.
nameField = nameField[1:]
nameOffset, err := strconv.Atoi(nameField)
if err != nil {
return "", 0, err
}
if nameData == nil || nameOffset < 0 || nameOffset >= len(*nameData) {
return "", 0, fmt.Errorf("invalid name length: %d", nameOffset)
}
i := bytes.IndexByte((*nameData)[nameOffset:], '/')
if i < 0 {
return "", 0, errors.New("file name does not end with '/'")
}
name = string((*nameData)[nameOffset : nameOffset+i])
case strings.HasSuffix(nameField, "/"):
// GNU / SysV-style short file name.
name = nameField[:len(nameField)-1]
default:
// Common format name.
name = nameField
}
return name, size, err
}
// extractFile reads size bytes from r and writes them to a new file, name.
func extractFile(r *bufio.Reader, name string, size int64) error {
w, err := os.Create(name)
if err != nil {
return err
}
defer w.Close()
_, err = io.CopyN(w, r, size)
if err != nil {
return err
}
if size%2 != 0 {
// Files are aligned at 2-byte offsets. Discard the padding byte if the
// size was odd.
if _, err := r.ReadByte(); err != nil {
return err
}
}
return nil
}
func skipFile(r *bufio.Reader, size int64) error {
if size%2 != 0 {
// Files are aligned at 2-byte offsets. Discard the padding byte if the
// size was odd.
size += 1
}
_, err := r.Discard(int(size))
return err
}
func isObjectFile(name string) bool {
return strings.HasSuffix(name, ".o")
}
// simpleName returns a file name which is at most 15 characters
// and doesn't conflict with other names. If it is not possible to choose
// such a name, simpleName will truncate the given name to 15 characters.
// The original file extension will be preserved.
func simpleName(name string, names map[string]struct{}) (string, error) {
if _, ok := names[name]; !ok && len(name) < 16 {
names[name] = struct{}{}
return name, nil
}
var stem, ext string
if i := strings.LastIndexByte(name, '.'); i < 0 {
stem = name
} else {
stem = strings.Replace(name[:i], ".", "_", -1)
ext = name[i:]
}
for n := 0; n < len(names)+1; n++ {
ns := strconv.Itoa(n)
stemLen := 15 - len(ext) - len(ns)
if stemLen < 0 {
break
}
if stemLen > len(stem) {
stemLen = len(stem)
}
candidate := stem[:stemLen] + ns + ext
if _, ok := names[candidate]; !ok {
names[candidate] = struct{}{}
return candidate, nil
}
}
return "", fmt.Errorf("cannot shorten file name: %q", name)
}
func appendFiles(goenv *env, archive string, files []string) error {
args := goenv.goTool("pack", "r", archive)
args = append(args, files...)
return goenv.runCommand(args)
}