1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package load
6
7import (
8	"cmd/go/internal/base"
9	"cmd/internal/quoted"
10	"fmt"
11	"strings"
12)
13
14var (
15	BuildAsmflags   PerPackageFlag // -asmflags
16	BuildGcflags    PerPackageFlag // -gcflags
17	BuildLdflags    PerPackageFlag // -ldflags
18	BuildGccgoflags PerPackageFlag // -gccgoflags
19)
20
21// A PerPackageFlag is a command-line flag implementation (a flag.Value)
22// that allows specifying different effective flags for different packages.
23// See 'go help build' for more details about per-package flags.
24type PerPackageFlag struct {
25	raw     string
26	present bool
27	values  []ppfValue
28}
29
30// A ppfValue is a single <pattern>=<flags> per-package flag value.
31type ppfValue struct {
32	match func(*Package) bool // compiled pattern
33	flags []string
34}
35
36// Set is called each time the flag is encountered on the command line.
37func (f *PerPackageFlag) Set(v string) error {
38	return f.set(v, base.Cwd())
39}
40
41// set is the implementation of Set, taking a cwd (current working directory) for easier testing.
42func (f *PerPackageFlag) set(v, cwd string) error {
43	f.raw = v
44	f.present = true
45	match := func(p *Package) bool { return p.Internal.CmdlinePkg || p.Internal.CmdlineFiles } // default predicate with no pattern
46	// For backwards compatibility with earlier flag splitting, ignore spaces around flags.
47	v = strings.TrimSpace(v)
48	if v == "" {
49		// Special case: -gcflags="" means no flags for command-line arguments
50		// (overrides previous -gcflags="-whatever").
51		f.values = append(f.values, ppfValue{match, []string{}})
52		return nil
53	}
54	if !strings.HasPrefix(v, "-") {
55		i := strings.Index(v, "=")
56		if i < 0 {
57			return fmt.Errorf("missing =<value> in <pattern>=<value>")
58		}
59		if i == 0 {
60			return fmt.Errorf("missing <pattern> in <pattern>=<value>")
61		}
62		if v[0] == '\'' || v[0] == '"' {
63			return fmt.Errorf("parameter may not start with quote character %c", v[0])
64		}
65		pattern := strings.TrimSpace(v[:i])
66		match = MatchPackage(pattern, cwd)
67		v = v[i+1:]
68	}
69	flags, err := quoted.Split(v)
70	if err != nil {
71		return err
72	}
73	if flags == nil {
74		flags = []string{}
75	}
76	f.values = append(f.values, ppfValue{match, flags})
77	return nil
78}
79
80func (f *PerPackageFlag) String() string { return f.raw }
81
82// Present reports whether the flag appeared on the command line.
83func (f *PerPackageFlag) Present() bool {
84	return f.present
85}
86
87// For returns the flags to use for the given package.
88func (f *PerPackageFlag) For(p *Package) []string {
89	flags := []string{}
90	for _, v := range f.values {
91		if v.match(p) {
92			flags = v.flags
93		}
94	}
95	return flags
96}
97