@ -22,13 +22,11 @@ import (
"os"
"os"
"strings"
"strings"
"golang.org/x/tools/go/packages"
"tailscale.com/util/codegen"
"tailscale.com/util/codegen"
)
)
var (
var (
flagTypes = flag . String ( "type" , "" , "comma-separated list of types; required" )
flagTypes = flag . String ( "type" , "" , "comma-separated list of types; required" )
flagOutput = flag . String ( "output" , "" , "output file; required" )
flagBuildTags = flag . String ( "tags" , "" , "compiler build tags to apply" )
flagBuildTags = flag . String ( "tags" , "" , "compiler build tags to apply" )
flagCloneFunc = flag . Bool ( "clonefunc" , false , "add a top-level Clone func" )
flagCloneFunc = flag . Bool ( "clonefunc" , false , "add a top-level Clone func" )
)
)
@ -43,30 +41,18 @@ func main() {
}
}
typeNames := strings . Split ( * flagTypes , "," )
typeNames := strings . Split ( * flagTypes , "," )
cfg := & packages . Config {
pkg , namedTypes , err := codegen . LoadTypes ( * flagBuildTags , "." )
Mode : packages . NeedTypes | packages . NeedTypesInfo | packages . NeedSyntax | packages . NeedName ,
Tests : false ,
}
if * flagBuildTags != "" {
cfg . BuildFlags = [ ] string { "-tags=" + * flagBuildTags }
}
pkgs , err := packages . Load ( cfg , "." )
if err != nil {
if err != nil {
log . Fatal ( err )
log . Fatal ( err )
}
}
if len ( pkgs ) != 1 {
it := codegen . NewImportTracker ( pkg . Types )
log . Fatalf ( "wrong number of packages: %d" , len ( pkgs ) )
}
pkg := pkgs [ 0 ]
buf := new ( bytes . Buffer )
buf := new ( bytes . Buffer )
imports := make ( map [ string ] struct { } )
namedTypes := codegen . NamedTypes ( pkg )
for _ , typeName := range typeNames {
for _ , typeName := range typeNames {
typ , ok := namedTypes [ typeName ]
typ , ok := namedTypes [ typeName ]
if ! ok {
if ! ok {
log . Fatalf ( "could not find type %s" , typeName )
log . Fatalf ( "could not find type %s" , typeName )
}
}
gen ( buf , i mpor ts , typ , pkg . Types )
gen ( buf , i t, typ )
}
}
w := func ( format string , args ... any ) {
w := func ( format string , args ... any ) {
@ -93,62 +79,13 @@ func main() {
w ( " return false" )
w ( " return false" )
w ( "}" )
w ( "}" )
}
}
cloneOutput := pkg . Name + "_clone.go"
contents := new ( bytes . Buffer )
if err := codegen . WritePackageFile ( "tailscale.com/cmd/cloner" , pkg , cloneOutput , it , buf ) ; err != nil {
var flagArgs [ ] string
if * flagTypes != "" {
flagArgs = append ( flagArgs , "-type=" + * flagTypes )
}
if * flagOutput != "" {
flagArgs = append ( flagArgs , "-output=" + * flagOutput )
}
if * flagBuildTags != "" {
flagArgs = append ( flagArgs , "-tags=" + * flagBuildTags )
}
if * flagCloneFunc {
flagArgs = append ( flagArgs , "-clonefunc" )
}
fmt . Fprintf ( contents , header , strings . Join ( flagArgs , " " ) , pkg . Name )
fmt . Fprintf ( contents , "import (\n" )
for s := range imports {
fmt . Fprintf ( contents , "\t%q\n" , s )
}
fmt . Fprintf ( contents , ")\n\n" )
contents . Write ( buf . Bytes ( ) )
output := * flagOutput
if output == "" {
flag . Usage ( )
os . Exit ( 2 )
}
if err := codegen . WriteFormatted ( contents . Bytes ( ) , output ) ; err != nil {
log . Fatal ( err )
log . Fatal ( err )
}
}
}
}
const header = ` // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
func gen ( buf * bytes . Buffer , it * codegen . ImportTracker , typ * types . Named ) {
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
//` + `go:generate` + ` go run tailscale.com/cmd/cloner %s
package % s
`
func gen ( buf * bytes . Buffer , imports map [ string ] struct { } , typ * types . Named , thisPkg * types . Package ) {
pkgQual := func ( pkg * types . Package ) string {
if thisPkg == pkg {
return ""
}
imports [ pkg . Path ( ) ] = struct { } { }
return pkg . Name ( )
}
importedName := func ( t types . Type ) string {
return types . TypeString ( t , pkgQual )
}
t , ok := typ . Underlying ( ) . ( * types . Struct )
t , ok := typ . Underlying ( ) . ( * types . Struct )
if ! ok {
if ! ok {
return
return
@ -169,11 +106,11 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
for i := 0 ; i < t . NumFields ( ) ; i ++ {
for i := 0 ; i < t . NumFields ( ) ; i ++ {
fname := t . Field ( i ) . Name ( )
fname := t . Field ( i ) . Name ( )
ft := t . Field ( i ) . Type ( )
ft := t . Field ( i ) . Type ( )
if ! codegen . ContainsPointers ( ft ) {
if ! codegen . ContainsPointers ( ft ) || codegen . HasNoClone ( t . Tag ( i ) ) {
continue
continue
}
}
if named , _ := ft . ( * types . Named ) ; named != nil {
if named , _ := ft . ( * types . Named ) ; named != nil {
if i sViewType( ft ) {
if codegen . I sViewType( ft ) {
writef ( "dst.%s = src.%s" , fname , fname )
writef ( "dst.%s = src.%s" , fname , fname )
continue
continue
}
}
@ -185,7 +122,7 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
switch ft := ft . Underlying ( ) . ( type ) {
switch ft := ft . Underlying ( ) . ( type ) {
case * types . Slice :
case * types . Slice :
if codegen . ContainsPointers ( ft . Elem ( ) ) {
if codegen . ContainsPointers ( ft . Elem ( ) ) {
n := import edName( ft . Elem ( ) )
n := it . Qualifi edName( ft . Elem ( ) )
writef ( "dst.%s = make([]%s, len(src.%s))" , fname , n , fname )
writef ( "dst.%s = make([]%s, len(src.%s))" , fname , n , fname )
writef ( "for i := range dst.%s {" , fname )
writef ( "for i := range dst.%s {" , fname )
if _ , isPtr := ft . Elem ( ) . ( * types . Pointer ) ; isPtr {
if _ , isPtr := ft . Elem ( ) . ( * types . Pointer ) ; isPtr {
@ -202,7 +139,7 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
writef ( "dst.%s = src.%s.Clone()" , fname , fname )
writef ( "dst.%s = src.%s.Clone()" , fname , fname )
continue
continue
}
}
n := import edName( ft . Elem ( ) )
n := it . Qualifi edName( ft . Elem ( ) )
writef ( "if dst.%s != nil {" , fname )
writef ( "if dst.%s != nil {" , fname )
writef ( "\tdst.%s = new(%s)" , fname , n )
writef ( "\tdst.%s = new(%s)" , fname , n )
writef ( "\t*dst.%s = *src.%s" , fname , fname )
writef ( "\t*dst.%s = *src.%s" , fname , fname )
@ -212,9 +149,9 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
writef ( "}" )
writef ( "}" )
case * types . Map :
case * types . Map :
writef ( "if dst.%s != nil {" , fname )
writef ( "if dst.%s != nil {" , fname )
writef ( "\tdst.%s = map[%s]%s{}" , fname , importedName ( ft . Key ( ) ) , import edName( ft . Elem ( ) ) )
writef ( "\tdst.%s = map[%s]%s{}" , fname , it . QualifiedName ( ft . Key ( ) ) , it . Qualifi edName( ft . Elem ( ) ) )
if sliceType , isSlice := ft . Elem ( ) . ( * types . Slice ) ; isSlice {
if sliceType , isSlice := ft . Elem ( ) . ( * types . Slice ) ; isSlice {
n := import edName( sliceType . Elem ( ) )
n := it . Qualifi edName( sliceType . Elem ( ) )
writef ( "\tfor k := range src.%s {" , fname )
writef ( "\tfor k := range src.%s {" , fname )
// use zero-length slice instead of nil to ensure
// use zero-length slice instead of nil to ensure
// the key is always copied.
// the key is always copied.
@ -237,20 +174,10 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, typ *types.Named, thisP
writef ( "return dst" )
writef ( "return dst" )
fmt . Fprintf ( buf , "}\n\n" )
fmt . Fprintf ( buf , "}\n\n" )
buf . Write ( codegen . AssertStructUnchanged ( t , thisPkg , name , "Clone" , imports ) )
buf . Write ( codegen . AssertStructUnchanged ( t , name , "Clone" , it ) )
}
func isViewType ( typ types . Type ) bool {
t , ok := typ . Underlying ( ) . ( * types . Struct )
if ! ok {
return false
}
if t . NumFields ( ) != 1 {
return false
}
return t . Field ( 0 ) . Name ( ) == "ж"
}
}
// hasBasicUnderlying reports true when typ.Underlying() is a slice or a map.
func hasBasicUnderlying ( typ types . Type ) bool {
func hasBasicUnderlying ( typ types . Type ) bool {
switch typ . Underlying ( ) . ( type ) {
switch typ . Underlying ( ) . ( type ) {
case * types . Slice , * types . Map :
case * types . Slice , * types . Map :