~themue/juju-core/053-env-more-script-friendly

« back to all changes in this revision

Viewing changes to environs/tools/build.go

Merge trunk and resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package tools
 
2
 
 
3
import (
 
4
        "archive/tar"
 
5
        "compress/gzip"
 
6
        "fmt"
 
7
        "io"
 
8
        "io/ioutil"
 
9
        "launchpad.net/juju-core/version"
 
10
        "os"
 
11
        "os/exec"
 
12
        "path/filepath"
 
13
        "strings"
 
14
)
 
15
 
 
16
// archive writes the executable files found in the given directory in
 
17
// gzipped tar format to w.  An error is returned if an entry inside dir
 
18
// is not a regular executable file.
 
19
func archive(w io.Writer, dir string) (err error) {
 
20
        entries, err := ioutil.ReadDir(dir)
 
21
        if err != nil {
 
22
                return err
 
23
        }
 
24
 
 
25
        gzw := gzip.NewWriter(w)
 
26
        defer closeErrorCheck(&err, gzw)
 
27
 
 
28
        tarw := tar.NewWriter(gzw)
 
29
        defer closeErrorCheck(&err, tarw)
 
30
 
 
31
        for _, ent := range entries {
 
32
                h := tarHeader(ent)
 
33
                // ignore local umask
 
34
                if isExecutable(ent) {
 
35
                        h.Mode = 0755
 
36
                } else {
 
37
                        h.Mode = 0644
 
38
                }
 
39
                err := tarw.WriteHeader(h)
 
40
                if err != nil {
 
41
                        return err
 
42
                }
 
43
                if err := copyFile(tarw, filepath.Join(dir, ent.Name())); err != nil {
 
44
                        return err
 
45
                }
 
46
        }
 
47
        return nil
 
48
}
 
49
 
 
50
// copyFile writes the contents of the given file to w.
 
51
func copyFile(w io.Writer, file string) error {
 
52
        f, err := os.Open(file)
 
53
        if err != nil {
 
54
                return err
 
55
        }
 
56
        defer f.Close()
 
57
        _, err = io.Copy(w, f)
 
58
        return err
 
59
}
 
60
 
 
61
// tarHeader returns a tar file header given the file's stat
 
62
// information.
 
63
func tarHeader(i os.FileInfo) *tar.Header {
 
64
        return &tar.Header{
 
65
                Typeflag:   tar.TypeReg,
 
66
                Name:       i.Name(),
 
67
                Size:       i.Size(),
 
68
                Mode:       int64(i.Mode() & 0777),
 
69
                ModTime:    i.ModTime(),
 
70
                AccessTime: i.ModTime(),
 
71
                ChangeTime: i.ModTime(),
 
72
                Uname:      "ubuntu",
 
73
                Gname:      "ubuntu",
 
74
        }
 
75
}
 
76
 
 
77
// isExecutable returns whether the given info
 
78
// represents a regular file executable by (at least) the user.
 
79
func isExecutable(i os.FileInfo) bool {
 
80
        return i.Mode()&(0100|os.ModeType) == 0100
 
81
}
 
82
 
 
83
// closeErrorCheck means that we can ensure that
 
84
// Close errors do not get lost even when we defer them,
 
85
func closeErrorCheck(errp *error, c io.Closer) {
 
86
        err := c.Close()
 
87
        if *errp == nil {
 
88
                *errp = err
 
89
        }
 
90
}
 
91
 
 
92
func setenv(env []string, val string) []string {
 
93
        prefix := val[0 : strings.Index(val, "=")+1]
 
94
        for i, eval := range env {
 
95
                if strings.HasPrefix(eval, prefix) {
 
96
                        env[i] = val
 
97
                        return env
 
98
                }
 
99
        }
 
100
        return append(env, val)
 
101
}
 
102
 
 
103
// bundleTools bundles all the current juju tools in gzipped tar
 
104
// format to the given writer.
 
105
// If forceVersion is not nil, a FORCE-VERSION file is included in
 
106
// the tools bundle so it will lie about its current version number.
 
107
func bundleTools(w io.Writer, forceVersion *version.Number) (version.Binary, error) {
 
108
        dir, err := ioutil.TempDir("", "juju-tools")
 
109
        if err != nil {
 
110
                return version.Binary{}, err
 
111
        }
 
112
        defer os.RemoveAll(dir)
 
113
 
 
114
        cmds := [][]string{
 
115
                {"go", "install", "launchpad.net/juju-core/cmd/jujud"},
 
116
                {"strip", dir + "/jujud"},
 
117
        }
 
118
        env := setenv(os.Environ(), "GOBIN="+dir)
 
119
        for _, args := range cmds {
 
120
                cmd := exec.Command(args[0], args[1:]...)
 
121
                cmd.Env = env
 
122
                out, err := cmd.CombinedOutput()
 
123
                if err != nil {
 
124
                        return version.Binary{}, fmt.Errorf("build command %q failed: %v; %s", args[0], err, out)
 
125
                }
 
126
        }
 
127
        if forceVersion != nil {
 
128
                if err := ioutil.WriteFile(filepath.Join(dir, "FORCE-VERSION"), []byte(forceVersion.String()), 0666); err != nil {
 
129
                        return version.Binary{}, err
 
130
                }
 
131
        }
 
132
        cmd := exec.Command(filepath.Join(dir, "jujud"), "version")
 
133
        out, err := cmd.CombinedOutput()
 
134
        if err != nil {
 
135
                return version.Binary{}, fmt.Errorf("cannot get version from %q: %v; %s", cmd.Args[0], err, out)
 
136
        }
 
137
        tvs := strings.TrimSpace(string(out))
 
138
        tvers, err := version.ParseBinary(tvs)
 
139
        if err != nil {
 
140
                return version.Binary{}, fmt.Errorf("invalid version %q printed by jujud", tvs)
 
141
        }
 
142
        err = archive(w, dir)
 
143
        if err != nil {
 
144
                return version.Binary{}, err
 
145
        }
 
146
        return tvers, err
 
147
}