Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(225)

Side by Side Diff: go/src/infra/tools/cipd/builder.go

Issue 1129043003: cipd: Refactor client to make it more readable. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « go/src/infra/tools/cipd/apps/cipd/main.go ('k') | go/src/infra/tools/cipd/builder_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package cipd
6
7 import (
8 "archive/zip"
9 "bytes"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "os"
14 "strings"
15 "time"
16 )
17
18 // BuildInstanceOptions defines options for BuildInstance function.
19 type BuildInstanceOptions struct {
20 // List of files to add to the package.
21 Input []File
22 // Where to write the package file to.
23 Output io.Writer
24 // Package name, e.g. 'infra/tools/cipd'.
25 PackageName string
26 }
27
28 // BuildInstance builds a new package instance for package named opts.PackageNam e
29 // by archiving input files (passed via opts.Input). The final binary is written
30 // to opts.Output. Some output may be written even if BuildInstance eventually
31 // returns an error.
32 func BuildInstance(opts BuildInstanceOptions) error {
33 err := ValidatePackageName(opts.PackageName)
34 if err != nil {
35 return err
36 }
37
38 // Make sure no files are written to package service directory.
39 for _, f := range opts.Input {
40 if strings.HasPrefix(f.Name(), packageServiceDir+"/") {
41 return fmt.Errorf("Can't write to %s: %s", packageServic eDir, f.Name())
42 }
43 }
44
45 // Generate the manifest file, add to the list of input files.
46 manifestFile, err := makeManifestFile(opts)
47 if err != nil {
48 return err
49 }
50 files := append(opts.Input, manifestFile)
51
52 // Make sure filenames are unique.
53 seenNames := make(map[string]struct{}, len(files))
54 for _, f := range files {
55 _, seen := seenNames[f.Name()]
56 if seen {
57 return fmt.Errorf("File %s is provided twice", f.Name())
58 }
59 seenNames[f.Name()] = struct{}{}
60 }
61
62 // Write the final zip file.
63 return zipInputFiles(files, opts.Output)
64 }
65
66 // zipInputFiles deterministically builds a zip archive out of input files and
67 // writes it to the writer. Files are written in the order given.
68 func zipInputFiles(files []File, w io.Writer) error {
69 writer := zip.NewWriter(w)
70 defer writer.Close()
71
72 // Reports zipping progress to the log each second.
73 lastReport := time.Time{}
74 progress := func(count int) {
75 if time.Since(lastReport) > time.Second {
76 lastReport = time.Now()
77 log.Infof("Zipping files: %d files left", len(files)-cou nt)
78 }
79 }
80
81 for i, in := range files {
82 progress(i)
83
84 // Intentionally do not add timestamp or file mode to make zip a rchive
85 // deterministic. See also zip.FileInfoHeader() implementation.
86 fh := zip.FileHeader{
87 Name: in.Name(),
88 Method: zip.Deflate,
89 }
90
91 mode := os.FileMode(0600)
92 if in.Executable() {
93 mode |= 0100
94 }
95 if in.Symlink() {
96 mode |= os.ModeSymlink
97 }
98 fh.SetMode(mode)
99
100 dst, err := writer.CreateHeader(&fh)
101 if err != nil {
102 return err
103 }
104 if in.Symlink() {
105 err = zipSymlinkFile(dst, in)
106 } else {
107 err = zipRegularFile(dst, in)
108 }
109 if err != nil {
110 return err
111 }
112 }
113
114 return nil
115 }
116
117 func zipRegularFile(dst io.Writer, f File) error {
118 src, err := f.Open()
119 if err != nil {
120 return err
121 }
122 defer src.Close()
123 written, err := io.Copy(dst, src)
124 if err != nil {
125 return err
126 }
127 if uint64(written) != f.Size() {
128 return fmt.Errorf("File %s changed midway", f.Name())
129 }
130 return nil
131 }
132
133 func zipSymlinkFile(dst io.Writer, f File) error {
134 target, err := f.SymlinkTarget()
135 if err != nil {
136 return err
137 }
138 // Symlinks are zipped as text files with target path. os.ModeSymlink bi t in
139 // the header distinguishes them from regular files.
140 _, err = dst.Write([]byte(target))
141 return err
142 }
143
144 ////////////////////////////////////////////////////////////////////////////////
145
146 type manifestFile []byte
147
148 func (m *manifestFile) Name() string { return manifestName }
149 func (m *manifestFile) Size() uint64 { return uint64(len(*m)) }
150 func (m *manifestFile) Executable() bool { return false }
151 func (m *manifestFile) Symlink() bool { return false }
152
153 func (m *manifestFile) SymlinkTarget() (string, error) {
154 return "", fmt.Errorf("Not a symlink: %s", m.Name())
155 }
156
157 func (m *manifestFile) Open() (io.ReadCloser, error) {
158 return ioutil.NopCloser(bytes.NewReader(*m)), nil
159 }
160
161 // makeManifestFile generates a package manifest file and returns it as
162 // File interface.
163 func makeManifestFile(opts BuildInstanceOptions) (File, error) {
164 buf := &bytes.Buffer{}
165 err := writeManifest(&Manifest{
166 FormatVersion: manifestFormatVersion,
167 PackageName: opts.PackageName,
168 }, buf)
169 if err != nil {
170 return nil, err
171 }
172 out := manifestFile(buf.Bytes())
173 return &out, nil
174 }
OLDNEW
« no previous file with comments | « go/src/infra/tools/cipd/apps/cipd/main.go ('k') | go/src/infra/tools/cipd/builder_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698