Involved Source Fileschecks.gofinalize.goinstance.gojoint.golift.goorder.gorevision.go Package solve is plumb's second phase: it turns the providers of one set into a
fully resolved Plan, instantiating generics by demand, inferring the injector
signature, ordering by dependency, and enforcing reachability and naming rules.
It is a deterministic function of its providers; it reads no files and consults
no clock.
# Pipeline
Set resolves one set by running run on a solver:
- classify: split providers into concrete / anchor / result-generic and
validate each result shape.
- seed: register the concrete providers and instantiate the anchors (lifting
their free parameters); registering an instance seeds demand from its inputs.
- solveDemands: the demand fixpoint. Each pass first works the worklist (a
demand a single result pins completely), then, if that made no progress, the
joint step (demands clustered across a template's results, lifting the
parameters no demand pins), until a whole pass adds nothing.
- checkUnusedTemplates / resolveFlexReceivers / finalize: reject a template no
consumer used, choose value-vs-pointer form for flexible struct receivers,
then resolve inputs, order instances by dependency, and build the Plan.
# Pinning-revision restart
A template whose pins arrive across different fixpoint rounds must be served by
one instantiation at the union of those pins, not split into half-pinned calls.
When a late pin would extend an existing instance's pinning, reviseOrSplit
commits the union and the solve restarts: it signals with the errRestart
sentinel, which rides the *diag.Error return channel up through
instantiateTemplate → viableSingle/tryWorklist/tryJoint → solveDemands → run,
compared by identity at each layer, until Set replays run on a fresh solver
with the accumulated commitments in force. Resolution is therefore a pure
function of (providers, commitments). A replay is the ordinary fixpoint, not a
mutation of the aborted attempt, and maxRestarts bounds the loop. See
revision.go.
# Solver memo state
Every memo table on solver is rebuilt fresh each attempt EXCEPT commitments and
refusedUnions, which persist across restarts (a committed union must stay in
force; a union already found non-viable must not re-trigger). The per-field roles
are documented on the solver struct.
Package-Level Type Names (total 13, in which 9 are exported)
/* sort exporteds by: | */
ArgRef records how one provider input is satisfied.CoerceCoerce // the type whose local holds the value // satisfied by an injector parameter
DestInfo describes the destination package as far as solve's collision checks
and emit's qualification decisions need it. gen builds it; solve and emit
(which imports solve) consume it. Imports maps each import qualifier used in the destination's existing files
to the base name of a file using it. A generated function name (package
block) cannot coexist with an import qualifier (file block) of the same
name in any one file, so this feeds the set-name collision check. // top-level identifier → base filename declaring it // the destination's package name // the destination is one of the scanned packages ReservedNames returns the base set of identifiers a name generated for this
destination must avoid: every predeclared universe name and every destination
top-level identifier (so, in same-package mode, a generated name never shadows
a builtin the body emits or a provider the body calls unqualified). The
receiver may be nil. Callers that reserve further categories (lifted-parameter
names, import aliases, set names) add them to the returned map.
func go.pact.im/x/plumb/internal/gen.buildDestInfo(opts gen.Options, pkgs []*discover.Package) (*DestInfo, *diag.Error)
func Set(name string, provs []*discover.Provider, destPath, outputBase string, dest *DestInfo, ctxt *types.Context) (*Plan, *diag.Error)
func go.pact.im/x/plumb/internal/emit.File(importPath, packageName string, pkgs []*discover.Package, plans []*Plan, dest *DestInfo) *emit.Result
func go.pact.im/x/plumb/internal/emit.assignAliases(pkgs []*types.Package, needErrors bool, destPath string, lifted, setNames map[string]bool, dest *DestInfo) (map[string]string, string, []string)
func go.pact.im/x/plumb/internal/emit.newAllocator(dest *DestInfo, q *emit.qualifier, lifted map[string]bool) *emit.allocator
func go.pact.im/x/plumb/internal/emit.recordPlanPackages(pl *Plan, q *emit.qualifier, dest *DestInfo, lifted map[string]bool)
func go.pact.im/x/plumb/internal/emit.renderPlan(pl *Plan, q *emit.qualifier, errorsAlias string, dest *DestInfo, lifted map[string]bool) string
func go.pact.im/x/plumb/internal/emit.reservedIdents(dest *DestInfo, lifted map[string]bool) map[string]bool
Input is one resolved injector parameter: its type and the source-name hint
(the earliest consumer's parameter or field name, or "") used to name the
generated parameter.NamestringTypetypes.Type
InputSlot is one consumed type, in call order. Name is the source parameter/field name, a hint for naming an injector
input. For a struct-type provider it is the exported field name, which emit
renders into the composite literal. // the struct type (value form) for a flexible receiver: a *Named, or a *Alias when the field is on an alias to an anonymous struct flexRecv marks the implicit receiver of a struct-field provider, whose
value-vs-pointer form is chosen during solving from the rest of the set.typtypes.Type
Instance is a concrete (fully instantiated) provider ready for wiring: pure
data describing what to call with what. emit turns it into Go source. For a
non-generic provider there is exactly one; for a template there is one per
pinning. // consumed types, in call order // the source provider this instantiates // classified results, in result order // type arguments, aligned with Prov.Tparams; nil if concretepostoken.Position InputTypes returns the instance's input types, in call order (for the report). valueOuts returns the value-output types of the instance, in result order.
func instantiate(p *discover.Provider, ctxt *types.Context, targs []types.Type) (in *Instance, err *diag.Error, miss error)
func ambiguousProducers(a, b *Instance, t types.Type) *diag.Error
func classifyParams(in *Instance, sig *types.Signature, start int)
func classifyResults(p *discover.Provider, in *Instance, sig *types.Signature) *diag.Error
func go.pact.im/x/plumb/internal/emit.instanceFallible(in *Instance) bool
func go.pact.im/x/plumb/internal/emit.joinResults(in *Instance, q func(types.Type) string) string
func go.pact.im/x/plumb/internal/emit.renderInstance(in *Instance, q *emit.qualifier, args []string) string
Plan is the fully resolved wiring for one set, ready for emission. // some provider yields a cleanup, so the injector returns an aggregated cleanup func // each instance's inputs, one ArgRef per input in order // some cleanup can itself fail, so the aggregated cleanup returns an error // some provider returns an error, so the injector returns a trailing error // injector parameters, in signature order // free type parameters carried onto the generic injector header // the set name; the generated injector func is named this // instances to emit, in dependency (topological) order // value outputs, in signature order // source providers in the set, for the -v report; not len(Order), the instantiation count // the set's source position, for diagnostics InputTypes returns the injector parameter types in signature order.
func Set(name string, provs []*discover.Provider, destPath, outputBase string, dest *DestInfo, ctxt *types.Context) (*Plan, *diag.Error)
func go.pact.im/x/plumb/internal/emit.File(importPath, packageName string, pkgs []*discover.Package, plans []*Plan, dest *DestInfo) *emit.Result
func go.pact.im/x/plumb/internal/emit.buildReport(importPath string, pkgs []*discover.Package, plans []*Plan, importLines []string, aliasByPath map[string]string) string
func go.pact.im/x/plumb/internal/emit.injectorOut(pl *Plan, q func(types.Type) string) string
func go.pact.im/x/plumb/internal/emit.liftedNamesOf(plans []*Plan) map[string]bool
func go.pact.im/x/plumb/internal/emit.planNeedsErrors(pl *Plan) bool
func go.pact.im/x/plumb/internal/emit.recordPlanPackages(pl *Plan, q *emit.qualifier, dest *DestInfo, lifted map[string]bool)
func go.pact.im/x/plumb/internal/emit.renderAggregate(acquired []emit.cleanupRef, pl *Plan, errorsAlias string, alloc *emit.allocator) string
func go.pact.im/x/plumb/internal/emit.renderPlan(pl *Plan, q *emit.qualifier, errorsAlias string, dest *DestInfo, lifted map[string]bool) string
func go.pact.im/x/plumb/internal/emit.renderUnwind(acquired []emit.cleanupRef, errLocal string, pl *Plan, errorsAlias string, alloc *emit.allocator) (stmts []string, errExpr string)
ResultSlot is one classified entry of a provider's result tuple. // ResultKindCleanup: true if func() errorKindResultKind // ResultKindValue: the value type; otherwise nil
injectorInput is a deduplicated injector parameter: its type, the source-name
hint and position of its earliest consumer (for ordering and naming).namestringpostoken.Positiontyptypes.Type
liftedParam records a lifted free type parameter and where it came from, for
deterministic header ordering. // index within its origin provider's type parameterspostoken.Positiontp*types.TypeParam
solver holds the mutable state of resolving one set.anchors[]*discover.Provider // identifiers lifted params must not equal commitments and refusedUnions persist across pinning-revision restarts;
every other field is rebuilt fresh each attempt. commitments hold, per
template and in commitment order, the union pinnings a replay merges into
compatible bindings at instantiation time; refusedUnions memoizes unions
found non-viable or lift-entangled, so a refused merge never re-triggers. // concrete providers, instantiated during classificationctxt*types.Context // demanded types, by type identitydest*DestInfodestPathstring // provider → set of instantiated type-arg lists (keyed by ListKey)instCountintinstances[]*Instance // provider → set of joint-cluster pinnings (keyed by jointBindKey)liftedMeta[]liftedParamliftedNamesmap[string]boolnamestring // template → the type-checker error a structural match failed to instantiate with // provider → set of bindings that failed to instantiate (constraint near-miss)outputBasestring pinnings records each template instance's pinned slots as instantiated
(after commitment merging), so the revision trigger can tell an
instance's own lifted slot from a pin that merely mentions another
instance's lifted parameter.provs[]*discover.ProviderrefusedUnionsmap[*discover.Provider]*gotypes.Set[*types.Tuple]resGen[]*discover.ProviderresGenUsedmap[*discover.Provider]bool // provider → memoized own-params skeleton // type → producing instance (exact), by type identity addInstance registers an instance's outputs and queues its inputs. applyCommitment merges bind with the first committed pinning of p it is
compatible with, so a replay instantiates the union where the aborted run
would have split. A binding incompatible with every commitment (a distinct
instantiation of the same template) passes through unchanged. bindInstantiates reports whether p cleanly instantiates at the given partial
binding, lifting the unpinned parameters to test, then rolling those lifts back
so the probe leaves no trace. It is used to decide whether merging two demands'
pins into one cluster would poison it (a near-miss on one pin failing the whole
instantiation). Both a constraint near-miss and a hard instantiation error count
as “does not cleanly instantiate”; the real instantiation in tryJoint surfaces
either for the pin's own cluster.(*solver) checkInputType(in *Instance, t types.Type) *diag.Error checkReachability enforces that everything the generated code must name across
a package boundary is exported and reachable. checkReservedAndCollision enforces the reserved-name and set-name collision
rules. checkTemplateAmbiguity reports an error if a still-unsatisfied demand can be
produced (cleanly, under each one's constraint) by two or more distinct
templates. Such a demand has no single producer, and picking one by source
order would be arbitrary. When no template's result matches the demand's exact
form, the same test runs against its dual, and the rejection names the
produced dual form. A template that merely lifts around a demand another
provider already supplies is not in tension here: a supplied demand is no longer
pending, so it never reaches this check. This is the joint-path analog of the
worklist's single-result ambiguity test. checkUnusedTemplates rejects result-generic templates that nothing pinned. classify splits providers into concrete / anchor / result-generic, validating
each provider's result shape in source-position order (so the first invalid
provider by position is the one reported, regardless of class) and rejecting
bare type-parameter results. coversPinnable reports whether bind pins every parameter that appears in a
value result. cycleError finds a dependency cycle among the not-yet-emitted instances and
reports it as a path, naming the type each provider needs from the next. edgeType returns the type consumer declares as the input that producer supplies
(the dependency that makes consumer follow producer) and whether that input is
satisfied through the value/pointer bridge. It reports the consumer's demanded
type (its InputSlot), not the producer's SrcType, so the diagnostic names the
type as written in the source; on a bridged edge the two are duals. The type is
nil if no such edge exists (defensive; cycle edges always have one). finalize resolves every input, classifies the signature, runs reachability
and reserved-name checks, orders the instances, and builds the plan. freshLiftedName returns a valid identifier derived from base that collides
with nothing the body references unqualified. instanceDone reports whether p has already been instantiated at this exact
list of type arguments. Concrete arguments make the key stable across passes;
freshly lifted parameters are distinct objects, so a lifted instantiation is
never deduplicated here (the worklist and joint-cluster passes gate those). instantiateTemplate instantiates p at the given partial binding, lifting any
unpinned parameters, and registers the resulting instance. A binding that a
prior pass found to violate the template's constraint (a near-miss) is skipped:
it would lift, fail, and roll back again on every fixpoint pass, leaking a
phantom lifted parameter and inflating the instantiation count each time.
Lifts mutate shared solver state (liftedMeta records the generated header's
type-parameter list), so a binding that does not yield a retained instance
must undo the lifts it speculatively appended; otherwise the failed attempt's
parameters survive into the signature. jointClusterDone reports whether p has already been instantiated for a cluster
with these pinned bindings. A cluster is identified by its pinned bindings
alone: parameters it leaves to be freshly lifted would otherwise vary the
instance identity every pass, so the unpinned slots are filled with a
sentinel that is identical only to itself. jointClusters groups the still-unsatisfied demands that pin p's results into
consistent bindings: one per distinct pinning. Each demand contributes the
binding it forces on p's result parameters; demands whose bindings agree merge
into one cluster, and conflicting ones form separate clusters, each a distinct
instantiation. Clusters are built in deterministic demand order, and a
parameter no demand pins is left for instantiateTemplate to lift. Seeding from
the unsatisfied demands (not every demand) keeps a type an existing instance
already supplies from driving a redundant (and possibly conflicting)
instantiation as later demands arrive across fixpoint passes. liftAll lifts every type parameter of an anchor template, returning the fresh
parameters as type arguments. liftOne creates a fresh injector type parameter standing in for an unpinned
template parameter. Its name is chosen to avoid the destination's identifiers
and the predeclared names the body emits, so it never shadows anything the
generated body references unqualified.(*solver) markInstanceDone(p *discover.Provider, targs []types.Type)(*solver) markJointClusterDone(p *discover.Provider, bind map[*types.TypeParam]types.Type)(*solver) markNearMissDone(p *discover.Provider, bind map[*types.TypeParam]types.Type)(*solver) markUnionRefused(p *discover.Provider, union map[*types.TypeParam]types.Type) nearMissDoneAt reports whether binding (p, bind) was already found to violate
p's constraint. The key matches jointClusterDone's: pinned slots by type,
unpinned slots by the jointUnpinned sentinel, so it is stable across the
freshly lifted parameters an unpinned slot takes on each pass. orderedLifted returns the lifted parameters in deterministic header order:
by the source position of their origin template, then parameter index. pendingDemands returns the still-unsatisfied demanded types, sorted. resolveFlexReceivers fixes each struct-field provider's receiver to value or
pointer form, preferring the pointer when the set uses it elsewhere. resolveInput maps one provider input to its source: an exact producer, a
value/pointer-bridged producer, or an injector parameter. reviseOrSplit is the pinning-revision trigger: about to instantiate p at
bind, it looks for an existing instance of p whose pinning is compatible
with bind and would gain pins from it. When their union is viable and
mentions no lifted parameter, the union becomes a committed pinning and the
solve restarts to replay with it in force; otherwise the union is refused
once (recorded so it never re-triggers) and the split stands, the same
result the resolver reaches when no revision applies. Pinnings that conflict
on a shared slot are ordinary multi-instantiation and never trigger. rollbackLifts undoes the speculative lifts appended since mark: it both
truncates liftedMeta and releases the names those lifts reserved in liftedNames.
Releasing the names is what lets a later successful lift reuse a name a failed
attempt had taken (e.g. T rather than T2); liftedMeta and liftedNames must be
rolled back together or the two go out of sync.(*solver) run() (*Plan, *diag.Error) satisfied reports whether demand t is met by an exact or value/pointer-dual
supply. seed registers every concrete instance and instantiates every anchor template,
registering their outputs and queuing their inputs. setLiftedConstraints rewrites the constraint of each freshly lifted parameter
(the indices in fresh) so that references to the template's other parameters
point at their resolved targets: concrete types for pinned parameters, the
co-lifted fresh parameters for the rest. The original template parameters are
not in scope in the generated header, so an inter-parameter constraint such as
U interface{ ~[]T } must travel as ~[]<pin or lifted T>, not the verbatim T.
Leaving it verbatim renders a dangling identifier (uncompilable) and makes
types.Instantiate's validation reject the lift.(*solver) setPos() token.Position shadowedDemand explains the common reason a template goes unused: a consumer
does pin its result, but another provider already supplies that type, so the
demand was met before the template could run. It returns the shadowed demand,
its producer, and the dual type that producer supplies when the value/pointer
bridge (not an exact producer) is what preempted the demand, and nil when the
producer supplies the demand directly. in is nil when no such producer exists
(the result is genuinely unconsumed). Demands are scanned in CmpType order so
the named producer is deterministic. skeleton returns p instantiated at its own type parameters, computed once per
provider and reused across the fixpoint. The skeleton is a pure function of the
provider, and classify already proved it instantiates (panicking otherwise), so
no error or near-miss can occur here; callers only read it (valueOuts). solveDemands runs the demand-driven instantiation fixpoint. Each successful
instantiation restarts the scan, and pendingDemands re-derives the whole demand
set each pass, so this is O(V²) in the instantiation count, bounded by
maxInstantiations and sub-millisecond for real sets (tens of instances), as with
topoOrder; a rewrite to incremental tracking would be premature. templateServes reports whether template p could produce demand d, directly or
through the value/pointer bridge by producing d's dual, mirroring tryWorklist's
dual retry. It is the same unify+coversPinnable test viableSingle uses, so a hit
means the template really would have been a candidate for d. topoOrder returns the instances in dependency order: every producer precedes
the consumers of its outputs. Ties are broken by source position so the order
is deterministic and independent of how packages and files were read. A cycle
is reported as a located path.
The selection loop below is O(V²·arity): each emitted node rescans all
instances and re-checks their dependency sets. That is quadratic, but V is
bounded by maxInstantiations and tiny for real sets (tens of instances; a
deepening generic template trips maxTypeDepth long before it manufactures
thousands of instances), so ordering stays sub-millisecond in practice. Kahn's
O(V+E) would be premature. The rescan also gives the deterministic
position-ordered pick and feeds the cycle report for free. tryJoint instantiates result-generic templates from the demands that are still
unserved. The pending demands are clustered into one binding per distinct
pinning, and the template is instantiated once per cluster, lifting the
parameters no demand pins, so one template may serve several consumers at
different types (the multi-result analog of pinning a single-result template at
several types).
This is the general path; the worklist is the fast case ahead of it. The
worklist first handles every demand that a single result pins completely.
Whatever it cannot fully pin from one demand (a demand that matches only a
partial result while another result mentions more parameters, or pinnings that
arrive jointly across disjoint results) falls to clustering here, where the
unpinned parameters are lifted. Dispatch is therefore driven by what the
demands actually pin, not by whether some result structurally covers every
parameter: a template whose all-covering result is never the one demanded is
still served, by lifting the parameters its demanded (partial) result leaves free.
Two clusters never produce the same output type, because every distinct pin for
a parameter lands in exactly one cluster, and clusters seed only from still-
unsatisfied demands, so a type an existing instance already supplies never
drives a second instantiation; a genuine duplicate across providers still
surfaces downstream as an ambiguous producer. tryWorklist attempts to satisfy demand d by instantiating a simple or
monadic result-generic template fully pinned by a single demand.(*solver) unionRefused(p *discover.Provider, union map[*types.TypeParam]types.Type) bool viableProducers returns the distinct templates with some value result that
unifies with d in a viable binding, and whether any result of any template
unified at all, viable or not: the caller falls through to the dual only
when d's exact form matched nothing. viableSingle returns, per template, the first value result (in result order)
that unifies with d in a covering, viable binding, so a template contributes
at most one candidate and ambiguity is decided among distinct templates. A
covering result whose binding fails the template's constraint is recorded as a
near-miss before the scan moves on: if the template ends the solve unused, the
actionable reason is its constraint rejecting the pin, not the rival that
served the demand.
Package-Level Functions (total 21, in which 1 is exported)
Set resolves one set into a plan. destPath is the destination import path
(what is referenced unqualified) and outputBase is the base name of the file
being overwritten, used by the set-name collision check (empty for stdout).
bridgeDir returns the coercion that turns a value into the want form, where the
available value and want are a value/pointer dual; the direction is fixed by want.
want is unaliased first so an alias to a pointer (type P = *T) is recognized as
the pointer form, matching pointerElem/DualType, which decide the dual the same way.
classifyParams appends the signature parameters (starting at index start) as
inputs. A variadic parameter needs nothing special here: the tuple already
types it as its slice ([]T), and the call-site spread (x...) is emit's job.
classifyResults classifies the signature results into value/cleanup/error
slots, enforcing the at-most-one-error rule.
clusterCands collects one candidate binding per value result the demand
unifies with, in result order, counting only bindings that pin at least one
parameter. A demand pins at most one result slot: merging its per-slot
bindings into one would, when two slots share an outer generic type (A[T] and
A[U] both matched by A[int]), pin both parameters from a single demand and
make the template produce that type twice, a spurious self-collision. matched
reports whether any result unified at all: a parameter-free result yields no
binding but still means the exact form matched, so the caller must not fall
through to the dual.
compatibleBind reports whether two partial bindings agree on every type
parameter they share, so they describe one instantiation rather than two.
crossBoundarySymbol returns the named symbol a provider renders across the
boundary, or nil if the provider renders no qualified symbol of its own.
fieldTypes returns the field's type and the (possibly instantiated) receiver
type for a field provider: a *types.Named, or a *types.Alias when the field is
on an alias to an anonymous struct. A non-nil error is the constraint near-miss
reason.
findUnreachable walks t and returns the first named type from a non-destination
package that is unexported (and therefore cannot be named from the destination).
The offending type lives in the returned TypeName's own package, so diagnostics
name obj.Pkg().Path(), not destPath, the one package it is guaranteed not in.
Its Pkg() is never nil: the match requires a non-nil package below.
instantiate produces the concrete instance of p for the given type arguments
(empty for a concrete provider): it classifies the provider's inputs and
results, with no rendering: emit decides how each kind is written. It returns
a located error (*diag.Error) for user-visible problems (e.g. multiple error
results), and a non-nil miss error (the type-checker's own *types.ArgumentError)
when the instantiation is infeasible because targs violate a type-parameter
constraint, which the solver treats as a near-miss non-match. Both nil means a
matched instance. The per-kind helpers panic on a post-instantiation structural
failure (a method/field that vanished), which is unreachable for valid input.
instSignature instantiates a generic signature, or returns it unchanged when
there are no type args. A non-nil error is the constraint near-miss reason.
methodSignature returns the receiver input type and the (possibly
instantiated) signature of a method provider, whether the receiver is a
concrete named type or an interface. For a concrete receiver the input is the
method's own (value- or pointer-) receiver type; for an interface it is the
interface named type itself, instantiated at targs. A non-nil error is the
constraint near-miss reason.
ownParams returns the provider's own type parameters as type arguments (used
to build the generic skeleton).
structFields returns the declared type to render (the directive's named or
alias type, instantiated at targs) and its exported struct fields. The alias is
kept as written so the composite literal names it (and an exported alias to an
unexported struct stays reachable across packages); the fields come from its
flattened underlying struct. A non-nil error is the constraint near-miss reason.
Package-Level Variables (total 2, neither is exported)
errRestart signals, up through the fixpoint to Set, that a new pinning was
committed and resolution must replay from seed with it in force. It rides the
*diag.Error return channel but is a control-flow signal, not a diagnostic:
every layer compares it by identity (err == errRestart) and Set consumes it
before it can escape. It is a diag.Sentinel rather than a zero-value *Error so
that a stray format of an in-flight error prints the message instead of
nil-panicking.
jointUnpinned is a unique sentinel type standing for a cluster parameter no
demand pinned. Being a fresh named type, it is identical only to itself, so it
never coincides with a real pinned type.
Package-Level Constants (total 9, in which 6 are exported)
Coercion directions for the value/pointer bridge.
Coercion directions for the value/pointer bridge.
Coercion directions for the value/pointer bridge.
The result kinds for classifying entries of a provider’s result tuple.
The result kinds for classifying entries of a provider’s result tuple.
The result kinds for classifying entries of a provider’s result tuple.
Bounds that protect against adversarial input. They are far above any real
program and exist only so a non-terminating generic solve fails loudly rather
than looping.
maxRestarts bounds pinning-revision restarts. Each restart commits a pinning
strictly larger than an existing instance's (or is preempted by a one-shot
refusal), so the bound is far above any real program.
Bounds that protect against adversarial input. They are far above any real
program and exist only so a non-terminating generic solve fails loudly rather
than looping.
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)