vendor: update buildkit to f449174742bf
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
module github.com/docker/buildx
|
module github.com/docker/buildx
|
||||||
|
|
||||||
go 1.25.5
|
go 1.25.9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.4.0
|
github.com/Masterminds/semver/v3 v3.4.0
|
||||||
@@ -10,10 +10,10 @@ require (
|
|||||||
github.com/compose-spec/compose-go/v2 v2.10.2
|
github.com/compose-spec/compose-go/v2 v2.10.2
|
||||||
github.com/containerd/console v1.0.5
|
github.com/containerd/console v1.0.5
|
||||||
github.com/containerd/containerd/v2 v2.2.4
|
github.com/containerd/containerd/v2 v2.2.4
|
||||||
github.com/containerd/continuity v0.4.5
|
github.com/containerd/continuity v0.5.0
|
||||||
github.com/containerd/errdefs v1.0.0
|
github.com/containerd/errdefs v1.0.0
|
||||||
github.com/containerd/log v0.1.0
|
github.com/containerd/log v0.1.0
|
||||||
github.com/containerd/platforms v1.0.0-rc.2
|
github.com/containerd/platforms v1.0.0-rc.4
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
@@ -30,7 +30,7 @@ require (
|
|||||||
github.com/hashicorp/hcl/v2 v2.24.0
|
github.com/hashicorp/hcl/v2 v2.24.0
|
||||||
github.com/in-toto/in-toto-golang v0.11.0
|
github.com/in-toto/in-toto-golang v0.11.0
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/moby/buildkit v0.30.0-rc2.0.20260610003221-41c29fffe299
|
github.com/moby/buildkit v0.30.0-rc2.0.20260610142556-f449174742bf
|
||||||
github.com/moby/go-archive v0.2.0
|
github.com/moby/go-archive v0.2.0
|
||||||
github.com/moby/moby/api v1.54.2
|
github.com/moby/moby/api v1.54.2
|
||||||
github.com/moby/moby/client v0.4.1
|
github.com/moby/moby/client v0.4.1
|
||||||
@@ -41,7 +41,7 @@ require (
|
|||||||
github.com/open-policy-agent/opa v1.10.1
|
github.com/open-policy-agent/opa v1.10.1
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.3.1
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||||
@@ -51,25 +51,25 @@ require (
|
|||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/pflag v1.0.10
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20260603212341-ed540e182f8a
|
github.com/tonistiigi/fsutil v0.0.0-20260609091201-0257b3308df4
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0
|
||||||
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117
|
||||||
github.com/zclconf/go-cty v1.17.0
|
github.com/zclconf/go-cty v1.17.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0
|
||||||
go.opentelemetry.io/otel v1.43.0
|
go.opentelemetry.io/otel v1.44.0
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0
|
||||||
go.opentelemetry.io/otel/metric v1.43.0
|
go.opentelemetry.io/otel/metric v1.44.0
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0
|
go.opentelemetry.io/otel/sdk v1.44.0
|
||||||
go.opentelemetry.io/otel/trace v1.43.0
|
go.opentelemetry.io/otel/trace v1.44.0
|
||||||
go.yaml.in/yaml/v3 v3.0.4
|
go.yaml.in/yaml/v3 v3.0.4
|
||||||
golang.org/x/crypto v0.52.0
|
golang.org/x/crypto v0.52.0
|
||||||
golang.org/x/mod v0.35.0
|
golang.org/x/mod v0.36.0
|
||||||
golang.org/x/sync v0.20.0
|
golang.org/x/sync v0.20.0
|
||||||
golang.org/x/sys v0.45.0
|
golang.org/x/sys v0.45.0
|
||||||
golang.org/x/term v0.43.0
|
golang.org/x/term v0.43.0
|
||||||
golang.org/x/text v0.37.0
|
golang.org/x/text v0.37.0
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa
|
||||||
google.golang.org/grpc v1.80.0
|
google.golang.org/grpc v1.81.1
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
k8s.io/api v0.35.4
|
k8s.io/api v0.35.4
|
||||||
@@ -148,7 +148,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/go-containerregistry v0.20.7 // indirect
|
github.com/google/go-containerregistry v0.20.7 // indirect
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hiddeco/sshsig v0.2.0 // indirect
|
github.com/hiddeco/sshsig v0.2.0 // indirect
|
||||||
github.com/in-toto/attestation v1.1.2 // indirect
|
github.com/in-toto/attestation v1.1.2 // indirect
|
||||||
@@ -212,22 +212,22 @@ require (
|
|||||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.68.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.69.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect
|
||||||
golang.org/x/net v0.55.0 // indirect
|
golang.org/x/net v0.55.0 // indirect
|
||||||
golang.org/x/oauth2 v0.36.0 // indirect
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
golang.org/x/time v0.15.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
golang.org/x/tools v0.44.0 // indirect
|
golang.org/x/tools v0.44.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXe
|
|||||||
github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=
|
github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=
|
||||||
github.com/containerd/containerd/v2 v2.2.4 h1:8x2UdXqww7NYqGNabQ7i1nAgB5LegzjC9KQzO/900iA=
|
github.com/containerd/containerd/v2 v2.2.4 h1:8x2UdXqww7NYqGNabQ7i1nAgB5LegzjC9KQzO/900iA=
|
||||||
github.com/containerd/containerd/v2 v2.2.4/go.mod h1:YBcTO8D9149QY9zNmUjy04Mhuc4DlrZQ8FIOwKZEM7o=
|
github.com/containerd/containerd/v2 v2.2.4/go.mod h1:YBcTO8D9149QY9zNmUjy04Mhuc4DlrZQ8FIOwKZEM7o=
|
||||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg=
|
||||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
@@ -132,10 +132,10 @@ github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY
|
|||||||
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
github.com/containerd/nydus-snapshotter v0.15.13 h1:z9yCiTPMxVBIZlHxOPinZXhly2MdcIqxk9VXPlHIOJY=
|
github.com/containerd/nydus-snapshotter v0.15.15 h1:kVYbFpYA4K43qxGVoc/VBwRXLAVWn4X9mdwGrR+HsLk=
|
||||||
github.com/containerd/nydus-snapshotter v0.15.13/go.mod h1:t95dwCb4I0RE4n1iOk0sJCWosNoACA8daOXmU5A2VHI=
|
github.com/containerd/nydus-snapshotter v0.15.15/go.mod h1:L96yO+4iE6qqDiqXKhxMXBoPeaE7JgzXir9yanUVuOY=
|
||||||
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
|
github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4=
|
||||||
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
|
github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
|
||||||
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
|
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
|
||||||
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
|
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
|
||||||
github.com/containerd/stargz-snapshotter v0.18.2 h1:Ev/sxfQUjwzJQ9eqy3XzttcQ3osMIqkQgMYlcET+10M=
|
github.com/containerd/stargz-snapshotter v0.18.2 h1:Ev/sxfQUjwzJQ9eqy3XzttcQ3osMIqkQgMYlcET+10M=
|
||||||
@@ -321,8 +321,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
|||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
@@ -415,8 +415,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4
|
|||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/moby/buildkit v0.30.0-rc2.0.20260610003221-41c29fffe299 h1:vDHdjaWsBBAgG3Y59MTsTxzxmGEwvmod1AG5tb8mzwk=
|
github.com/moby/buildkit v0.30.0-rc2.0.20260610142556-f449174742bf h1:9qGH4EXFQgmqJUMjRxyX0zxA5L2x7CJiNHPAksuaF+U=
|
||||||
github.com/moby/buildkit v0.30.0-rc2.0.20260610003221-41c29fffe299/go.mod h1:lWVt4gAu/Erz7leTk63nz+FbnZy8VPTgbFGWteapT60=
|
github.com/moby/buildkit v0.30.0-rc2.0.20260610142556-f449174742bf/go.mod h1:9NQbhPKN8TizS1PFglCTHotCdHXF3Vm36FR5t3fjJ38=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||||
@@ -471,13 +471,13 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
|||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
|
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
|
||||||
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/opencontainers/selinux v1.14.1 h1:a7XlXV/nN/l5zFP1FWZYoExpClu1QOPMfWUV2CZ8kEQ=
|
github.com/opencontainers/selinux v1.15.1 h1:ERxeh5caJvCzNAKdI8WQbJmB1LDTn4BuaAg8wihLBpA=
|
||||||
github.com/opencontainers/selinux v1.14.1/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ=
|
github.com/opencontainers/selinux v1.15.1/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ=
|
||||||
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
|
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
|
||||||
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
|
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
|
||||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -574,8 +574,8 @@ github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C
|
|||||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
|
||||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
|
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
|
||||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
|
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20260603212341-ed540e182f8a h1:fouZioB486i05IyehzLIrq4Jcz5LHQbMJsznx6ByJZo=
|
github.com/tonistiigi/fsutil v0.0.0-20260609091201-0257b3308df4 h1:tJkv/edHw9FXVtbHxc6cpqDttiCLNzhqI1W40fcnxIY=
|
||||||
github.com/tonistiigi/fsutil v0.0.0-20260603212341-ed540e182f8a/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
|
github.com/tonistiigi/fsutil v0.0.0-20260609091201-0257b3308df4/go.mod h1:K5zrLch9UaSGNiek5XHZeqZUf1zPWJHqDfLIcnpquQ4=
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
|
||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||||
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117 h1:XFwyh2JZwR5aiKLXHX2C1n0v5F11dCJpyGL1W/Cpl3U=
|
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117 h1:XFwyh2JZwR5aiKLXHX2C1n0v5F11dCJpyGL1W/Cpl3U=
|
||||||
@@ -590,8 +590,8 @@ github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG
|
|||||||
github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A=
|
github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
|
github.com/vbatts/tar-split v0.12.3 h1:Cd46rkGXI3Td4yrVNwU8ripbxFaQbmesqhjBUUYAJSw=
|
||||||
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
github.com/vbatts/tar-split v0.12.3/go.mod h1:sQOc6OlqGCr7HkGx/IDBeKiTIvqhmj8KffNhEXG4Nq0=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
|
github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
@@ -614,34 +614,36 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0 h1:2yEATaop1/a1I4psnSLgWVPLWwCzkqWakgJy7xTDVy0=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.69.0/go.mod h1:D7J12YRapIekYyPWgGPlA/23pRmpSEZC5xJC/TTLI9U=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.68.0 h1:cuXaPAfIoJKsYjBjPSb2nKZEmgM43zVr25l37IxhKME=
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.69.0 h1:MCcYL7J6Vt/X0kjqbMZkekCmwsurbQRbL69vkiye2lk=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.68.0/go.mod h1:BuzhPofpCzlDi/Q/Xjg54M4/3oWqqyDe2Zeq7A2I0QE=
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.69.0/go.mod h1:3jnStNwSufK+f5ktjL4EPcwtig4rtd81NS70lqHuXl8=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
|
||||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUplec5dp06reu1zaXmOXdvqH398taqrDXqUl99jxSc=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
|
||||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||||
go.step.sm/crypto v0.77.2 h1:qFjjei+RHc5kP5R7NW9OUWT7SqWIuAOvOkXqg4fNWj8=
|
go.step.sm/crypto v0.77.2 h1:qFjjei+RHc5kP5R7NW9OUWT7SqWIuAOvOkXqg4fNWj8=
|
||||||
@@ -652,18 +654,18 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
|
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q=
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
@@ -688,12 +690,12 @@ google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
|
|||||||
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
||||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
||||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
+1
-1
@@ -216,7 +216,7 @@ func (c *context) verifyMetadata(resource, target Resource) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if target.GID() != resource.GID() {
|
if target.GID() != resource.GID() {
|
||||||
return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
|
return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), resource.GID())
|
||||||
}
|
}
|
||||||
|
|
||||||
if xattrer, ok := resource.(XAttrer); ok {
|
if xattrer, ok := resource.(XAttrer); ok {
|
||||||
|
|||||||
+2
-2
@@ -38,14 +38,14 @@ func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mknod provides a shortcut for syscall.Mknod
|
// mknod provides a shortcut for syscall.Mknod
|
||||||
func Mknod(p string, mode os.FileMode, maj, min int) error {
|
func Mknod(p string, mode os.FileMode, major, minor int) error {
|
||||||
var (
|
var (
|
||||||
m = syscallMode(mode.Perm())
|
m = syscallMode(mode.Perm())
|
||||||
dev uint64
|
dev uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
if mode&os.ModeDevice != 0 {
|
if mode&os.ModeDevice != 0 {
|
||||||
dev = unix.Mkdev(uint32(maj), uint32(min))
|
dev = unix.Mkdev(uint32(major), uint32(minor))
|
||||||
|
|
||||||
if mode&os.ModeCharDevice != 0 {
|
if mode&os.ModeCharDevice != 0 {
|
||||||
m |= unix.S_IFCHR
|
m |= unix.S_IFCHR
|
||||||
|
|||||||
+1
-1
@@ -109,7 +109,7 @@ type LXAttrDriver interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DeviceInfoDriver interface {
|
type DeviceInfoDriver interface {
|
||||||
DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error)
|
DeviceInfo(fi os.FileInfo) (major uint64, minor uint64, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// driver is a simple default implementation that sends calls out to the "os"
|
// driver is a simple default implementation that sends calls out to the "os"
|
||||||
|
|||||||
+1
-1
@@ -128,6 +128,6 @@ func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
|
func (d *driver) DeviceInfo(fi os.FileInfo) (major uint64, minor uint64, err error) {
|
||||||
return devices.DeviceInfo(fi)
|
return devices.DeviceInfo(fi)
|
||||||
}
|
}
|
||||||
|
|||||||
+116
@@ -26,6 +26,122 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxCopyChunk is the maximum size passed to copy_file_range per call,
|
||||||
|
// avoiding int overflow on 32-bit architectures.
|
||||||
|
const maxCopyChunk = 1 << 30 // 1 GiB
|
||||||
|
|
||||||
|
// copyFile copies a file from source to target preserving sparse file holes.
|
||||||
|
//
|
||||||
|
// If the filesystem does not support SEEK_DATA/SEEK_HOLE, it falls back
|
||||||
|
// to a plain io.Copy.
|
||||||
|
func copyFile(target, source string) error {
|
||||||
|
src, err := os.Open(source)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open source %s: %w", source, err)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
fi, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to stat source %s: %w", source, err)
|
||||||
|
}
|
||||||
|
size := fi.Size()
|
||||||
|
|
||||||
|
tgt, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open target %s: %w", target, err)
|
||||||
|
}
|
||||||
|
defer tgt.Close()
|
||||||
|
|
||||||
|
if err := tgt.Truncate(size); err != nil {
|
||||||
|
return fmt.Errorf("failed to truncate target %s: %w", target, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcFd := int(src.Fd())
|
||||||
|
|
||||||
|
// Try a SEEK_DATA to check if the filesystem supports it.
|
||||||
|
// If not, fall back to a plain copy.
|
||||||
|
if _, err := unix.Seek(srcFd, 0, unix.SEEK_DATA); err != nil {
|
||||||
|
// ENXIO means no data in the file at all. In other words it's entirely sparse.
|
||||||
|
// The truncated target is already correct.
|
||||||
|
if errors.Is(err, syscall.ENXIO) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, syscall.EOPNOTSUPP) || errors.Is(err, syscall.ENOTSUP) || errors.Is(err, syscall.EINVAL) {
|
||||||
|
// Filesystem doesn't support SEEK_DATA/SEEK_HOLE. Fall back to a plain copy.
|
||||||
|
src.Close()
|
||||||
|
tgt.Close()
|
||||||
|
return openAndCopyFile(target, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to seek data in source %s: %w", source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data regions from source to target, skipping holes.
|
||||||
|
var offset int64
|
||||||
|
tgtFd := int(tgt.Fd())
|
||||||
|
|
||||||
|
for offset < size {
|
||||||
|
dataStart, err := unix.Seek(srcFd, offset, unix.SEEK_DATA)
|
||||||
|
if err != nil {
|
||||||
|
// No more data past offset. Remainder of file is a hole.
|
||||||
|
if errors.Is(err, syscall.ENXIO) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return fmt.Errorf("SEEK_DATA failed at offset %d: %w", offset, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the end of this data region (start of next hole).
|
||||||
|
holeStart, err := unix.Seek(srcFd, dataStart, unix.SEEK_HOLE)
|
||||||
|
if err != nil {
|
||||||
|
// ENXIO shouldn't happen after a successful SEEK_DATA, but
|
||||||
|
// treat it as data extending to end of file.
|
||||||
|
if errors.Is(err, syscall.ENXIO) {
|
||||||
|
holeStart = size
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("SEEK_HOLE failed at offset %d: %w", dataStart, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the data region [dataStart, holeStart).
|
||||||
|
srcOff := dataStart
|
||||||
|
tgtOff := dataStart
|
||||||
|
remain := holeStart - dataStart
|
||||||
|
|
||||||
|
for remain > 0 {
|
||||||
|
chunk := remain
|
||||||
|
if chunk > maxCopyChunk {
|
||||||
|
chunk = maxCopyChunk
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := unix.CopyFileRange(srcFd, &srcOff, tgtFd, &tgtOff, int(chunk), 0)
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to a plain copy if copy_file_range is not supported
|
||||||
|
// across the source and target filesystems.
|
||||||
|
if errors.Is(err, syscall.EXDEV) || errors.Is(err, syscall.ENOSYS) || errors.Is(err, syscall.EOPNOTSUPP) {
|
||||||
|
src.Close()
|
||||||
|
tgt.Close()
|
||||||
|
return openAndCopyFile(target, source)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("copy_file_range failed: %w", err)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return fmt.Errorf("copy_file_range returned 0 with %d bytes remaining", remain)
|
||||||
|
}
|
||||||
|
remain -= int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = holeStart
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tgt.Sync(); err != nil {
|
||||||
|
return fmt.Errorf("failed to sync target %s: %w", target, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func copyFileInfo(fi os.FileInfo, src, name string) error {
|
func copyFileInfo(fi os.FileInfo, src, name string) error {
|
||||||
st := fi.Sys().(*syscall.Stat_t)
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
//go:build !darwin
|
//go:build !darwin && !linux
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|||||||
+2
-2
@@ -51,11 +51,11 @@ func overlayFSWhiteoutConvert(diffDir, path string, f os.FileInfo, changeFn Chan
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
maj, min, err := devices.DeviceInfo(f)
|
major, minor, err := devices.DeviceInfo(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return (maj == 0 && min == 0), nil
|
return (major == 0 && minor == 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
|
|||||||
+4
-2
@@ -20,11 +20,13 @@ package fs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containerd/continuity/sysx"
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// compareSysStat returns whether the stats are equivalent,
|
// compareSysStat returns whether the stats are equivalent,
|
||||||
@@ -45,11 +47,11 @@ func compareSysStat(s1, s2 interface{}) (bool, error) {
|
|||||||
|
|
||||||
func compareCapabilities(p1, p2 string) (bool, error) {
|
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||||
c1, err := sysx.LGetxattr(p1, "security.capability")
|
c1, err := sysx.LGetxattr(p1, "security.capability")
|
||||||
if err != nil && err != sysx.ENODATA {
|
if err != nil && !errors.Is(err, unix.ENOTSUP) && !errors.Is(err, sysx.ENODATA) {
|
||||||
return false, fmt.Errorf("failed to get xattr for %s: %w", p1, err)
|
return false, fmt.Errorf("failed to get xattr for %s: %w", p1, err)
|
||||||
}
|
}
|
||||||
c2, err := sysx.LGetxattr(p2, "security.capability")
|
c2, err := sysx.LGetxattr(p2, "security.capability")
|
||||||
if err != nil && err != sysx.ENODATA {
|
if err != nil && !errors.Is(err, unix.ENOTSUP) && !errors.Is(err, sysx.ENODATA) {
|
||||||
return false, fmt.Errorf("failed to get xattr for %s: %w", p2, err)
|
return false, fmt.Errorf("failed to get xattr for %s: %w", p2, err)
|
||||||
}
|
}
|
||||||
return bytes.Equal(c1, c2), nil
|
return bytes.Equal(c1, c2), nil
|
||||||
|
|||||||
+2
-2
@@ -111,9 +111,9 @@ func CreateDir(name string, perm os.FileMode) Applier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename returns a file applier which renames a file
|
// Rename returns a file applier which renames a file
|
||||||
func Rename(old, new string) Applier {
|
func Rename(oldpath, newpath string) Applier {
|
||||||
return applyFn(func(root string) error {
|
return applyFn(func(root string) error {
|
||||||
return os.Rename(filepath.Join(root, old), filepath.Join(root, new))
|
return os.Rename(filepath.Join(root, oldpath), filepath.Join(root, newpath))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -48,10 +48,10 @@ func Lchtimes(name string, atime, mtime time.Time) Applier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateDeviceFile provides creates devices Applier.
|
// CreateDeviceFile provides creates devices Applier.
|
||||||
func CreateDeviceFile(name string, mode os.FileMode, maj, min int) Applier {
|
func CreateDeviceFile(name string, mode os.FileMode, major, minor int) Applier {
|
||||||
return applyFn(func(root string) error {
|
return applyFn(func(root string) error {
|
||||||
fullPath := filepath.Join(root, name)
|
fullPath := filepath.Join(root, name)
|
||||||
return devices.Mknod(fullPath, mode, maj, min)
|
return devices.Mknod(fullPath, mode, major, minor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ func Lchtimes(name string, atime, mtime time.Time) Applier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateDeviceFile provides creates devices Applier.
|
// CreateDeviceFile provides creates devices Applier.
|
||||||
func CreateDeviceFile(name string, mode os.FileMode, maj, min int) Applier {
|
func CreateDeviceFile(name string, mode os.FileMode, major, minor int) Applier {
|
||||||
return applyFn(func(root string) error {
|
return applyFn(func(root string) error {
|
||||||
return errors.New("Not implemented")
|
return errors.New("Not implemented")
|
||||||
})
|
})
|
||||||
|
|||||||
+15
-22
@@ -1,32 +1,25 @@
|
|||||||
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
- gofmt
|
- dupword
|
||||||
- goimports
|
|
||||||
- gosec
|
- gosec
|
||||||
- ineffassign
|
|
||||||
- misspell
|
- misspell
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- revive
|
- revive
|
||||||
- staticcheck
|
|
||||||
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
|
||||||
- govet
|
|
||||||
- dupword # Checks for duplicate words in the source code
|
|
||||||
disable:
|
disable:
|
||||||
- errcheck
|
- errcheck
|
||||||
|
exclusions:
|
||||||
run:
|
generated: lax
|
||||||
timeout: 5m
|
presets:
|
||||||
|
- comments
|
||||||
issues:
|
- common-false-positives
|
||||||
exclude-dirs:
|
- legacy
|
||||||
- api
|
- std-error-handling
|
||||||
- cluster
|
formatters:
|
||||||
- design
|
enable:
|
||||||
- docs
|
- gofmt
|
||||||
- docs/man
|
- goimports
|
||||||
- releases
|
exclusions:
|
||||||
- reports
|
generated: lax
|
||||||
- test # e2e scripts
|
|
||||||
|
|||||||
+105
-1
@@ -152,6 +152,88 @@ func Only(platform specs.Platform) MatchComparer {
|
|||||||
return Ordered(platformVector(Normalize(platform))...)
|
return Ordered(platformVector(Normalize(platform))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnlyOS returns a match comparer that matches only platforms with the same
|
||||||
|
// OS, OS version, and OS features, regardless of architecture. When comparing,
|
||||||
|
// it always ranks the best architecture match highest using the default
|
||||||
|
// platform resolution logic.
|
||||||
|
func OnlyOS(platform specs.Platform) MatchComparer {
|
||||||
|
normalized := Normalize(platform)
|
||||||
|
return onlyOSComparer{
|
||||||
|
platform: normalized,
|
||||||
|
osvM: newOSVersionMatcher(normalized),
|
||||||
|
archOrder: orderedPlatformComparer{
|
||||||
|
matchers: []Matcher{NewMatcher(normalized)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOSVersionMatcher(platform specs.Platform) osVerMatcher {
|
||||||
|
if platform.OS == "windows" {
|
||||||
|
return &windowsVersionMatcher{
|
||||||
|
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type onlyOSComparer struct {
|
||||||
|
platform specs.Platform
|
||||||
|
osvM osVerMatcher
|
||||||
|
archOrder orderedPlatformComparer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c onlyOSComparer) matchOS(platform specs.Platform) bool {
|
||||||
|
normalized := Normalize(platform)
|
||||||
|
if c.platform.OS != normalized.OS {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c.osvM != nil {
|
||||||
|
if !c.osvM.Match(platform.OSVersion) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(normalized.OSFeatures) > 0 {
|
||||||
|
if len(c.platform.OSFeatures) < len(normalized.OSFeatures) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
j := 0
|
||||||
|
for _, feature := range normalized.OSFeatures {
|
||||||
|
found := false
|
||||||
|
for ; j < len(c.platform.OSFeatures); j++ {
|
||||||
|
if feature == c.platform.OSFeatures[j] {
|
||||||
|
found = true
|
||||||
|
j++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if feature < c.platform.OSFeatures[j] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c onlyOSComparer) Match(platform specs.Platform) bool {
|
||||||
|
return c.matchOS(platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c onlyOSComparer) Less(p1, p2 specs.Platform) bool {
|
||||||
|
p1m := c.matchOS(p1)
|
||||||
|
p2m := c.matchOS(p2)
|
||||||
|
if p1m && !p2m {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !p1m {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Both match — rank by architecture preference
|
||||||
|
return c.archOrder.Less(p1, p2)
|
||||||
|
}
|
||||||
|
|
||||||
// OnlyStrict returns a match comparer for a single platform.
|
// OnlyStrict returns a match comparer for a single platform.
|
||||||
//
|
//
|
||||||
// Unlike Only, OnlyStrict does not match sub platforms.
|
// Unlike Only, OnlyStrict does not match sub platforms.
|
||||||
@@ -213,9 +295,20 @@ func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if p1m || p2m {
|
if p1m || p2m {
|
||||||
|
if p1m && p2m {
|
||||||
|
// Prefer one with most matching features
|
||||||
|
if len(p1.OSFeatures) != len(p2.OSFeatures) {
|
||||||
|
return len(p1.OSFeatures) > len(p2.OSFeatures)
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0 {
|
||||||
|
p1.OSFeatures = nil
|
||||||
|
p2.OSFeatures = nil
|
||||||
|
return c.Less(p1, p2)
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,9 +335,20 @@ func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool {
|
|||||||
p2m = true
|
p2m = true
|
||||||
}
|
}
|
||||||
if p1m && p2m {
|
if p1m && p2m {
|
||||||
return false
|
if len(p1.OSFeatures) != len(p2.OSFeatures) {
|
||||||
|
return len(p1.OSFeatures) > len(p2.OSFeatures)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If neither match and has features, strip features and compare
|
||||||
|
if !p1m && !p2m && (len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0) {
|
||||||
|
p1.OSFeatures = nil
|
||||||
|
p2.OSFeatures = nil
|
||||||
|
return c.Less(p1, p2)
|
||||||
|
}
|
||||||
|
|
||||||
// If one matches, and the other does, sort match first
|
// If one matches, and the other does, sort match first
|
||||||
return p1m && !p2m
|
return p1m && !p2m
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -45,7 +45,6 @@ func getMachineArch() (string, error) {
|
|||||||
// So we don't need to access the ARM registers to detect platform information
|
// So we don't need to access the ARM registers to detect platform information
|
||||||
// by ourselves. We can just parse these information from /proc/cpuinfo
|
// by ourselves. We can just parse these information from /proc/cpuinfo
|
||||||
func getCPUInfo(pattern string) (info string, err error) {
|
func getCPUInfo(pattern string) (info string, err error) {
|
||||||
|
|
||||||
cpuinfo, err := os.Open("/proc/cpuinfo")
|
cpuinfo, err := os.Open("/proc/cpuinfo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -75,7 +74,6 @@ func getCPUInfo(pattern string) (info string, err error) {
|
|||||||
|
|
||||||
// getCPUVariantFromArch get CPU variant from arch through a system call
|
// getCPUVariantFromArch get CPU variant from arch through a system call
|
||||||
func getCPUVariantFromArch(arch string) (string, error) {
|
func getCPUVariantFromArch(arch string) (string, error) {
|
||||||
|
|
||||||
var variant string
|
var variant string
|
||||||
|
|
||||||
arch = strings.ToLower(arch)
|
arch = strings.ToLower(arch)
|
||||||
|
|||||||
+4
-4
@@ -24,10 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getCPUVariant() (string, error) {
|
func getCPUVariant() (string, error) {
|
||||||
|
|
||||||
var variant string
|
var variant string
|
||||||
|
|
||||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
switch runtime.GOOS {
|
||||||
|
case "windows", "darwin":
|
||||||
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
||||||
// runtime.GOARCH to determine the variants
|
// runtime.GOARCH to determine the variants
|
||||||
switch runtime.GOARCH {
|
switch runtime.GOARCH {
|
||||||
@@ -38,7 +38,7 @@ func getCPUVariant() (string, error) {
|
|||||||
default:
|
default:
|
||||||
variant = "unknown"
|
variant = "unknown"
|
||||||
}
|
}
|
||||||
} else if runtime.GOOS == "freebsd" {
|
case "freebsd":
|
||||||
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
||||||
// detecting those variants is currently unimplemented
|
// detecting those variants is currently unimplemented
|
||||||
switch runtime.GOARCH {
|
switch runtime.GOARCH {
|
||||||
@@ -47,7 +47,7 @@ func getCPUVariant() (string, error) {
|
|||||||
default:
|
default:
|
||||||
variant = "unknown"
|
variant = "unknown"
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented)
|
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
@@ -17,6 +17,7 @@
|
|||||||
package platforms
|
package platforms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -162,3 +163,14 @@ func (c *windowsMatchComparer) Less(p1, p2 specs.Platform) bool {
|
|||||||
}
|
}
|
||||||
return m1 && !m2
|
return m1 && !m2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type windowsStripFeaturesMatcher struct {
|
||||||
|
Matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m windowsStripFeaturesMatcher) Match(p specs.Platform) bool {
|
||||||
|
if i := slices.Index(p.OSFeatures, "win32k"); i >= 0 {
|
||||||
|
p.OSFeatures = slices.Delete(slices.Clone(p.OSFeatures), i, i+1)
|
||||||
|
}
|
||||||
|
return m.Matcher.Match(p)
|
||||||
|
}
|
||||||
|
|||||||
+168
-22
@@ -111,9 +111,11 @@ package platforms
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -121,12 +123,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
|
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
|
||||||
osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`)
|
osRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.%-]*)((?:\+[A-Za-z0-9_.%-]+)*)\))?$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
const osAndVersionFormat = "%s(%s)"
|
|
||||||
|
|
||||||
// Platform is a type alias for convenience, so there is no need to import image-spec package everywhere.
|
// Platform is a type alias for convenience, so there is no need to import image-spec package everywhere.
|
||||||
type Platform = specs.Platform
|
type Platform = specs.Platform
|
||||||
|
|
||||||
@@ -143,6 +143,10 @@ type Matcher interface {
|
|||||||
// functionality.
|
// functionality.
|
||||||
//
|
//
|
||||||
// Applications should opt to use `Match` over directly parsing specifiers.
|
// Applications should opt to use `Match` over directly parsing specifiers.
|
||||||
|
//
|
||||||
|
// For OSFeatures, this matcher will match if the platform to match has
|
||||||
|
// OSFeatures which are a subset of the OSFeatures of the platform
|
||||||
|
// provided to NewMatcher.
|
||||||
func NewMatcher(platform specs.Platform) Matcher {
|
func NewMatcher(platform specs.Platform) Matcher {
|
||||||
m := &matcher{
|
m := &matcher{
|
||||||
Platform: Normalize(platform),
|
Platform: Normalize(platform),
|
||||||
@@ -152,6 +156,11 @@ func NewMatcher(platform specs.Platform) Matcher {
|
|||||||
m.osvM = &windowsVersionMatcher{
|
m.osvM = &windowsVersionMatcher{
|
||||||
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
|
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In prior versions, the win32k os feature was not considered for matching,
|
||||||
|
// strip out the win32k feature for comparison
|
||||||
|
var stripped Matcher = windowsStripFeaturesMatcher{m}
|
||||||
|
|
||||||
// In prior versions, on windows, the returned matcher implements a
|
// In prior versions, on windows, the returned matcher implements a
|
||||||
// MatchComprarer interface.
|
// MatchComprarer interface.
|
||||||
// This preserves that behavior for backwards compatibility.
|
// This preserves that behavior for backwards compatibility.
|
||||||
@@ -161,8 +170,9 @@ func NewMatcher(platform specs.Platform) Matcher {
|
|||||||
// It was likely intended to be used in `Ordered` but it is not since
|
// It was likely intended to be used in `Ordered` but it is not since
|
||||||
// `Less` that is implemented here ends up getting masked due to wrapping.
|
// `Less` that is implemented here ends up getting masked due to wrapping.
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return &windowsMatchComparer{m}
|
return &windowsMatchComparer{stripped}
|
||||||
}
|
}
|
||||||
|
return stripped
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -178,10 +188,39 @@ type matcher struct {
|
|||||||
|
|
||||||
func (m *matcher) Match(platform specs.Platform) bool {
|
func (m *matcher) Match(platform specs.Platform) bool {
|
||||||
normalized := Normalize(platform)
|
normalized := Normalize(platform)
|
||||||
return m.OS == normalized.OS &&
|
if m.OS == normalized.OS &&
|
||||||
m.Architecture == normalized.Architecture &&
|
m.Architecture == normalized.Architecture &&
|
||||||
m.Variant == normalized.Variant &&
|
m.Variant == normalized.Variant &&
|
||||||
m.matchOSVersion(platform)
|
m.matchOSVersion(platform) {
|
||||||
|
if len(normalized.OSFeatures) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(m.OSFeatures) >= len(normalized.OSFeatures) {
|
||||||
|
// Ensure that normalized.OSFeatures is a subset of
|
||||||
|
// m.OSFeatures
|
||||||
|
j := 0
|
||||||
|
for _, feature := range normalized.OSFeatures {
|
||||||
|
found := false
|
||||||
|
for ; j < len(m.OSFeatures); j++ {
|
||||||
|
if feature == m.OSFeatures[j] {
|
||||||
|
found = true
|
||||||
|
j++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Since both lists are ordered, if the feature is less
|
||||||
|
// than what is seen, it is not in the list
|
||||||
|
if feature < m.OSFeatures[j] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *matcher) matchOSVersion(platform specs.Platform) bool {
|
func (m *matcher) matchOSVersion(platform specs.Platform) bool {
|
||||||
@@ -210,11 +249,14 @@ func ParseAll(specifiers []string) ([]specs.Platform, error) {
|
|||||||
|
|
||||||
// Parse parses the platform specifier syntax into a platform declaration.
|
// Parse parses the platform specifier syntax into a platform declaration.
|
||||||
//
|
//
|
||||||
// Platform specifiers are in the format `<os>[(<OSVersion>)]|<arch>|<os>[(<OSVersion>)]/<arch>[/<variant>]`.
|
// Platform specifiers are in the format `<os>[(<os options>)]|<arch>|<os>[(<os options>)]/<arch>[/<variant>]`.
|
||||||
// The minimum required information for a platform specifier is the operating
|
// The minimum required information for a platform specifier is the operating
|
||||||
// system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)`
|
// system or architecture. The "os options" may be OSVersion which can be part of the OS
|
||||||
// When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value,
|
// like `windows(10.0.17763)`. When an OSVersion is specified, then specs.Platform.OSVersion is
|
||||||
// and an empty string otherwise.
|
// populated with that value, and an empty string otherwise. The "os options" may also include an
|
||||||
|
// array of OSFeatures, each feature prefixed with '+', without any other separator, and provided
|
||||||
|
// after the OSVersion when the OSVersion is specified. An "os options" with version and features
|
||||||
|
// is like `windows(10.0.17763+win32k)`.
|
||||||
// If there is only a single string (no slashes), the
|
// If there is only a single string (no slashes), the
|
||||||
// value will be matched against the known set of operating systems, then fall
|
// value will be matched against the known set of operating systems, then fall
|
||||||
// back to the known set of architectures. The missing component will be
|
// back to the known set of architectures. The missing component will be
|
||||||
@@ -231,14 +273,24 @@ func Parse(specifier string) (specs.Platform, error) {
|
|||||||
var p specs.Platform
|
var p specs.Platform
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
// First element is <os>[(<OSVersion>)]
|
// First element is <os>[(<OSVersion>[+<OSFeature>]*)]
|
||||||
osVer := osAndVersionRe.FindStringSubmatch(part)
|
osOptions := osRe.FindStringSubmatch(part)
|
||||||
if osVer == nil {
|
if osOptions == nil {
|
||||||
return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osAndVersionRe.String(), errInvalidArgument)
|
return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osRe.String(), errInvalidArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.OS = normalizeOS(osVer[1])
|
p.OS = normalizeOS(osOptions[1])
|
||||||
p.OSVersion = osVer[2]
|
osVersion, err := decodeOSOption(osOptions[2])
|
||||||
|
if err != nil {
|
||||||
|
return specs.Platform{}, fmt.Errorf("%q has an invalid OS version %q: %w", specifier, osOptions[2], err)
|
||||||
|
}
|
||||||
|
p.OSVersion = osVersion
|
||||||
|
if osOptions[3] != "" {
|
||||||
|
p.OSFeatures, err = parseOSFeatures(osOptions[3][1:])
|
||||||
|
if err != nil {
|
||||||
|
return specs.Platform{}, fmt.Errorf("%q has invalid OS features: %w", specifier, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if !specifierRe.MatchString(part) {
|
if !specifierRe.MatchString(part) {
|
||||||
return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument)
|
return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument)
|
||||||
@@ -296,6 +348,30 @@ func Parse(specifier string) (specs.Platform, error) {
|
|||||||
return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument)
|
return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseOSFeatures(s string) ([]string, error) {
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var features []string
|
||||||
|
for raw := range strings.SplitSeq(s, "+") {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return nil, fmt.Errorf("empty os feature: %w", errInvalidArgument)
|
||||||
|
}
|
||||||
|
feature, err := decodeOSOption(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid os feature %q: %w", raw, err)
|
||||||
|
}
|
||||||
|
if feature == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
features = append(features, feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
|
||||||
// MustParse is like Parses but panics if the specifier cannot be parsed.
|
// MustParse is like Parses but panics if the specifier cannot be parsed.
|
||||||
// Simplifies initialization of global variables.
|
// Simplifies initialization of global variables.
|
||||||
func MustParse(specifier string) specs.Platform {
|
func MustParse(specifier string) specs.Platform {
|
||||||
@@ -321,12 +397,77 @@ func FormatAll(platform specs.Platform) string {
|
|||||||
if platform.OS == "" {
|
if platform.OS == "" {
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
if platform.OSVersion == "" && len(platform.OSFeatures) == 0 {
|
||||||
if platform.OSVersion != "" {
|
return path.Join(platform.OS, platform.Architecture, platform.Variant)
|
||||||
OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion)
|
|
||||||
return path.Join(OSAndVersion, platform.Architecture, platform.Variant)
|
|
||||||
}
|
}
|
||||||
return path.Join(platform.OS, platform.Architecture, platform.Variant)
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString(platform.OS)
|
||||||
|
osv := encodeOSOption(platform.OSVersion)
|
||||||
|
formatted := formatOSFeatures(platform.OSFeatures)
|
||||||
|
if osv != "" || formatted != "" {
|
||||||
|
b.Grow(len(osv) + len(formatted) + 3) // parens + maybe '+'
|
||||||
|
b.WriteByte('(')
|
||||||
|
if osv != "" {
|
||||||
|
b.WriteString(osv)
|
||||||
|
}
|
||||||
|
if formatted != "" {
|
||||||
|
b.WriteByte('+')
|
||||||
|
b.WriteString(formatted)
|
||||||
|
}
|
||||||
|
b.WriteByte(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(b.String(), platform.Architecture, platform.Variant)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatOSFeatures(features []string) string {
|
||||||
|
if len(features) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.IsSorted(features) {
|
||||||
|
features = slices.Clone(features)
|
||||||
|
slices.Sort(features)
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
var wrote bool
|
||||||
|
var prev string
|
||||||
|
for _, f := range features {
|
||||||
|
if f == "" || f == prev {
|
||||||
|
// skip empty and duplicate values
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prev = f
|
||||||
|
if wrote {
|
||||||
|
b.WriteByte('+')
|
||||||
|
}
|
||||||
|
b.WriteString(encodeOSOption(f))
|
||||||
|
wrote = true
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// osOptionReplacer encodes characters in OS option values (version and
|
||||||
|
// features) that are ambiguous with the format syntax. The percent sign
|
||||||
|
// must be replaced first to avoid double-encoding.
|
||||||
|
var osOptionReplacer = strings.NewReplacer(
|
||||||
|
"%", "%25",
|
||||||
|
"+", "%2B",
|
||||||
|
"(", "%28",
|
||||||
|
")", "%29",
|
||||||
|
"/", "%2F",
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeOSOption(v string) string {
|
||||||
|
return osOptionReplacer.Replace(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeOSOption(v string) (string, error) {
|
||||||
|
if strings.Contains(v, "%") {
|
||||||
|
return url.PathUnescape(v)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize validates and translate the platform to the canonical value.
|
// Normalize validates and translate the platform to the canonical value.
|
||||||
@@ -336,6 +477,11 @@ func FormatAll(platform specs.Platform) string {
|
|||||||
func Normalize(platform specs.Platform) specs.Platform {
|
func Normalize(platform specs.Platform) specs.Platform {
|
||||||
platform.OS = normalizeOS(platform.OS)
|
platform.OS = normalizeOS(platform.OS)
|
||||||
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
|
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
|
||||||
|
if len(platform.OSFeatures) > 0 {
|
||||||
|
platform.OSFeatures = slices.Clone(platform.OSFeatures)
|
||||||
|
slices.Sort(platform.OSFeatures)
|
||||||
|
platform.OSFeatures = slices.Compact(platform.OSFeatures)
|
||||||
|
}
|
||||||
|
|
||||||
return platform
|
return platform
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
|
load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library")
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
|
||||||
|
|
||||||
package(default_visibility = ["//visibility:public"])
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -71,6 +71,7 @@ type ServeMux struct {
|
|||||||
streamErrorHandler StreamErrorHandlerFunc
|
streamErrorHandler StreamErrorHandlerFunc
|
||||||
routingErrorHandler RoutingErrorHandlerFunc
|
routingErrorHandler RoutingErrorHandlerFunc
|
||||||
disablePathLengthFallback bool
|
disablePathLengthFallback bool
|
||||||
|
disableHTTPMethodOverride bool
|
||||||
unescapingMode UnescapingMode
|
unescapingMode UnescapingMode
|
||||||
writeContentLength bool
|
writeContentLength bool
|
||||||
disableChunkedEncoding bool
|
disableChunkedEncoding bool
|
||||||
@@ -271,6 +272,19 @@ func WithDisablePathLengthFallback() ServeMuxOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithDisableHTTPMethodOverride returns a ServeMuxOption that disables the
|
||||||
|
// X-HTTP-Method-Override header handling.
|
||||||
|
//
|
||||||
|
// When this option is used, the mux will no longer allow POST requests with
|
||||||
|
// the X-HTTP-Method-Override header to override the HTTP method. The path
|
||||||
|
// length fallback (POST with application/x-www-form-urlencoded falling back
|
||||||
|
// to a matching GET handler) is not affected by this option.
|
||||||
|
func WithDisableHTTPMethodOverride() ServeMuxOption {
|
||||||
|
return func(serveMux *ServeMux) {
|
||||||
|
serveMux.disableHTTPMethodOverride = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithWriteContentLength returns a ServeMuxOption to enable writing content length on non-streaming responses
|
// WithWriteContentLength returns a ServeMuxOption to enable writing content length on non-streaming responses
|
||||||
func WithWriteContentLength() ServeMuxOption {
|
func WithWriteContentLength() ServeMuxOption {
|
||||||
return func(serveMux *ServeMux) {
|
return func(serveMux *ServeMux) {
|
||||||
@@ -405,7 +419,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
path = r.URL.RawPath
|
path = r.URL.RawPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && s.isPathLengthFallback(r) {
|
if override := r.Header.Get("X-HTTP-Method-Override"); override != "" && !s.disableHTTPMethodOverride && s.isPathLengthFallback(r) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
_, outboundMarshaler := MarshalerForRequest(s, r)
|
_, outboundMarshaler := MarshalerForRequest(s, r)
|
||||||
sterr := status.Error(codes.InvalidArgument, err.Error())
|
sterr := status.Error(codes.InvalidArgument, err.Error())
|
||||||
@@ -467,6 +481,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
HTTPStatus: http.StatusBadRequest,
|
HTTPStatus: http.StatusBadRequest,
|
||||||
Err: mse,
|
Err: mse,
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -509,6 +524,7 @@ func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
HTTPStatus: http.StatusBadRequest,
|
HTTPStatus: http.StatusBadRequest,
|
||||||
Err: mse,
|
Err: mse,
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -1,5 +1,11 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ExporterImage = "image"
|
ExporterImage = "image"
|
||||||
ExporterLocal = "local"
|
ExporterLocal = "local"
|
||||||
@@ -7,3 +13,21 @@ const (
|
|||||||
ExporterOCI = "oci"
|
ExporterOCI = "oci"
|
||||||
ExporterDocker = "docker"
|
ExporterDocker = "docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LocalExporterMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocalExporterModeCopy LocalExporterMode = "copy"
|
||||||
|
LocalExporterModeDelete LocalExporterMode = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseLocalExporterMode(v string) (LocalExporterMode, error) {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||||
|
case "", string(LocalExporterModeCopy):
|
||||||
|
return LocalExporterModeCopy, nil
|
||||||
|
case string(LocalExporterModeDelete):
|
||||||
|
return LocalExporterModeDelete, nil
|
||||||
|
default:
|
||||||
|
return "", errors.Errorf("invalid local exporter mode %q", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+16
-1
@@ -189,7 +189,22 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
|
|||||||
if ex.OutputDir == "" {
|
if ex.OutputDir == "" {
|
||||||
return nil, errors.Errorf("output directory is required for %s exporter", ex.Type)
|
return nil, errors.Errorf("output directory is required for %s exporter", ex.Type)
|
||||||
}
|
}
|
||||||
syncTargets = append(syncTargets, filesync.WithFSSyncDir(exID, ex.OutputDir))
|
if ex.Type == ExporterLocal {
|
||||||
|
mode := LocalExporterModeCopy
|
||||||
|
if ex.Attrs != nil {
|
||||||
|
mode, err = ParseLocalExporterMode(ex.Attrs["mode"])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mode == LocalExporterModeDelete {
|
||||||
|
syncTargets = append(syncTargets, filesync.WithFSSyncDirDelete(exID, ex.OutputDir))
|
||||||
|
} else {
|
||||||
|
syncTargets = append(syncTargets, filesync.WithFSSyncDir(exID, ex.OutputDir))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syncTargets = append(syncTargets, filesync.WithFSSyncDir(exID, ex.OutputDir))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if supportStore {
|
if supportStore {
|
||||||
store := ex.OutputStore
|
store := ex.OutputStore
|
||||||
|
|||||||
+20
-4
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/moby/buildkit/util/bklog"
|
"github.com/moby/buildkit/util/bklog"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tonistiigi/fsutil"
|
"github.com/tonistiigi/fsutil"
|
||||||
fstypes "github.com/tonistiigi/fsutil/types"
|
fstypes "github.com/tonistiigi/fsutil/types"
|
||||||
@@ -111,11 +110,12 @@ func recvDiffCopy(ds grpc.ClientStream, dest string, cu CacheUpdater, progress p
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncTargetDiffCopy(ds grpc.ServerStream, dest string) error {
|
func syncTargetDiffCopy(ds grpc.ServerStream, dest string, deleteMode bool) error {
|
||||||
if err := os.MkdirAll(dest, 0700); err != nil {
|
if err := os.MkdirAll(dest, 0700); err != nil {
|
||||||
return errors.Wrapf(err, "failed to create synctarget dest dir %s", dest)
|
return errors.Wrapf(err, "failed to create synctarget dest dir %s", dest)
|
||||||
}
|
}
|
||||||
return errors.WithStack(fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
|
|
||||||
|
opt := fsutil.ReceiveOpt{
|
||||||
Merge: true,
|
Merge: true,
|
||||||
Filter: func() func(string, *fstypes.Stat) bool {
|
Filter: func() func(string, *fstypes.Stat) bool {
|
||||||
uid := os.Getuid()
|
uid := os.Getuid()
|
||||||
@@ -126,7 +126,23 @@ func syncTargetDiffCopy(ds grpc.ServerStream, dest string) error {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
}))
|
}
|
||||||
|
|
||||||
|
osRoot, err := os.OpenRoot(dest)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open synctarget dest root %s", dest)
|
||||||
|
}
|
||||||
|
root := fsutil.NewRoot(osRoot)
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
if deleteMode {
|
||||||
|
opt.Merge = false
|
||||||
|
// Request every source file so delete mode mirrors file contents without
|
||||||
|
// relying on fsutil's path-based content comparison.
|
||||||
|
opt.Differ = fsutil.DiffNone
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(fsutil.ReceiveRoot(ds.Context(), ds, root, opt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeTargetFile(ds grpc.ServerStream, wc io.WriteCloser) error {
|
func writeTargetFile(ds grpc.ServerStream, wc io.WriteCloser) error {
|
||||||
|
|||||||
+22
-8
@@ -253,9 +253,10 @@ type FSSyncTarget interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fsSyncTarget struct {
|
type fsSyncTarget struct {
|
||||||
id int
|
id int
|
||||||
outdir string
|
outdir string
|
||||||
f FileOutputFunc
|
deleteMode bool
|
||||||
|
f FileOutputFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (target *fsSyncTarget) target() *fsSyncTarget {
|
func (target *fsSyncTarget) target() *fsSyncTarget {
|
||||||
@@ -276,10 +277,23 @@ func WithFSSyncDir(id int, outdir string) FSSyncTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithFSSyncDirDelete(id int, outdir string) FSSyncTarget {
|
||||||
|
return &fsSyncTarget{
|
||||||
|
id: id,
|
||||||
|
outdir: outdir,
|
||||||
|
deleteMode: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fsSyncDirTarget struct {
|
||||||
|
outdir string
|
||||||
|
deleteMode bool
|
||||||
|
}
|
||||||
|
|
||||||
func NewFSSyncTarget(targets ...FSSyncTarget) *SyncTarget {
|
func NewFSSyncTarget(targets ...FSSyncTarget) *SyncTarget {
|
||||||
st := &SyncTarget{
|
st := &SyncTarget{
|
||||||
fs: make(map[int]FileOutputFunc),
|
fs: make(map[int]FileOutputFunc),
|
||||||
outdirs: make(map[int]string),
|
outdirs: make(map[int]fsSyncDirTarget),
|
||||||
}
|
}
|
||||||
st.Add(targets...)
|
st.Add(targets...)
|
||||||
return st
|
return st
|
||||||
@@ -287,7 +301,7 @@ func NewFSSyncTarget(targets ...FSSyncTarget) *SyncTarget {
|
|||||||
|
|
||||||
type SyncTarget struct {
|
type SyncTarget struct {
|
||||||
fs map[int]FileOutputFunc
|
fs map[int]FileOutputFunc
|
||||||
outdirs map[int]string
|
outdirs map[int]fsSyncDirTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ session.Attachable = &SyncTarget{}
|
var _ session.Attachable = &SyncTarget{}
|
||||||
@@ -299,7 +313,7 @@ func (sp *SyncTarget) Add(targets ...FSSyncTarget) {
|
|||||||
sp.fs[t.id] = t.f
|
sp.fs[t.id] = t.f
|
||||||
}
|
}
|
||||||
if t.outdir != "" {
|
if t.outdir != "" {
|
||||||
sp.outdirs[t.id] = t.outdir
|
sp.outdirs[t.id] = fsSyncDirTarget{outdir: t.outdir, deleteMode: t.deleteMode}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,8 +340,8 @@ func (sp *SyncTarget) chooser(ctx context.Context) int {
|
|||||||
|
|
||||||
func (sp *SyncTarget) DiffCopy(stream FileSend_DiffCopyServer) (err error) {
|
func (sp *SyncTarget) DiffCopy(stream FileSend_DiffCopyServer) (err error) {
|
||||||
id := sp.chooser(stream.Context())
|
id := sp.chooser(stream.Context())
|
||||||
if outdir, ok := sp.outdirs[id]; ok {
|
if target, ok := sp.outdirs[id]; ok {
|
||||||
return syncTargetDiffCopy(stream, outdir)
|
return syncTargetDiffCopy(stream, target.outdir, target.deleteMode)
|
||||||
}
|
}
|
||||||
f, ok := sp.fs[id]
|
f, ok := sp.fs[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
+2
-6
@@ -30,7 +30,6 @@ const (
|
|||||||
NetMode_UNSET NetMode = 0 // sandbox
|
NetMode_UNSET NetMode = 0 // sandbox
|
||||||
NetMode_HOST NetMode = 1
|
NetMode_HOST NetMode = 1
|
||||||
NetMode_NONE NetMode = 2
|
NetMode_NONE NetMode = 2
|
||||||
NetMode_PROXY NetMode = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for NetMode.
|
// Enum value maps for NetMode.
|
||||||
@@ -39,13 +38,11 @@ var (
|
|||||||
0: "UNSET",
|
0: "UNSET",
|
||||||
1: "HOST",
|
1: "HOST",
|
||||||
2: "NONE",
|
2: "NONE",
|
||||||
3: "PROXY",
|
|
||||||
}
|
}
|
||||||
NetMode_value = map[string]int32{
|
NetMode_value = map[string]int32{
|
||||||
"UNSET": 0,
|
"UNSET": 0,
|
||||||
"HOST": 1,
|
"HOST": 1,
|
||||||
"NONE": 2,
|
"NONE": 2,
|
||||||
"PROXY": 3,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3845,12 +3842,11 @@ const file_github_com_moby_buildkit_solver_pb_ops_proto_rawDesc = "" +
|
|||||||
"\x05upper\x18\x02 \x01(\v2\x12.pb.UpperDiffInputR\x05upper\"9\n" +
|
"\x05upper\x18\x02 \x01(\v2\x12.pb.UpperDiffInputR\x05upper\"9\n" +
|
||||||
"\rPassthroughOp\x12\x0e\n" +
|
"\rPassthroughOp\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" +
|
"\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" +
|
||||||
"\aoutputs\x18\x02 \x03(\x03R\aoutputs*3\n" +
|
"\aoutputs\x18\x02 \x03(\x03R\aoutputs*(\n" +
|
||||||
"\aNetMode\x12\t\n" +
|
"\aNetMode\x12\t\n" +
|
||||||
"\x05UNSET\x10\x00\x12\b\n" +
|
"\x05UNSET\x10\x00\x12\b\n" +
|
||||||
"\x04HOST\x10\x01\x12\b\n" +
|
"\x04HOST\x10\x01\x12\b\n" +
|
||||||
"\x04NONE\x10\x02\x12\t\n" +
|
"\x04NONE\x10\x02*)\n" +
|
||||||
"\x05PROXY\x10\x03*)\n" +
|
|
||||||
"\fSecurityMode\x12\v\n" +
|
"\fSecurityMode\x12\v\n" +
|
||||||
"\aSANDBOX\x10\x00\x12\f\n" +
|
"\aSANDBOX\x10\x00\x12\f\n" +
|
||||||
"\bINSECURE\x10\x01*@\n" +
|
"\bINSECURE\x10\x01*@\n" +
|
||||||
|
|||||||
-1
@@ -83,7 +83,6 @@ enum NetMode {
|
|||||||
UNSET = 0; // sandbox
|
UNSET = 0; // sandbox
|
||||||
HOST = 1;
|
HOST = 1;
|
||||||
NONE = 2;
|
NONE = 2;
|
||||||
PROXY = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SecurityMode {
|
enum SecurityMode {
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ cmd/tomljson/tomljson
|
|||||||
cmd/tomltestgen/tomltestgen
|
cmd/tomltestgen/tomltestgen
|
||||||
dist
|
dist
|
||||||
tests/
|
tests/
|
||||||
|
test-results
|
||||||
|
|||||||
+33
-41
@@ -1,84 +1,76 @@
|
|||||||
[service]
|
version = "2"
|
||||||
golangci-lint-version = "1.39.0"
|
|
||||||
|
|
||||||
[linters-settings.wsl]
|
|
||||||
allow-assign-and-anything = true
|
|
||||||
|
|
||||||
[linters-settings.exhaustive]
|
|
||||||
default-signifies-exhaustive = true
|
|
||||||
|
|
||||||
[linters]
|
[linters]
|
||||||
disable-all = true
|
default = "none"
|
||||||
enable = [
|
enable = [
|
||||||
"asciicheck",
|
"asciicheck",
|
||||||
"bodyclose",
|
"bodyclose",
|
||||||
"cyclop",
|
|
||||||
"deadcode",
|
|
||||||
"depguard",
|
|
||||||
"dogsled",
|
"dogsled",
|
||||||
"dupl",
|
"dupl",
|
||||||
"durationcheck",
|
"durationcheck",
|
||||||
"errcheck",
|
"errcheck",
|
||||||
"errorlint",
|
"errorlint",
|
||||||
"exhaustive",
|
"exhaustive",
|
||||||
# "exhaustivestruct",
|
|
||||||
"exportloopref",
|
|
||||||
"forbidigo",
|
"forbidigo",
|
||||||
# "forcetypeassert",
|
|
||||||
"funlen",
|
|
||||||
"gci",
|
|
||||||
# "gochecknoglobals",
|
|
||||||
"gochecknoinits",
|
"gochecknoinits",
|
||||||
"gocognit",
|
|
||||||
"goconst",
|
"goconst",
|
||||||
"gocritic",
|
"gocritic",
|
||||||
"gocyclo",
|
"godoclint",
|
||||||
"godot",
|
|
||||||
"godox",
|
|
||||||
# "goerr113",
|
|
||||||
"gofmt",
|
|
||||||
"gofumpt",
|
|
||||||
"goheader",
|
"goheader",
|
||||||
"goimports",
|
|
||||||
"golint",
|
|
||||||
"gomnd",
|
|
||||||
# "gomoddirectives",
|
|
||||||
"gomodguard",
|
"gomodguard",
|
||||||
"goprintffuncname",
|
"goprintffuncname",
|
||||||
"gosec",
|
"gosec",
|
||||||
"gosimple",
|
|
||||||
"govet",
|
"govet",
|
||||||
# "ifshort",
|
|
||||||
"importas",
|
"importas",
|
||||||
"ineffassign",
|
"ineffassign",
|
||||||
"lll",
|
"lll",
|
||||||
"makezero",
|
"makezero",
|
||||||
|
"mirror",
|
||||||
"misspell",
|
"misspell",
|
||||||
"nakedret",
|
"nakedret",
|
||||||
"nestif",
|
|
||||||
"nilerr",
|
"nilerr",
|
||||||
# "nlreturn",
|
|
||||||
"noctx",
|
"noctx",
|
||||||
"nolintlint",
|
"nolintlint",
|
||||||
#"paralleltest",
|
"perfsprint",
|
||||||
"prealloc",
|
"prealloc",
|
||||||
"predeclared",
|
"predeclared",
|
||||||
"revive",
|
"revive",
|
||||||
"rowserrcheck",
|
"rowserrcheck",
|
||||||
"sqlclosecheck",
|
"sqlclosecheck",
|
||||||
"staticcheck",
|
"staticcheck",
|
||||||
"structcheck",
|
|
||||||
"stylecheck",
|
|
||||||
# "testpackage",
|
|
||||||
"thelper",
|
"thelper",
|
||||||
"tparallel",
|
"tparallel",
|
||||||
"typecheck",
|
|
||||||
"unconvert",
|
"unconvert",
|
||||||
"unparam",
|
"unparam",
|
||||||
"unused",
|
"unused",
|
||||||
"varcheck",
|
"usetesting",
|
||||||
"wastedassign",
|
"wastedassign",
|
||||||
"whitespace",
|
"whitespace",
|
||||||
# "wrapcheck",
|
]
|
||||||
# "wsl"
|
|
||||||
|
[linters.settings.exhaustive]
|
||||||
|
default-signifies-exhaustive = true
|
||||||
|
|
||||||
|
[linters.settings.lll]
|
||||||
|
line-length = 150
|
||||||
|
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
path = ".test.go"
|
||||||
|
linters = ["goconst", "gosec"]
|
||||||
|
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
path = "main.go"
|
||||||
|
linters = ["forbidigo"]
|
||||||
|
|
||||||
|
[[linters.exclusions.rules]]
|
||||||
|
path = "internal"
|
||||||
|
linters = ["revive"]
|
||||||
|
text = "(exported|indent-error-flow): "
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
enable = [
|
||||||
|
"gci",
|
||||||
|
"gofmt",
|
||||||
|
"gofumpt",
|
||||||
|
"goimports",
|
||||||
]
|
]
|
||||||
|
|||||||
-3
@@ -22,7 +22,6 @@ builds:
|
|||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
- windows_amd64
|
- windows_amd64
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- windows_arm
|
|
||||||
- darwin_amd64
|
- darwin_amd64
|
||||||
- darwin_arm64
|
- darwin_arm64
|
||||||
- id: tomljson
|
- id: tomljson
|
||||||
@@ -42,7 +41,6 @@ builds:
|
|||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
- windows_amd64
|
- windows_amd64
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- windows_arm
|
|
||||||
- darwin_amd64
|
- darwin_amd64
|
||||||
- darwin_arm64
|
- darwin_arm64
|
||||||
- id: jsontoml
|
- id: jsontoml
|
||||||
@@ -62,7 +60,6 @@ builds:
|
|||||||
- linux_arm
|
- linux_arm
|
||||||
- windows_amd64
|
- windows_amd64
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- windows_arm
|
|
||||||
- darwin_amd64
|
- darwin_amd64
|
||||||
- darwin_arm64
|
- darwin_arm64
|
||||||
universal_binaries:
|
universal_binaries:
|
||||||
|
|||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
# Agent Guidelines for go-toml
|
||||||
|
|
||||||
|
This file provides guidelines for AI agents contributing to go-toml. All agents must follow these rules derived from [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
go-toml is a TOML library for Go. The goal is to provide an easy-to-use and efficient TOML implementation that gets the job done without getting in the way.
|
||||||
|
|
||||||
|
## Code Change Rules
|
||||||
|
|
||||||
|
### Backward Compatibility
|
||||||
|
|
||||||
|
- **No backward-incompatible changes** unless explicitly discussed and approved
|
||||||
|
- Avoid breaking people's programs unless absolutely necessary
|
||||||
|
|
||||||
|
### Testing Requirements
|
||||||
|
|
||||||
|
- **All bug fixes must include regression tests**
|
||||||
|
- **All new code must be tested**
|
||||||
|
- Run tests before submitting: `go test -race ./...`
|
||||||
|
- Test coverage must not decrease. Check with:
|
||||||
|
```bash
|
||||||
|
go test -covermode=atomic -coverprofile=coverage.out
|
||||||
|
go tool cover -func=coverage.out
|
||||||
|
```
|
||||||
|
- All lines of code touched by changes should be covered by tests
|
||||||
|
|
||||||
|
### Performance Requirements
|
||||||
|
|
||||||
|
- go-toml aims to stay efficient; avoid performance regressions
|
||||||
|
- Run benchmarks to verify: `go test ./... -bench=. -count=10`
|
||||||
|
- Compare results using [benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- New features or feature extensions must include documentation
|
||||||
|
- Documentation lives in [README.md](./README.md) and throughout source code
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
- Follow existing code format and structure
|
||||||
|
- Code must pass `go fmt`
|
||||||
|
- Code must pass linting with the same golangci-lint version as CI (see version in `.github/workflows/lint.yml`):
|
||||||
|
```bash
|
||||||
|
# Install specific version (check lint.yml for current version)
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin <version>
|
||||||
|
# Run linter
|
||||||
|
golangci-lint run ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit Messages
|
||||||
|
|
||||||
|
- Commit messages must explain **why** the change is needed
|
||||||
|
- Keep messages clear and informative even if details are in the PR description
|
||||||
|
|
||||||
|
## Pull Request Checklist
|
||||||
|
|
||||||
|
Before submitting:
|
||||||
|
|
||||||
|
1. Tests pass (`go test -race ./...`)
|
||||||
|
2. No backward-incompatible changes (unless discussed)
|
||||||
|
3. Relevant documentation added/updated
|
||||||
|
4. No performance regression (verify with benchmarks)
|
||||||
|
5. Title is clear and understandable for changelog
|
||||||
+51
-9
@@ -33,7 +33,7 @@ The documentation is present in the [README][readme] and thorough the source
|
|||||||
code. On release, it gets updated on [pkg.go.dev][pkg.go.dev]. To make a change
|
code. On release, it gets updated on [pkg.go.dev][pkg.go.dev]. To make a change
|
||||||
to the documentation, create a pull request with your proposed changes. For
|
to the documentation, create a pull request with your proposed changes. For
|
||||||
simple changes like that, the easiest way to go is probably the "Fork this
|
simple changes like that, the easiest way to go is probably the "Fork this
|
||||||
project and edit the file" button on Github, displayed at the top right of the
|
project and edit the file" button on GitHub, displayed at the top right of the
|
||||||
file. Unless it's a trivial change (for example a typo), provide a little bit of
|
file. Unless it's a trivial change (for example a typo), provide a little bit of
|
||||||
context in your pull request description or commit message.
|
context in your pull request description or commit message.
|
||||||
|
|
||||||
@@ -92,6 +92,48 @@ However, given GitHub's new policy to _not_ run Actions on pull requests until a
|
|||||||
maintainer clicks on button, it is highly recommended that you run them locally
|
maintainer clicks on button, it is highly recommended that you run them locally
|
||||||
as you make changes.
|
as you make changes.
|
||||||
|
|
||||||
|
### Test across Go versions
|
||||||
|
|
||||||
|
The repository includes tooling to test go-toml across multiple Go versions
|
||||||
|
(1.11 through 1.25) both locally and in GitHub Actions.
|
||||||
|
|
||||||
|
#### Local testing with Docker
|
||||||
|
|
||||||
|
Prerequisites: Docker installed and running, Bash shell, `rsync` command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test all Go versions in parallel (default)
|
||||||
|
./test-go-versions.sh
|
||||||
|
|
||||||
|
# Test specific versions
|
||||||
|
./test-go-versions.sh 1.21 1.22 1.23
|
||||||
|
|
||||||
|
# Test sequentially (slower but uses less resources)
|
||||||
|
./test-go-versions.sh --sequential
|
||||||
|
|
||||||
|
# Verbose output with custom results directory
|
||||||
|
./test-go-versions.sh --verbose --output ./my-results 1.24 1.25
|
||||||
|
|
||||||
|
# Show all options
|
||||||
|
./test-go-versions.sh --help
|
||||||
|
```
|
||||||
|
|
||||||
|
The script creates Docker containers for each Go version and runs the full test
|
||||||
|
suite. Results are saved to a `test-results/` directory with individual logs and
|
||||||
|
a comprehensive summary report.
|
||||||
|
|
||||||
|
The script only exits with a non-zero status code if either of the two most
|
||||||
|
recent Go versions fail.
|
||||||
|
|
||||||
|
#### GitHub Actions testing (maintainers)
|
||||||
|
|
||||||
|
1. Go to the **Actions** tab in the GitHub repository
|
||||||
|
2. Select **"Go Versions Compatibility Test"** from the workflow list
|
||||||
|
3. Click **"Run workflow"**
|
||||||
|
4. Optionally customize:
|
||||||
|
- **Go versions**: Space-separated list (e.g., `1.21 1.22 1.23`)
|
||||||
|
- **Execution mode**: Parallel (faster) or sequential (more stable)
|
||||||
|
|
||||||
### Check coverage
|
### Check coverage
|
||||||
|
|
||||||
We use `go tool cover` to compute test coverage. Most code editors have a way to
|
We use `go tool cover` to compute test coverage. Most code editors have a way to
|
||||||
@@ -111,7 +153,7 @@ code lowers the coverage.
|
|||||||
|
|
||||||
Go-toml aims to stay efficient. We rely on a set of scenarios executed with Go's
|
Go-toml aims to stay efficient. We rely on a set of scenarios executed with Go's
|
||||||
builtin benchmark systems. Because of their noisy nature, containers provided by
|
builtin benchmark systems. Because of their noisy nature, containers provided by
|
||||||
Github Actions cannot be reliably used for benchmarking. As a result, you are
|
GitHub Actions cannot be reliably used for benchmarking. As a result, you are
|
||||||
responsible for checking that your changes do not incur a performance penalty.
|
responsible for checking that your changes do not incur a performance penalty.
|
||||||
You can run their following to execute benchmarks:
|
You can run their following to execute benchmarks:
|
||||||
|
|
||||||
@@ -168,13 +210,13 @@ Checklist:
|
|||||||
1. Decide on the next version number. Use semver. Review commits since last
|
1. Decide on the next version number. Use semver. Review commits since last
|
||||||
version to assess.
|
version to assess.
|
||||||
2. Tag release. For example:
|
2. Tag release. For example:
|
||||||
```
|
```
|
||||||
git checkout v2
|
git checkout v2
|
||||||
git pull
|
git pull
|
||||||
git tag v2.2.0
|
git tag v2.2.0
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
3. CI automatically builds a draft Github release. Review it and edit as
|
3. CI automatically builds a draft GitHub release. Review it and edit as
|
||||||
necessary. Look for "Other changes". That would indicate a pull request not
|
necessary. Look for "Other changes". That would indicate a pull request not
|
||||||
labeled properly. Tweak labels and pull request titles until changelog looks
|
labeled properly. Tweak labels and pull request titles until changelog looks
|
||||||
good for users.
|
good for users.
|
||||||
|
|||||||
+88
-44
@@ -21,8 +21,6 @@ documentation.
|
|||||||
import "github.com/pelletier/go-toml/v2"
|
import "github.com/pelletier/go-toml/v2"
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Modules](#Modules).
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Stdlib behavior
|
### Stdlib behavior
|
||||||
@@ -107,7 +105,11 @@ type MyConfig struct {
|
|||||||
### Unmarshaling
|
### Unmarshaling
|
||||||
|
|
||||||
[`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its
|
[`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its
|
||||||
content. For example:
|
content.
|
||||||
|
|
||||||
|
Note that the struct variable names are _capitalized_, while the variables in the toml document are _lowercase_.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
doc := `
|
doc := `
|
||||||
@@ -133,6 +135,62 @@ fmt.Println("tags:", cfg.Tags)
|
|||||||
|
|
||||||
[unmarshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Unmarshal
|
[unmarshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Unmarshal
|
||||||
|
|
||||||
|
|
||||||
|
Here is an example using tables with some simple nesting:
|
||||||
|
|
||||||
|
```go
|
||||||
|
doc := `
|
||||||
|
age = 45
|
||||||
|
fruits = ["apple", "pear"]
|
||||||
|
|
||||||
|
# these are very important!
|
||||||
|
[my-variables]
|
||||||
|
first = 1
|
||||||
|
second = 0.2
|
||||||
|
third = "abc"
|
||||||
|
|
||||||
|
# this is not so important.
|
||||||
|
[my-variables.b]
|
||||||
|
bfirst = 123
|
||||||
|
`
|
||||||
|
|
||||||
|
var Document struct {
|
||||||
|
Age int
|
||||||
|
Fruits []string
|
||||||
|
|
||||||
|
Myvariables struct {
|
||||||
|
First int
|
||||||
|
Second float64
|
||||||
|
Third string
|
||||||
|
|
||||||
|
B struct {
|
||||||
|
Bfirst int
|
||||||
|
}
|
||||||
|
} `toml:"my-variables"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := toml.Unmarshal([]byte(doc), &Document)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("age:", Document.Age)
|
||||||
|
fmt.Println("fruits:", Document.Fruits)
|
||||||
|
fmt.Println("my-variables.first:", Document.Myvariables.First)
|
||||||
|
fmt.Println("my-variables.second:", Document.Myvariables.Second)
|
||||||
|
fmt.Println("my-variables.third:", Document.Myvariables.Third)
|
||||||
|
fmt.Println("my-variables.B.Bfirst:", Document.Myvariables.B.Bfirst)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// age: 45
|
||||||
|
// fruits: [apple pear]
|
||||||
|
// my-variables.first: 1
|
||||||
|
// my-variables.second: 0.2
|
||||||
|
// my-variables.third: abc
|
||||||
|
// my-variables.B.Bfirst: 123
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Marshaling
|
### Marshaling
|
||||||
|
|
||||||
[`Marshal`][marshal] is the opposite of Unmarshal: it represents a Go structure
|
[`Marshal`][marshal] is the opposite of Unmarshal: it represents a Go structure
|
||||||
@@ -175,17 +233,17 @@ the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable.
|
|||||||
Execution time speedup compared to other Go TOML libraries:
|
Execution time speedup compared to other Go TOML libraries:
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Marshal/HugoFrontMatter-2</td><td>1.9x</td><td>2.2x</td></tr>
|
<tr><td>Marshal/HugoFrontMatter-2</td><td>2.1x</td><td>2.0x</td></tr>
|
||||||
<tr><td>Marshal/ReferenceFile/map-2</td><td>1.7x</td><td>2.1x</td></tr>
|
<tr><td>Marshal/ReferenceFile/map-2</td><td>2.0x</td><td>2.0x</td></tr>
|
||||||
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.2x</td><td>3.0x</td></tr>
|
<tr><td>Marshal/ReferenceFile/struct-2</td><td>2.3x</td><td>2.5x</td></tr>
|
||||||
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>2.9x</td><td>2.7x</td></tr>
|
<tr><td>Unmarshal/HugoFrontMatter-2</td><td>3.3x</td><td>2.8x</td></tr>
|
||||||
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.6x</td><td>2.7x</td></tr>
|
<tr><td>Unmarshal/ReferenceFile/map-2</td><td>2.9x</td><td>3.0x</td></tr>
|
||||||
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.6x</td><td>5.1x</td></tr>
|
<tr><td>Unmarshal/ReferenceFile/struct-2</td><td>4.8x</td><td>5.0x</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<details><summary>See more</summary>
|
<details><summary>See more</summary>
|
||||||
<p>The table above has the results of the most common use-cases. The table below
|
<p>The table above has the results of the most common use-cases. The table below
|
||||||
@@ -193,40 +251,26 @@ contains the results of all benchmarks, including unrealistic ones. It is
|
|||||||
provided for completeness.</p>
|
provided for completeness.</p>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Marshal/SimpleDocument/map-2</td><td>1.8x</td><td>2.7x</td></tr>
|
<tr><td>Marshal/SimpleDocument/map-2</td><td>2.0x</td><td>2.9x</td></tr>
|
||||||
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.7x</td><td>3.8x</td></tr>
|
<tr><td>Marshal/SimpleDocument/struct-2</td><td>2.5x</td><td>3.6x</td></tr>
|
||||||
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>3.8x</td><td>3.0x</td></tr>
|
<tr><td>Unmarshal/SimpleDocument/map-2</td><td>4.2x</td><td>3.4x</td></tr>
|
||||||
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>5.6x</td><td>4.1x</td></tr>
|
<tr><td>Unmarshal/SimpleDocument/struct-2</td><td>5.9x</td><td>4.4x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/example-2</td><td>3.0x</td><td>3.2x</td></tr>
|
<tr><td>UnmarshalDataset/example-2</td><td>3.2x</td><td>2.9x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/code-2</td><td>2.3x</td><td>2.9x</td></tr>
|
<tr><td>UnmarshalDataset/code-2</td><td>2.4x</td><td>2.8x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/twitter-2</td><td>2.6x</td><td>2.7x</td></tr>
|
<tr><td>UnmarshalDataset/twitter-2</td><td>2.7x</td><td>2.5x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.2x</td><td>2.3x</td></tr>
|
<tr><td>UnmarshalDataset/citm_catalog-2</td><td>2.3x</td><td>2.3x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/canada-2</td><td>1.8x</td><td>1.5x</td></tr>
|
<tr><td>UnmarshalDataset/canada-2</td><td>1.9x</td><td>1.5x</td></tr>
|
||||||
<tr><td>UnmarshalDataset/config-2</td><td>4.1x</td><td>2.9x</td></tr>
|
<tr><td>UnmarshalDataset/config-2</td><td>5.4x</td><td>3.0x</td></tr>
|
||||||
<tr><td>geomean</td><td>2.7x</td><td>2.8x</td></tr>
|
<tr><td>geomean</td><td>2.9x</td><td>2.8x</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
|
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Modules
|
|
||||||
|
|
||||||
go-toml uses Go's standard modules system.
|
|
||||||
|
|
||||||
Installation instructions:
|
|
||||||
|
|
||||||
- Go ≥ 1.16: Nothing to do. Use the import in your code. The `go` command deals
|
|
||||||
with it automatically.
|
|
||||||
- Go ≥ 1.13: `GO111MODULE=on go get github.com/pelletier/go-toml/v2`.
|
|
||||||
|
|
||||||
In case of trouble: [Go Modules FAQ][mod-faq].
|
|
||||||
|
|
||||||
[mod-faq]: https://github.com/golang/go/wiki/Modules#why-does-installing-a-tool-via-go-get-fail-with-error-cannot-find-main-module
|
|
||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
Go-toml provides three handy command line tools:
|
Go-toml provides three handy command line tools:
|
||||||
|
|||||||
+6
-1
@@ -147,7 +147,7 @@ bench() {
|
|||||||
pushd "$dir"
|
pushd "$dir"
|
||||||
|
|
||||||
if [ "${replace}" != "" ]; then
|
if [ "${replace}" != "" ]; then
|
||||||
find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2|${replace}|g" {} \;
|
find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2\"|${replace}\"|g" {} \;
|
||||||
go get "${replace}"
|
go get "${replace}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -195,6 +195,11 @@ for line in reversed(lines[2:]):
|
|||||||
"%.1fx" % (float(line[3])/v2), # v1
|
"%.1fx" % (float(line[3])/v2), # v1
|
||||||
"%.1fx" % (float(line[7])/v2), # bs
|
"%.1fx" % (float(line[7])/v2), # bs
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("No benchmark results to display.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# move geomean to the end
|
# move geomean to the end
|
||||||
results.append(results[0])
|
results.append(results[0])
|
||||||
del results[0]
|
del results[0]
|
||||||
|
|||||||
+2
-3
@@ -230,8 +230,8 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
|||||||
return t, nil, err
|
return t, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Second > 60 {
|
if t.Second > 59 {
|
||||||
return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60")
|
return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater than 59")
|
||||||
}
|
}
|
||||||
|
|
||||||
b = b[8:]
|
b = b[8:]
|
||||||
@@ -279,7 +279,6 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
|
|||||||
return t, b, nil
|
return t, b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:cyclop
|
|
||||||
func parseFloat(b []byte) (float64, error) {
|
func parseFloat(b []byte) (float64, error) {
|
||||||
if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' {
|
if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' {
|
||||||
return math.NaN(), nil
|
return math.NaN(), nil
|
||||||
|
|||||||
+35
-4
@@ -2,10 +2,10 @@ package toml
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
|
||||||
"github.com/pelletier/go-toml/v2/unstable"
|
"github.com/pelletier/go-toml/v2/unstable"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,6 +54,18 @@ func (s *StrictMissingError) String() string {
|
|||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap returns wrapped decode errors
|
||||||
|
//
|
||||||
|
// Implements errors.Join() interface.
|
||||||
|
func (s *StrictMissingError) Unwrap() []error {
|
||||||
|
errs := make([]error, len(s.Errors))
|
||||||
|
for i := range s.Errors {
|
||||||
|
errs[i] = &s.Errors[i]
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key represents a TOML key as a sequence of key parts.
|
||||||
type Key []string
|
type Key []string
|
||||||
|
|
||||||
// Error returns the error message contained in the DecodeError.
|
// Error returns the error message contained in the DecodeError.
|
||||||
@@ -78,7 +90,7 @@ func (e *DecodeError) Key() Key {
|
|||||||
return e.key
|
return e.key
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeErrorFromHighlight creates a DecodeError referencing a highlighted
|
// wrapDecodeError creates a DecodeError referencing a highlighted
|
||||||
// range of bytes from document.
|
// range of bytes from document.
|
||||||
//
|
//
|
||||||
// highlight needs to be a sub-slice of document, or this function panics.
|
// highlight needs to be a sub-slice of document, or this function panics.
|
||||||
@@ -88,7 +100,7 @@ func (e *DecodeError) Key() Key {
|
|||||||
//
|
//
|
||||||
//nolint:funlen
|
//nolint:funlen
|
||||||
func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
|
func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
|
||||||
offset := danger.SubsliceOffset(document, de.Highlight)
|
offset := subsliceOffset(document, de.Highlight)
|
||||||
|
|
||||||
errMessage := de.Error()
|
errMessage := de.Error()
|
||||||
errLine, errColumn := positionAtEnd(document[:offset])
|
errLine, errColumn := positionAtEnd(document[:offset])
|
||||||
@@ -248,5 +260,24 @@ func positionAtEnd(b []byte) (row int, column int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return row, column
|
||||||
|
}
|
||||||
|
|
||||||
|
// subsliceOffset returns the byte offset of subslice within data.
|
||||||
|
// subslice must share the same backing array as data.
|
||||||
|
func subsliceOffset(data []byte, subslice []byte) int {
|
||||||
|
if len(subslice) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reflect to get the data pointers of both slices.
|
||||||
|
// This is safe because we're only reading the pointer values for comparison.
|
||||||
|
dataPtr := reflect.ValueOf(data).Pointer()
|
||||||
|
subPtr := reflect.ValueOf(subslice).Pointer()
|
||||||
|
|
||||||
|
offset := int(subPtr - dataPtr)
|
||||||
|
if offset < 0 || offset > len(data) {
|
||||||
|
panic("subslice is not within data")
|
||||||
|
}
|
||||||
|
return offset
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
package characters
|
package characters
|
||||||
|
|
||||||
var invalidAsciiTable = [256]bool{
|
var invalidASCIITable = [256]bool{
|
||||||
0x00: true,
|
0x00: true,
|
||||||
0x01: true,
|
0x01: true,
|
||||||
0x02: true,
|
0x02: true,
|
||||||
@@ -37,6 +37,6 @@ var invalidAsciiTable = [256]bool{
|
|||||||
0x7F: true,
|
0x7F: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func InvalidAscii(b byte) bool {
|
func InvalidASCII(b byte) bool {
|
||||||
return invalidAsciiTable[b]
|
return invalidASCIITable[b]
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-46
@@ -1,20 +1,12 @@
|
|||||||
|
// Package characters provides functions for working with string encodings.
|
||||||
package characters
|
package characters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type utf8Err struct {
|
// Utf8TomlValidAlreadyEscaped verifies that a given string is only made of
|
||||||
Index int
|
// valid UTF-8 characters allowed by the TOML spec:
|
||||||
Size int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u utf8Err) Zero() bool {
|
|
||||||
return u.Size == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verified that a given string is only made of valid UTF-8 characters allowed
|
|
||||||
// by the TOML spec:
|
|
||||||
//
|
//
|
||||||
// Any Unicode character may be used except those that must be escaped:
|
// Any Unicode character may be used except those that must be escaped:
|
||||||
// quotation mark, backslash, and the control characters other than tab (U+0000
|
// quotation mark, backslash, and the control characters other than tab (U+0000
|
||||||
@@ -23,8 +15,8 @@ func (u utf8Err) Zero() bool {
|
|||||||
// It is a copy of the Go 1.17 utf8.Valid implementation, tweaked to exit early
|
// It is a copy of the Go 1.17 utf8.Valid implementation, tweaked to exit early
|
||||||
// when a character is not allowed.
|
// when a character is not allowed.
|
||||||
//
|
//
|
||||||
// The returned utf8Err is Zero() if the string is valid, or contains the byte
|
// The returned slice is empty if the string is valid, or contains the bytes
|
||||||
// index and size of the invalid character.
|
// of the invalid character.
|
||||||
//
|
//
|
||||||
// quotation mark => already checked
|
// quotation mark => already checked
|
||||||
// backslash => already checked
|
// backslash => already checked
|
||||||
@@ -32,9 +24,8 @@ func (u utf8Err) Zero() bool {
|
|||||||
// 0x9 => tab, ok
|
// 0x9 => tab, ok
|
||||||
// 0xA - 0x1F => invalid
|
// 0xA - 0x1F => invalid
|
||||||
// 0x7F => invalid
|
// 0x7F => invalid
|
||||||
func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
|
func Utf8TomlValidAlreadyEscaped(p []byte) []byte {
|
||||||
// Fast path. Check for and skip 8 bytes of ASCII characters per iteration.
|
// Fast path. Check for and skip 8 bytes of ASCII characters per iteration.
|
||||||
offset := 0
|
|
||||||
for len(p) >= 8 {
|
for len(p) >= 8 {
|
||||||
// Combining two 32 bit loads allows the same code to be used
|
// Combining two 32 bit loads allows the same code to be used
|
||||||
// for 32 and 64 bit platforms.
|
// for 32 and 64 bit platforms.
|
||||||
@@ -48,24 +39,19 @@ func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, b := range p[:8] {
|
for i, b := range p[:8] {
|
||||||
if InvalidAscii(b) {
|
if InvalidASCII(b) {
|
||||||
err.Index = offset + i
|
return p[i : i+1]
|
||||||
err.Size = 1
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p = p[8:]
|
p = p[8:]
|
||||||
offset += 8
|
|
||||||
}
|
}
|
||||||
n := len(p)
|
n := len(p)
|
||||||
for i := 0; i < n; {
|
for i := 0; i < n; {
|
||||||
pi := p[i]
|
pi := p[i]
|
||||||
if pi < utf8.RuneSelf {
|
if pi < utf8.RuneSelf {
|
||||||
if InvalidAscii(pi) {
|
if InvalidASCII(pi) {
|
||||||
err.Index = offset + i
|
return p[i : i+1]
|
||||||
err.Size = 1
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
@@ -73,44 +59,34 @@ func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
|
|||||||
x := first[pi]
|
x := first[pi]
|
||||||
if x == xx {
|
if x == xx {
|
||||||
// Illegal starter byte.
|
// Illegal starter byte.
|
||||||
err.Index = offset + i
|
return p[i : i+1]
|
||||||
err.Size = 1
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
size := int(x & 7)
|
size := int(x & 7)
|
||||||
if i+size > n {
|
if i+size > n {
|
||||||
// Short or invalid.
|
// Short or invalid.
|
||||||
err.Index = offset + i
|
return p[i:n]
|
||||||
err.Size = n - i
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
accept := acceptRanges[x>>4]
|
accept := acceptRanges[x>>4]
|
||||||
if c := p[i+1]; c < accept.lo || accept.hi < c {
|
if c := p[i+1]; c < accept.lo || accept.hi < c {
|
||||||
err.Index = offset + i
|
return p[i : i+2]
|
||||||
err.Size = 2
|
} else if size == 2 { //revive:disable:empty-block
|
||||||
return
|
|
||||||
} else if size == 2 {
|
|
||||||
} else if c := p[i+2]; c < locb || hicb < c {
|
} else if c := p[i+2]; c < locb || hicb < c {
|
||||||
err.Index = offset + i
|
return p[i : i+3]
|
||||||
err.Size = 3
|
} else if size == 3 { //revive:disable:empty-block
|
||||||
return
|
|
||||||
} else if size == 3 {
|
|
||||||
} else if c := p[i+3]; c < locb || hicb < c {
|
} else if c := p[i+3]; c < locb || hicb < c {
|
||||||
err.Index = offset + i
|
return p[i : i+4]
|
||||||
err.Size = 4
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
i += size
|
i += size
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the size of the next rune if valid, 0 otherwise.
|
// Utf8ValidNext returns the size of the next rune if valid, 0 otherwise.
|
||||||
func Utf8ValidNext(p []byte) int {
|
func Utf8ValidNext(p []byte) int {
|
||||||
c := p[0]
|
c := p[0]
|
||||||
|
|
||||||
if c < utf8.RuneSelf {
|
if c < utf8.RuneSelf {
|
||||||
if InvalidAscii(c) {
|
if InvalidASCII(c) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
@@ -129,10 +105,10 @@ func Utf8ValidNext(p []byte) int {
|
|||||||
accept := acceptRanges[x>>4]
|
accept := acceptRanges[x>>4]
|
||||||
if c := p[1]; c < accept.lo || accept.hi < c {
|
if c := p[1]; c < accept.lo || accept.hi < c {
|
||||||
return 0
|
return 0
|
||||||
} else if size == 2 {
|
} else if size == 2 { //nolint:revive
|
||||||
} else if c := p[2]; c < locb || hicb < c {
|
} else if c := p[2]; c < locb || hicb < c {
|
||||||
return 0
|
return 0
|
||||||
} else if size == 3 {
|
} else if size == 3 { //nolint:revive
|
||||||
} else if c := p[3]; c < locb || hicb < c {
|
} else if c := p[3]; c < locb || hicb < c {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
-65
@@ -1,65 +0,0 @@
|
|||||||
package danger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const maxInt = uintptr(int(^uint(0) >> 1))
|
|
||||||
|
|
||||||
func SubsliceOffset(data []byte, subslice []byte) int {
|
|
||||||
datap := (*reflect.SliceHeader)(unsafe.Pointer(&data))
|
|
||||||
hlp := (*reflect.SliceHeader)(unsafe.Pointer(&subslice))
|
|
||||||
|
|
||||||
if hlp.Data < datap.Data {
|
|
||||||
panic(fmt.Errorf("subslice address (%d) is before data address (%d)", hlp.Data, datap.Data))
|
|
||||||
}
|
|
||||||
offset := hlp.Data - datap.Data
|
|
||||||
|
|
||||||
if offset > maxInt {
|
|
||||||
panic(fmt.Errorf("slice offset larger than int (%d)", offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
intoffset := int(offset)
|
|
||||||
|
|
||||||
if intoffset > datap.Len {
|
|
||||||
panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, datap.Len))
|
|
||||||
}
|
|
||||||
|
|
||||||
if intoffset+hlp.Len > datap.Len {
|
|
||||||
panic(fmt.Errorf("slice ends (%d+%d) is farther than data length (%d)", intoffset, hlp.Len, datap.Len))
|
|
||||||
}
|
|
||||||
|
|
||||||
return intoffset
|
|
||||||
}
|
|
||||||
|
|
||||||
func BytesRange(start []byte, end []byte) []byte {
|
|
||||||
if start == nil || end == nil {
|
|
||||||
panic("cannot call BytesRange with nil")
|
|
||||||
}
|
|
||||||
startp := (*reflect.SliceHeader)(unsafe.Pointer(&start))
|
|
||||||
endp := (*reflect.SliceHeader)(unsafe.Pointer(&end))
|
|
||||||
|
|
||||||
if startp.Data > endp.Data {
|
|
||||||
panic(fmt.Errorf("start pointer address (%d) is after end pointer address (%d)", startp.Data, endp.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
l := startp.Len
|
|
||||||
endLen := int(endp.Data-startp.Data) + endp.Len
|
|
||||||
if endLen > l {
|
|
||||||
l = endLen
|
|
||||||
}
|
|
||||||
|
|
||||||
if l > startp.Cap {
|
|
||||||
panic(fmt.Errorf("range length is larger than capacity"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return start[:l]
|
|
||||||
}
|
|
||||||
|
|
||||||
func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer {
|
|
||||||
// TODO: replace with unsafe.Add when Go 1.17 is released
|
|
||||||
// https://github.com/golang/go/issues/40481
|
|
||||||
return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset))
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
package danger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// typeID is used as key in encoder and decoder caches to enable using
|
|
||||||
// the optimize runtime.mapaccess2_fast64 function instead of the more
|
|
||||||
// expensive lookup if we were to use reflect.Type as map key.
|
|
||||||
//
|
|
||||||
// typeID holds the pointer to the reflect.Type value, which is unique
|
|
||||||
// in the program.
|
|
||||||
//
|
|
||||||
// https://github.com/segmentio/encoding/blob/master/json/codec.go#L59-L61
|
|
||||||
type TypeID unsafe.Pointer
|
|
||||||
|
|
||||||
func MakeTypeID(t reflect.Type) TypeID {
|
|
||||||
// reflect.Type has the fields:
|
|
||||||
// typ unsafe.Pointer
|
|
||||||
// ptr unsafe.Pointer
|
|
||||||
return TypeID((*[2]unsafe.Pointer)(unsafe.Pointer(&t))[1])
|
|
||||||
}
|
|
||||||
+1
-1
@@ -36,7 +36,7 @@ func (t *KeyTracker) Pop(node *unstable.Node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns the current key
|
// Key returns the current key.
|
||||||
func (t *KeyTracker) Key() []string {
|
func (t *KeyTracker) Key() []string {
|
||||||
k := make([]string, len(t.k))
|
k := make([]string, len(t.k))
|
||||||
copy(k, t.k)
|
copy(k, t.k)
|
||||||
|
|||||||
+7
-6
@@ -288,11 +288,12 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) {
|
|||||||
idx = s.create(parentIdx, k, tableKind, false, true)
|
idx = s.create(parentIdx, k, tableKind, false, true)
|
||||||
} else {
|
} else {
|
||||||
entry := s.entries[idx]
|
entry := s.entries[idx]
|
||||||
if it.IsLast() {
|
switch {
|
||||||
|
case it.IsLast():
|
||||||
return false, fmt.Errorf("toml: key %s is already defined", string(k))
|
return false, fmt.Errorf("toml: key %s is already defined", string(k))
|
||||||
} else if entry.kind != tableKind {
|
case entry.kind != tableKind:
|
||||||
return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
|
return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind)
|
||||||
} else if entry.explicit {
|
case entry.explicit:
|
||||||
return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))
|
return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,16 +310,16 @@ func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) {
|
|||||||
return s.checkInlineTable(value)
|
return s.checkInlineTable(value)
|
||||||
case unstable.Array:
|
case unstable.Array:
|
||||||
return s.checkArray(value)
|
return s.checkArray(value)
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) {
|
func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) {
|
||||||
it := node.Children()
|
it := node.Children()
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
n := it.Node()
|
n := it.Node()
|
||||||
switch n.Kind {
|
switch n.Kind { //nolint:exhaustive
|
||||||
case unstable.InlineTable:
|
case unstable.InlineTable:
|
||||||
first, err = s.checkInlineTable(n)
|
first, err = s.checkInlineTable(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+1
@@ -1 +1,2 @@
|
|||||||
|
// Package tracker provides functions for keeping track of AST nodes.
|
||||||
package tracker
|
package tracker
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ func (d *LocalDate) UnmarshalText(b []byte) error {
|
|||||||
type LocalTime struct {
|
type LocalTime struct {
|
||||||
Hour int // Hour of the day: [0; 24[
|
Hour int // Hour of the day: [0; 24[
|
||||||
Minute int // Minute of the hour: [0; 60[
|
Minute int // Minute of the hour: [0; 60[
|
||||||
Second int // Second of the minute: [0; 60[
|
Second int // Second of the minute: [0; 59]
|
||||||
Nanosecond int // Nanoseconds within the second: [0, 1000000000[
|
Nanosecond int // Nanoseconds within the second: [0, 1000000000[
|
||||||
Precision int // Number of digits to display for Nanosecond.
|
Precision int // Number of digits to display for Nanosecond.
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-48
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding"
|
"encoding"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@@ -42,7 +43,7 @@ type Encoder struct {
|
|||||||
arraysMultiline bool
|
arraysMultiline bool
|
||||||
indentSymbol string
|
indentSymbol string
|
||||||
indentTables bool
|
indentTables bool
|
||||||
marshalJsonNumbers bool
|
marshalJSONNumbers bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns a new Encoder that writes to w.
|
// NewEncoder returns a new Encoder that writes to w.
|
||||||
@@ -89,14 +90,14 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
|
|||||||
return enc
|
return enc
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a
|
// SetMarshalJSONNumbers forces the encoder to serialize `json.Number` as a
|
||||||
// float or integer instead of relying on TextMarshaler to emit a string.
|
// float or integer instead of relying on TextMarshaler to emit a string.
|
||||||
//
|
//
|
||||||
// *Unstable:* This method does not follow the compatibility guarantees of
|
// *Unstable:* This method does not follow the compatibility guarantees of
|
||||||
// semver. It can be changed or removed without a new major version being
|
// semver. It can be changed or removed without a new major version being
|
||||||
// issued.
|
// issued.
|
||||||
func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder {
|
func (enc *Encoder) SetMarshalJSONNumbers(indent bool) *Encoder {
|
||||||
enc.marshalJsonNumbers = indent
|
enc.marshalJSONNumbers = indent
|
||||||
return enc
|
return enc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +162,8 @@ func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder {
|
|||||||
//
|
//
|
||||||
// The "omitempty" option prevents empty values or groups from being emitted.
|
// The "omitempty" option prevents empty values or groups from being emitted.
|
||||||
//
|
//
|
||||||
|
// The "omitzero" option prevents zero values or groups from being emitted.
|
||||||
|
//
|
||||||
// The "commented" option prefixes the value and all its children with a comment
|
// The "commented" option prefixes the value and all its children with a comment
|
||||||
// symbol.
|
// symbol.
|
||||||
//
|
//
|
||||||
@@ -177,7 +180,7 @@ func (enc *Encoder) Encode(v interface{}) error {
|
|||||||
ctx.inline = enc.tablesInline
|
ctx.inline = enc.tablesInline
|
||||||
|
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return fmt.Errorf("toml: cannot encode a nil interface")
|
return errors.New("toml: cannot encode a nil interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := enc.encode(b, ctx, reflect.ValueOf(v))
|
b, err := enc.encode(b, ctx, reflect.ValueOf(v))
|
||||||
@@ -196,6 +199,7 @@ func (enc *Encoder) Encode(v interface{}) error {
|
|||||||
type valueOptions struct {
|
type valueOptions struct {
|
||||||
multiline bool
|
multiline bool
|
||||||
omitempty bool
|
omitempty bool
|
||||||
|
omitzero bool
|
||||||
commented bool
|
commented bool
|
||||||
comment string
|
comment string
|
||||||
}
|
}
|
||||||
@@ -266,16 +270,15 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
|||||||
case LocalDateTime:
|
case LocalDateTime:
|
||||||
return append(b, x.String()...), nil
|
return append(b, x.String()...), nil
|
||||||
case json.Number:
|
case json.Number:
|
||||||
if enc.marshalJsonNumbers {
|
if enc.marshalJSONNumbers {
|
||||||
if x == "" { /// Useful zero value.
|
if x == "" { /// Useful zero value.
|
||||||
return append(b, "0"...), nil
|
return append(b, "0"...), nil
|
||||||
} else if v, err := x.Int64(); err == nil {
|
} else if v, err := x.Int64(); err == nil {
|
||||||
return enc.encode(b, ctx, reflect.ValueOf(v))
|
return enc.encode(b, ctx, reflect.ValueOf(v))
|
||||||
} else if f, err := x.Float64(); err == nil {
|
} else if f, err := x.Float64(); err == nil {
|
||||||
return enc.encode(b, ctx, reflect.ValueOf(f))
|
return enc.encode(b, ctx, reflect.ValueOf(f))
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x)
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +312,7 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
|||||||
return enc.encodeSlice(b, ctx, v)
|
return enc.encodeSlice(b, ctx, v)
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
if v.IsNil() {
|
if v.IsNil() {
|
||||||
return nil, fmt.Errorf("toml: encoding a nil interface is not supported")
|
return nil, errors.New("toml: encoding a nil interface is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
return enc.encode(b, ctx, v.Elem())
|
return enc.encode(b, ctx, v.Elem())
|
||||||
@@ -326,28 +329,30 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
|
|||||||
case reflect.Float32:
|
case reflect.Float32:
|
||||||
f := v.Float()
|
f := v.Float()
|
||||||
|
|
||||||
if math.IsNaN(f) {
|
switch {
|
||||||
|
case math.IsNaN(f):
|
||||||
b = append(b, "nan"...)
|
b = append(b, "nan"...)
|
||||||
} else if f > math.MaxFloat32 {
|
case f > math.MaxFloat32:
|
||||||
b = append(b, "inf"...)
|
b = append(b, "inf"...)
|
||||||
} else if f < -math.MaxFloat32 {
|
case f < -math.MaxFloat32:
|
||||||
b = append(b, "-inf"...)
|
b = append(b, "-inf"...)
|
||||||
} else if math.Trunc(f) == f {
|
case math.Trunc(f) == f:
|
||||||
b = strconv.AppendFloat(b, f, 'f', 1, 32)
|
b = strconv.AppendFloat(b, f, 'f', 1, 32)
|
||||||
} else {
|
default:
|
||||||
b = strconv.AppendFloat(b, f, 'f', -1, 32)
|
b = strconv.AppendFloat(b, f, 'f', -1, 32)
|
||||||
}
|
}
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
f := v.Float()
|
f := v.Float()
|
||||||
if math.IsNaN(f) {
|
switch {
|
||||||
|
case math.IsNaN(f):
|
||||||
b = append(b, "nan"...)
|
b = append(b, "nan"...)
|
||||||
} else if f > math.MaxFloat64 {
|
case f > math.MaxFloat64:
|
||||||
b = append(b, "inf"...)
|
b = append(b, "inf"...)
|
||||||
} else if f < -math.MaxFloat64 {
|
case f < -math.MaxFloat64:
|
||||||
b = append(b, "-inf"...)
|
b = append(b, "-inf"...)
|
||||||
} else if math.Trunc(f) == f {
|
case math.Trunc(f) == f:
|
||||||
b = strconv.AppendFloat(b, f, 'f', 1, 64)
|
b = strconv.AppendFloat(b, f, 'f', 1, 64)
|
||||||
} else {
|
default:
|
||||||
b = strconv.AppendFloat(b, f, 'f', -1, 64)
|
b = strconv.AppendFloat(b, f, 'f', -1, 64)
|
||||||
}
|
}
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
@@ -384,6 +389,31 @@ func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
|
|||||||
return options.omitempty && isEmptyValue(v)
|
return options.omitempty && isEmptyValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldOmitZero(options valueOptions, v reflect.Value) bool {
|
||||||
|
if !options.omitzero {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the type implements isZeroer interface (has a custom IsZero method).
|
||||||
|
if v.Type().Implements(isZeroerType) {
|
||||||
|
return v.Interface().(isZeroer).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pointer type implements isZeroer.
|
||||||
|
if reflect.PointerTo(v.Type()).Implements(isZeroerType) {
|
||||||
|
if v.CanAddr() {
|
||||||
|
return v.Addr().Interface().(isZeroer).IsZero()
|
||||||
|
}
|
||||||
|
// Create a temporary addressable copy to call the pointer receiver method.
|
||||||
|
pv := reflect.New(v.Type())
|
||||||
|
pv.Elem().Set(v)
|
||||||
|
return pv.Interface().(isZeroer).IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to reflect's IsZero for types without custom IsZero method.
|
||||||
|
return v.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -434,8 +464,9 @@ func isEmptyValue(v reflect.Value) bool {
|
|||||||
return v.Float() == 0
|
return v.Float() == 0
|
||||||
case reflect.Interface, reflect.Ptr:
|
case reflect.Interface, reflect.Ptr:
|
||||||
return v.IsNil()
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEmptyStruct(v reflect.Value) bool {
|
func isEmptyStruct(v reflect.Value) bool {
|
||||||
@@ -479,7 +510,7 @@ func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byt
|
|||||||
func needsQuoting(v string) bool {
|
func needsQuoting(v string) bool {
|
||||||
// TODO: vectorize
|
// TODO: vectorize
|
||||||
for _, b := range []byte(v) {
|
for _, b := range []byte(v) {
|
||||||
if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) {
|
if b == '\'' || b == '\r' || b == '\n' || characters.InvalidASCII(b) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -517,12 +548,26 @@ func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byt
|
|||||||
del = 0x7f
|
del = 0x7f
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, r := range []byte(v) {
|
bv := []byte(v)
|
||||||
|
for i := 0; i < len(bv); i++ {
|
||||||
|
r := bv[i]
|
||||||
switch r {
|
switch r {
|
||||||
case '\\':
|
case '\\':
|
||||||
b = append(b, `\\`...)
|
b = append(b, `\\`...)
|
||||||
case '"':
|
case '"':
|
||||||
b = append(b, `\"`...)
|
if multiline {
|
||||||
|
// Quotation marks do not need to be quoted in multiline strings unless
|
||||||
|
// it contains 3 consecutive. If 3+ quotes appear, quote all of them
|
||||||
|
// because it's visually better
|
||||||
|
if i+2 > len(bv) || bv[i+1] != '"' || bv[i+2] != '"' {
|
||||||
|
b = append(b, r)
|
||||||
|
} else {
|
||||||
|
b = append(b, `\"\"\"`...)
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b = append(b, `\"`...)
|
||||||
|
}
|
||||||
case '\b':
|
case '\b':
|
||||||
b = append(b, `\b`...)
|
b = append(b, `\b`...)
|
||||||
case '\f':
|
case '\f':
|
||||||
@@ -559,9 +604,9 @@ func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte {
|
|||||||
return append(b, v...)
|
return append(b, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) {
|
func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) []byte {
|
||||||
if len(ctx.parentKey) == 0 {
|
if len(ctx.parentKey) == 0 {
|
||||||
return b, nil
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
|
||||||
@@ -581,10 +626,9 @@ func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error)
|
|||||||
|
|
||||||
b = append(b, "]\n"...)
|
b = append(b, "]\n"...)
|
||||||
|
|
||||||
return b, nil
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:cyclop
|
|
||||||
func (enc *Encoder) encodeKey(b []byte, k string) []byte {
|
func (enc *Encoder) encodeKey(b []byte, k string) []byte {
|
||||||
needsQuotation := false
|
needsQuotation := false
|
||||||
cannotUseLiteral := false
|
cannotUseLiteral := false
|
||||||
@@ -621,30 +665,33 @@ func (enc *Encoder) encodeKey(b []byte, k string) []byte {
|
|||||||
|
|
||||||
func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
|
func (enc *Encoder) keyToString(k reflect.Value) (string, error) {
|
||||||
keyType := k.Type()
|
keyType := k.Type()
|
||||||
switch {
|
if keyType.Implements(textMarshalerType) {
|
||||||
case keyType.Kind() == reflect.String:
|
|
||||||
return k.String(), nil
|
|
||||||
|
|
||||||
case keyType.Implements(textMarshalerType):
|
|
||||||
keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()
|
keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
|
return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err)
|
||||||
}
|
}
|
||||||
return string(keyB), nil
|
return string(keyB), nil
|
||||||
|
}
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
|
switch keyType.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return k.String(), nil
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return strconv.FormatInt(k.Int(), 10), nil
|
return strconv.FormatInt(k.Int(), 10), nil
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
return strconv.FormatUint(k.Uint(), 10), nil
|
return strconv.FormatUint(k.Uint(), 10), nil
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Float32:
|
case reflect.Float32:
|
||||||
return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil
|
return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Float64:
|
case reflect.Float64:
|
||||||
return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil
|
return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) {
|
||||||
@@ -657,8 +704,18 @@ func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte
|
|||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
v := iter.Value()
|
v := iter.Value()
|
||||||
|
|
||||||
if isNil(v) {
|
// Handle nil values: convert nil pointers to zero value,
|
||||||
continue
|
// skip nil interfaces and nil maps.
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
v = reflect.Zero(v.Type().Elem())
|
||||||
|
}
|
||||||
|
case reflect.Interface, reflect.Map:
|
||||||
|
if v.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
k, err := enc.keyToString(iter.Key())
|
k, err := enc.keyToString(iter.Key())
|
||||||
@@ -748,9 +805,8 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
|||||||
walkStruct(ctx, t, f.Elem())
|
walkStruct(ctx, t, f.Elem())
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
k = fieldType.Name
|
|
||||||
}
|
}
|
||||||
|
k = fieldType.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNil(f) {
|
if isNil(f) {
|
||||||
@@ -760,6 +816,7 @@ func walkStruct(ctx encoderCtx, t *table, v reflect.Value) {
|
|||||||
options := valueOptions{
|
options := valueOptions{
|
||||||
multiline: opts.multiline,
|
multiline: opts.multiline,
|
||||||
omitempty: opts.omitempty,
|
omitempty: opts.omitempty,
|
||||||
|
omitzero: opts.omitzero,
|
||||||
commented: opts.commented,
|
commented: opts.commented,
|
||||||
comment: fieldType.Tag.Get("comment"),
|
comment: fieldType.Tag.Get("comment"),
|
||||||
}
|
}
|
||||||
@@ -820,6 +877,7 @@ type tagOptions struct {
|
|||||||
multiline bool
|
multiline bool
|
||||||
inline bool
|
inline bool
|
||||||
omitempty bool
|
omitempty bool
|
||||||
|
omitzero bool
|
||||||
commented bool
|
commented bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,7 +890,7 @@ func parseTag(tag string) (string, tagOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
raw := tag[idx+1:]
|
raw := tag[idx+1:]
|
||||||
tag = string(tag[:idx])
|
tag = tag[:idx]
|
||||||
for raw != "" {
|
for raw != "" {
|
||||||
var o string
|
var o string
|
||||||
i := strings.Index(raw, ",")
|
i := strings.Index(raw, ",")
|
||||||
@@ -848,6 +906,8 @@ func parseTag(tag string) (string, tagOptions) {
|
|||||||
opts.inline = true
|
opts.inline = true
|
||||||
case "omitempty":
|
case "omitempty":
|
||||||
opts.omitempty = true
|
opts.omitempty = true
|
||||||
|
case "omitzero":
|
||||||
|
opts.omitzero = true
|
||||||
case "commented":
|
case "commented":
|
||||||
opts.commented = true
|
opts.commented = true
|
||||||
}
|
}
|
||||||
@@ -866,10 +926,7 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.skipTableHeader {
|
if !ctx.skipTableHeader {
|
||||||
b, err = enc.encodeTableHeader(ctx, b)
|
b = enc.encodeTableHeader(ctx, b)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if enc.indentTables && len(ctx.parentKey) > 0 {
|
if enc.indentTables && len(ctx.parentKey) > 0 {
|
||||||
ctx.indent++
|
ctx.indent++
|
||||||
@@ -882,6 +939,9 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
|||||||
if shouldOmitEmpty(kv.Options, kv.Value) {
|
if shouldOmitEmpty(kv.Options, kv.Value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if kv.Options.omitzero && shouldOmitZero(kv.Options, kv.Value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
hasNonEmptyKV = true
|
hasNonEmptyKV = true
|
||||||
|
|
||||||
ctx.setKey(kv.Key)
|
ctx.setKey(kv.Key)
|
||||||
@@ -901,6 +961,9 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
|
|||||||
if shouldOmitEmpty(table.Options, table.Value) {
|
if shouldOmitEmpty(table.Options, table.Value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if table.Options.omitzero && shouldOmitZero(table.Options, table.Value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if first {
|
if first {
|
||||||
first = false
|
first = false
|
||||||
if hasNonEmptyKV {
|
if hasNonEmptyKV {
|
||||||
@@ -935,6 +998,9 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
|
|||||||
if shouldOmitEmpty(kv.Options, kv.Value) {
|
if shouldOmitEmpty(kv.Options, kv.Value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if kv.Options.omitzero && shouldOmitZero(kv.Options, kv.Value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if first {
|
if first {
|
||||||
first = false
|
first = false
|
||||||
@@ -963,11 +1029,14 @@ func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
|
|||||||
if !v.IsValid() {
|
if !v.IsValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) {
|
t := v.Type()
|
||||||
|
if t == timeType || t.Implements(textMarshalerType) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(t).Implements(textMarshalerType) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
t := v.Type()
|
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
case reflect.Map, reflect.Struct:
|
case reflect.Map, reflect.Struct:
|
||||||
return !ctx.inline
|
return !ctx.inline
|
||||||
|
|||||||
+16
-9
@@ -1,7 +1,6 @@
|
|||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/tracker"
|
"github.com/pelletier/go-toml/v2/internal/tracker"
|
||||||
"github.com/pelletier/go-toml/v2/unstable"
|
"github.com/pelletier/go-toml/v2/unstable"
|
||||||
)
|
)
|
||||||
@@ -13,6 +12,9 @@ type strict struct {
|
|||||||
key tracker.KeyTracker
|
key tracker.KeyTracker
|
||||||
|
|
||||||
missing []unstable.ParserError
|
missing []unstable.ParserError
|
||||||
|
|
||||||
|
// Reference to the document for computing key ranges.
|
||||||
|
doc []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *strict) EnterTable(node *unstable.Node) {
|
func (s *strict) EnterTable(node *unstable.Node) {
|
||||||
@@ -53,7 +55,7 @@ func (s *strict) MissingTable(node *unstable.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.missing = append(s.missing, unstable.ParserError{
|
s.missing = append(s.missing, unstable.ParserError{
|
||||||
Highlight: keyLocation(node),
|
Highlight: s.keyLocation(node),
|
||||||
Message: "missing table",
|
Message: "missing table",
|
||||||
Key: s.key.Key(),
|
Key: s.key.Key(),
|
||||||
})
|
})
|
||||||
@@ -65,8 +67,8 @@ func (s *strict) MissingField(node *unstable.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.missing = append(s.missing, unstable.ParserError{
|
s.missing = append(s.missing, unstable.ParserError{
|
||||||
Highlight: keyLocation(node),
|
Highlight: s.keyLocation(node),
|
||||||
Message: "missing field",
|
Message: "unknown field",
|
||||||
Key: s.key.Key(),
|
Key: s.key.Key(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -88,7 +90,7 @@ func (s *strict) Error(doc []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyLocation(node *unstable.Node) []byte {
|
func (s *strict) keyLocation(node *unstable.Node) []byte {
|
||||||
k := node.Key()
|
k := node.Key()
|
||||||
|
|
||||||
hasOne := k.Next()
|
hasOne := k.Next()
|
||||||
@@ -96,12 +98,17 @@ func keyLocation(node *unstable.Node) []byte {
|
|||||||
panic("should not be called with empty key")
|
panic("should not be called with empty key")
|
||||||
}
|
}
|
||||||
|
|
||||||
start := k.Node().Data
|
// Get the range from the first key to the last key.
|
||||||
end := k.Node().Data
|
firstRaw := k.Node().Raw
|
||||||
|
lastRaw := firstRaw
|
||||||
|
|
||||||
for k.Next() {
|
for k.Next() {
|
||||||
end = k.Node().Data
|
lastRaw = k.Node().Raw
|
||||||
}
|
}
|
||||||
|
|
||||||
return danger.BytesRange(start, end)
|
// Compute the slice from the document using the ranges.
|
||||||
|
start := firstRaw.Offset
|
||||||
|
end := lastRaw.Offset + lastRaw.Length
|
||||||
|
|
||||||
|
return s.doc[start:end]
|
||||||
}
|
}
|
||||||
|
|||||||
+597
@@ -0,0 +1,597 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Go versions to test (1.11 through 1.26)
|
||||||
|
GO_VERSIONS=(
|
||||||
|
"1.11"
|
||||||
|
"1.12"
|
||||||
|
"1.13"
|
||||||
|
"1.14"
|
||||||
|
"1.15"
|
||||||
|
"1.16"
|
||||||
|
"1.17"
|
||||||
|
"1.18"
|
||||||
|
"1.19"
|
||||||
|
"1.20"
|
||||||
|
"1.21"
|
||||||
|
"1.22"
|
||||||
|
"1.23"
|
||||||
|
"1.24"
|
||||||
|
"1.25"
|
||||||
|
"1.26"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
PARALLEL=true
|
||||||
|
VERBOSE=false
|
||||||
|
OUTPUT_DIR="test-results"
|
||||||
|
DOCKER_TIMEOUT="10m"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: $0 [OPTIONS] [GO_VERSIONS...]
|
||||||
|
|
||||||
|
Test go-toml across multiple Go versions using Docker containers.
|
||||||
|
|
||||||
|
The script reports the lowest continuous supported Go version (where all subsequent
|
||||||
|
versions pass) and only exits with non-zero status if either of the two most recent
|
||||||
|
Go versions fail, indicating immediate attention is needed.
|
||||||
|
|
||||||
|
Note: For Go versions < 1.21, the script automatically updates go.mod to match the
|
||||||
|
target version, but older versions may still fail due to missing standard library
|
||||||
|
features (e.g., the 'slices' package introduced in Go 1.21).
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Show this help message
|
||||||
|
-s, --sequential Run tests sequentially instead of in parallel
|
||||||
|
-v, --verbose Enable verbose output
|
||||||
|
-o, --output DIR Output directory for test results (default: test-results)
|
||||||
|
-t, --timeout TIME Docker timeout for each test (default: 10m)
|
||||||
|
--list List available Go versions and exit
|
||||||
|
|
||||||
|
ARGUMENTS:
|
||||||
|
GO_VERSIONS Specific Go versions to test (default: all supported versions)
|
||||||
|
Examples: 1.21 1.22 1.23
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
$0 # Test all Go versions in parallel
|
||||||
|
$0 --sequential # Test all Go versions sequentially
|
||||||
|
$0 1.21 1.22 1.23 # Test specific versions
|
||||||
|
$0 --verbose --output ./results 1.25 1.26 # Verbose output to custom directory
|
||||||
|
|
||||||
|
EXIT CODES:
|
||||||
|
0 Recent Go versions pass (good compatibility)
|
||||||
|
1 Recent Go versions fail (needs attention) or script error
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo -e "${BLUE}[$(date +'%H:%M:%S')]${NC} $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[$(date +'%H:%M:%S')] ✓${NC} $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[$(date +'%H:%M:%S')] ✗${NC} $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[$(date +'%H:%M:%S')] ⚠${NC} $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-s|--sequential)
|
||||||
|
PARALLEL=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
VERBOSE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-o|--output)
|
||||||
|
OUTPUT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-t|--timeout)
|
||||||
|
DOCKER_TIMEOUT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--list)
|
||||||
|
echo "Available Go versions:"
|
||||||
|
printf '%s\n' "${GO_VERSIONS[@]}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Remaining arguments are Go versions
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# If specific versions provided, use those instead of defaults
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
GO_VERSIONS=("$@")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate Go versions
|
||||||
|
for version in "${GO_VERSIONS[@]}"; do
|
||||||
|
if ! [[ "$version" =~ ^1\.(1[1-9]|2[0-6])$ ]]; then
|
||||||
|
log_error "Invalid Go version: $version. Supported versions: 1.11-1.26"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if Docker is available
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
log_error "Docker is required but not installed or not in PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if Docker daemon is running
|
||||||
|
if ! docker info &> /dev/null; then
|
||||||
|
log_error "Docker daemon is not running"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Function to test a single Go version
|
||||||
|
test_go_version() {
|
||||||
|
local go_version="$1"
|
||||||
|
local container_name="go-toml-test-${go_version}"
|
||||||
|
local result_file="${OUTPUT_DIR}/go-${go_version}.txt"
|
||||||
|
local dockerfile_content
|
||||||
|
|
||||||
|
log "Testing Go $go_version..."
|
||||||
|
|
||||||
|
# Create a temporary Dockerfile for this version
|
||||||
|
# For Go versions < 1.21, we need to update go.mod to match the Go version
|
||||||
|
local needs_go_mod_update=false
|
||||||
|
if [[ $(echo "$go_version 1.21" | tr ' ' '\n' | sort -V | head -n1) == "$go_version" && "$go_version" != "1.21" ]]; then
|
||||||
|
needs_go_mod_update=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
dockerfile_content="FROM golang:${go_version}-alpine
|
||||||
|
|
||||||
|
# Install git (required for go mod)
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . ."
|
||||||
|
|
||||||
|
# Add go.mod update step for older Go versions
|
||||||
|
if [[ "$needs_go_mod_update" == true ]]; then
|
||||||
|
dockerfile_content="$dockerfile_content
|
||||||
|
|
||||||
|
# Update go.mod to match Go version (required for Go < 1.21)
|
||||||
|
RUN if [ -f go.mod ]; then sed -i 's/^go [0-9]\\+\\.[0-9]\\+\\(\\.[0-9]\\+\\)\\?/go $go_version/' go.mod; fi
|
||||||
|
|
||||||
|
# Note: Go versions < 1.21 may fail due to missing standard library packages (e.g., slices)
|
||||||
|
# This is expected for projects that use Go 1.21+ features"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dockerfile_content="$dockerfile_content
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
CMD [\"sh\", \"-c\", \"go version && echo '--- Running go test ./... ---' && go test ./...\"]"
|
||||||
|
|
||||||
|
# Create temporary directory for this test
|
||||||
|
local temp_dir
|
||||||
|
temp_dir=$(mktemp -d)
|
||||||
|
|
||||||
|
# Copy source to temp directory (excluding test results and git)
|
||||||
|
rsync -a --exclude="$OUTPUT_DIR" --exclude=".git" --exclude="*.test" . "$temp_dir/"
|
||||||
|
|
||||||
|
# Create Dockerfile in temp directory
|
||||||
|
echo "$dockerfile_content" > "$temp_dir/Dockerfile"
|
||||||
|
|
||||||
|
# Build and run container
|
||||||
|
local exit_code=0
|
||||||
|
local output
|
||||||
|
|
||||||
|
if $VERBOSE; then
|
||||||
|
log "Building Docker image for Go $go_version..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Capture both stdout and stderr, and the exit code
|
||||||
|
if output=$(cd "$temp_dir" && timeout "$DOCKER_TIMEOUT" docker build -t "$container_name" . 2>&1 && \
|
||||||
|
timeout "$DOCKER_TIMEOUT" docker run --rm "$container_name" 2>&1); then
|
||||||
|
log_success "Go $go_version: PASSED"
|
||||||
|
echo "PASSED" > "${result_file}.status"
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
log_error "Go $go_version: FAILED (exit code: $exit_code)"
|
||||||
|
echo "FAILED" > "${result_file}.status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save full output
|
||||||
|
echo "$output" > "$result_file"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker rmi "$container_name" &> /dev/null || true
|
||||||
|
rm -rf "$temp_dir"
|
||||||
|
|
||||||
|
if $VERBOSE; then
|
||||||
|
echo "--- Go $go_version output ---"
|
||||||
|
echo "$output"
|
||||||
|
echo "--- End Go $go_version output ---"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to run tests in parallel
|
||||||
|
run_parallel() {
|
||||||
|
local pids=()
|
||||||
|
local failed_versions=()
|
||||||
|
|
||||||
|
log "Starting parallel tests for ${#GO_VERSIONS[@]} Go versions..."
|
||||||
|
|
||||||
|
# Start all tests in background
|
||||||
|
for version in "${GO_VERSIONS[@]}"; do
|
||||||
|
test_go_version "$version" &
|
||||||
|
pids+=($!)
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait for all tests to complete
|
||||||
|
for i in "${!pids[@]}"; do
|
||||||
|
local pid=${pids[$i]}
|
||||||
|
local version=${GO_VERSIONS[$i]}
|
||||||
|
|
||||||
|
if ! wait $pid; then
|
||||||
|
failed_versions+=("$version")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return ${#failed_versions[@]}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to run tests sequentially
|
||||||
|
run_sequential() {
|
||||||
|
local failed_versions=()
|
||||||
|
|
||||||
|
log "Starting sequential tests for ${#GO_VERSIONS[@]} Go versions..."
|
||||||
|
|
||||||
|
for version in "${GO_VERSIONS[@]}"; do
|
||||||
|
if ! test_go_version "$version"; then
|
||||||
|
failed_versions+=("$version")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return ${#failed_versions[@]}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
local start_time
|
||||||
|
start_time=$(date +%s)
|
||||||
|
|
||||||
|
log "Starting Go version compatibility tests..."
|
||||||
|
log "Testing versions: ${GO_VERSIONS[*]}"
|
||||||
|
log "Output directory: $OUTPUT_DIR"
|
||||||
|
log "Parallel execution: $PARALLEL"
|
||||||
|
|
||||||
|
local failed_count
|
||||||
|
if $PARALLEL; then
|
||||||
|
run_parallel
|
||||||
|
failed_count=$?
|
||||||
|
else
|
||||||
|
run_sequential
|
||||||
|
failed_count=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
local end_time
|
||||||
|
end_time=$(date +%s)
|
||||||
|
local duration=$((end_time - start_time))
|
||||||
|
|
||||||
|
# Collect results for display
|
||||||
|
local passed_versions=()
|
||||||
|
local failed_versions=()
|
||||||
|
local unknown_versions=()
|
||||||
|
local passed_count=0
|
||||||
|
|
||||||
|
for version in "${GO_VERSIONS[@]}"; do
|
||||||
|
local status_file="${OUTPUT_DIR}/go-${version}.txt.status"
|
||||||
|
if [[ -f "$status_file" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(cat "$status_file")
|
||||||
|
if [[ "$status" == "PASSED" ]]; then
|
||||||
|
passed_versions+=("$version")
|
||||||
|
((passed_count++))
|
||||||
|
else
|
||||||
|
failed_versions+=("$version")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
unknown_versions+=("$version")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Generate summary report
|
||||||
|
local summary_file="${OUTPUT_DIR}/summary.txt"
|
||||||
|
{
|
||||||
|
echo "Go Version Compatibility Test Summary"
|
||||||
|
echo "====================================="
|
||||||
|
echo "Date: $(date)"
|
||||||
|
echo "Duration: ${duration}s"
|
||||||
|
echo "Parallel: $PARALLEL"
|
||||||
|
echo ""
|
||||||
|
echo "Results:"
|
||||||
|
|
||||||
|
for version in "${GO_VERSIONS[@]}"; do
|
||||||
|
local status_file="${OUTPUT_DIR}/go-${version}.txt.status"
|
||||||
|
if [[ -f "$status_file" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(cat "$status_file")
|
||||||
|
if [[ "$status" == "PASSED" ]]; then
|
||||||
|
echo " Go $version: ✓ PASSED"
|
||||||
|
else
|
||||||
|
echo " Go $version: ✗ FAILED"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Go $version: ? UNKNOWN (no status file)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Summary: $passed_count/${#GO_VERSIONS[@]} versions passed"
|
||||||
|
|
||||||
|
if [[ $failed_count -gt 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Failed versions details:"
|
||||||
|
for version in "${failed_versions[@]}"; do
|
||||||
|
echo ""
|
||||||
|
echo "--- Go $version (FAILED) ---"
|
||||||
|
local result_file="${OUTPUT_DIR}/go-${version}.txt"
|
||||||
|
if [[ -f "$result_file" ]]; then
|
||||||
|
tail -n 30 "$result_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
} > "$summary_file"
|
||||||
|
|
||||||
|
# Find lowest continuous supported version and check recent versions
|
||||||
|
local lowest_continuous_version=""
|
||||||
|
local recent_versions_failed=false
|
||||||
|
|
||||||
|
# Sort versions to ensure proper order
|
||||||
|
local sorted_versions=()
|
||||||
|
for version in "${GO_VERSIONS[@]}"; do
|
||||||
|
sorted_versions+=("$version")
|
||||||
|
done
|
||||||
|
# Sort versions numerically (1.11, 1.12, ..., 1.25)
|
||||||
|
IFS=$'\n' sorted_versions=($(sort -V <<< "${sorted_versions[*]}"))
|
||||||
|
|
||||||
|
# Find lowest continuous supported version (all versions from this point onwards pass)
|
||||||
|
for version in "${sorted_versions[@]}"; do
|
||||||
|
local status_file="${OUTPUT_DIR}/go-${version}.txt.status"
|
||||||
|
local all_subsequent_pass=true
|
||||||
|
|
||||||
|
# Check if this version and all subsequent versions pass
|
||||||
|
local found_current=false
|
||||||
|
for check_version in "${sorted_versions[@]}"; do
|
||||||
|
if [[ "$check_version" == "$version" ]]; then
|
||||||
|
found_current=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$found_current" == true ]]; then
|
||||||
|
local check_status_file="${OUTPUT_DIR}/go-${check_version}.txt.status"
|
||||||
|
if [[ -f "$check_status_file" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(cat "$check_status_file")
|
||||||
|
if [[ "$status" != "PASSED" ]]; then
|
||||||
|
all_subsequent_pass=false
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
all_subsequent_pass=false
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$all_subsequent_pass" == true ]]; then
|
||||||
|
lowest_continuous_version="$version"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if the two most recent versions failed
|
||||||
|
local num_versions=${#sorted_versions[@]}
|
||||||
|
if [[ $num_versions -ge 2 ]]; then
|
||||||
|
local second_recent="${sorted_versions[$((num_versions-2))]}"
|
||||||
|
local most_recent="${sorted_versions[$((num_versions-1))]}"
|
||||||
|
|
||||||
|
local second_recent_status_file="${OUTPUT_DIR}/go-${second_recent}.txt.status"
|
||||||
|
local most_recent_status_file="${OUTPUT_DIR}/go-${most_recent}.txt.status"
|
||||||
|
|
||||||
|
local second_recent_failed=false
|
||||||
|
local most_recent_failed=false
|
||||||
|
|
||||||
|
if [[ -f "$second_recent_status_file" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(cat "$second_recent_status_file")
|
||||||
|
if [[ "$status" != "PASSED" ]]; then
|
||||||
|
second_recent_failed=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
second_recent_failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$most_recent_status_file" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(cat "$most_recent_status_file")
|
||||||
|
if [[ "$status" != "PASSED" ]]; then
|
||||||
|
most_recent_failed=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
most_recent_failed=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$second_recent_failed" == true || "$most_recent_failed" == true ]]; then
|
||||||
|
recent_versions_failed=true
|
||||||
|
fi
|
||||||
|
elif [[ $num_versions -eq 1 ]]; then
|
||||||
|
# Only one version tested, check if it's the most recent and failed
|
||||||
|
local only_version="${sorted_versions[0]}"
|
||||||
|
local only_status_file="${OUTPUT_DIR}/go-${only_version}.txt.status"
|
||||||
|
|
||||||
|
if [[ -f "$only_status_file" ]]; then
|
||||||
|
local status
|
||||||
|
status=$(cat "$only_status_file")
|
||||||
|
if [[ "$status" != "PASSED" ]]; then
|
||||||
|
recent_versions_failed=true
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
recent_versions_failed=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display summary
|
||||||
|
echo ""
|
||||||
|
log "Test completed in ${duration}s"
|
||||||
|
log "Summary report: $summary_file"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo " FINAL RESULTS"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Display passed versions
|
||||||
|
if [[ ${#passed_versions[@]} -gt 0 ]]; then
|
||||||
|
log_success "PASSED (${#passed_versions[@]}/${#GO_VERSIONS[@]}):"
|
||||||
|
# Sort passed versions for display
|
||||||
|
local sorted_passed=()
|
||||||
|
for version in "${sorted_versions[@]}"; do
|
||||||
|
for passed_version in "${passed_versions[@]}"; do
|
||||||
|
if [[ "$version" == "$passed_version" ]]; then
|
||||||
|
sorted_passed+=("$version")
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
for version in "${sorted_passed[@]}"; do
|
||||||
|
echo -e " ${GREEN}✓${NC} Go $version"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display failed versions
|
||||||
|
if [[ ${#failed_versions[@]} -gt 0 ]]; then
|
||||||
|
log_error "FAILED (${#failed_versions[@]}/${#GO_VERSIONS[@]}):"
|
||||||
|
# Sort failed versions for display
|
||||||
|
local sorted_failed=()
|
||||||
|
for version in "${sorted_versions[@]}"; do
|
||||||
|
for failed_version in "${failed_versions[@]}"; do
|
||||||
|
if [[ "$version" == "$failed_version" ]]; then
|
||||||
|
sorted_failed+=("$version")
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
for version in "${sorted_failed[@]}"; do
|
||||||
|
echo -e " ${RED}✗${NC} Go $version"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show failure details
|
||||||
|
echo "========================================"
|
||||||
|
echo " FAILURE DETAILS"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for version in "${sorted_failed[@]}"; do
|
||||||
|
echo -e "${RED}--- Go $version FAILURE LOGS (last 30 lines) ---${NC}"
|
||||||
|
local result_file="${OUTPUT_DIR}/go-${version}.txt"
|
||||||
|
if [[ -f "$result_file" ]]; then
|
||||||
|
tail -n 30 "$result_file" | sed 's/^/ /'
|
||||||
|
else
|
||||||
|
echo " No log file found: $result_file"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display unknown versions
|
||||||
|
if [[ ${#unknown_versions[@]} -gt 0 ]]; then
|
||||||
|
log_warning "UNKNOWN (${#unknown_versions[@]}/${#GO_VERSIONS[@]}):"
|
||||||
|
for version in "${unknown_versions[@]}"; do
|
||||||
|
echo -e " ${YELLOW}?${NC} Go $version (no status file)"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " COMPATIBILITY SUMMARY"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ -n "$lowest_continuous_version" ]]; then
|
||||||
|
log_success "Lowest continuous supported version: Go $lowest_continuous_version"
|
||||||
|
echo " (All versions from Go $lowest_continuous_version onwards pass)"
|
||||||
|
else
|
||||||
|
log_error "No continuous version support found"
|
||||||
|
echo " (No version has all subsequent versions passing)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo "Full detailed logs available in: $OUTPUT_DIR"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# Determine exit code based on recent versions
|
||||||
|
if [[ "$recent_versions_failed" == true ]]; then
|
||||||
|
log_error "OVERALL RESULT: Recent Go versions failed - this needs attention!"
|
||||||
|
if [[ -n "$lowest_continuous_version" ]]; then
|
||||||
|
echo "Note: Continuous support starts from Go $lowest_continuous_version"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
log_success "OVERALL RESULT: Recent Go versions pass - compatibility looks good!"
|
||||||
|
if [[ -n "$lowest_continuous_version" ]]; then
|
||||||
|
echo "Continuous support starts from Go $lowest_continuous_version"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap to clean up on exit
|
||||||
|
cleanup() {
|
||||||
|
# Kill any remaining background processes
|
||||||
|
jobs -p | xargs -r kill 2>/dev/null || true
|
||||||
|
|
||||||
|
# Clean up any remaining Docker containers
|
||||||
|
docker ps -q --filter "name=go-toml-test-" | xargs -r docker stop 2>/dev/null || true
|
||||||
|
docker images -q --filter "reference=go-toml-test-*" | xargs -r docker rmi 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main
|
||||||
+15
-6
@@ -6,9 +6,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
// isZeroer is used to check if a type has a custom IsZero method.
|
||||||
var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
// This allows custom types to define their own zero-value semantics.
|
||||||
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
type isZeroer interface {
|
||||||
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil))
|
IsZero() bool
|
||||||
var sliceInterfaceType = reflect.TypeOf([]interface{}(nil))
|
}
|
||||||
var stringType = reflect.TypeOf("")
|
|
||||||
|
var (
|
||||||
|
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||||
|
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||||
|
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||||
|
isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem()
|
||||||
|
mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil))
|
||||||
|
sliceInterfaceType = reflect.TypeOf([]interface{}(nil))
|
||||||
|
stringType = reflect.TypeOf("")
|
||||||
|
)
|
||||||
|
|||||||
+176
-52
@@ -12,7 +12,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/tracker"
|
"github.com/pelletier/go-toml/v2/internal/tracker"
|
||||||
"github.com/pelletier/go-toml/v2/unstable"
|
"github.com/pelletier/go-toml/v2/unstable"
|
||||||
)
|
)
|
||||||
@@ -57,13 +56,18 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
|
|||||||
|
|
||||||
// EnableUnmarshalerInterface allows to enable unmarshaler interface.
|
// EnableUnmarshalerInterface allows to enable unmarshaler interface.
|
||||||
//
|
//
|
||||||
// With this feature enabled, types implementing the unstable/Unmarshaler
|
// With this feature enabled, types implementing the unstable.Unmarshaler
|
||||||
// interface can be decoded from any structure of the document. It allows types
|
// interface can be decoded from any structure of the document. It allows types
|
||||||
// that don't have a straightforward TOML representation to provide their own
|
// that don't have a straightforward TOML representation to provide their own
|
||||||
// decoding logic.
|
// decoding logic.
|
||||||
//
|
//
|
||||||
// Currently, types can only decode from a single value. Tables and array tables
|
// The UnmarshalTOML method receives raw TOML bytes:
|
||||||
// are not supported.
|
// - For single values: the raw value bytes (e.g., `"hello"` for a string)
|
||||||
|
// - For tables: all key-value lines belonging to that table
|
||||||
|
// - For inline tables/arrays: the raw bytes of the inline structure
|
||||||
|
//
|
||||||
|
// The unstable.RawMessage type can be used to capture raw TOML bytes for
|
||||||
|
// later processing, similar to json.RawMessage.
|
||||||
//
|
//
|
||||||
// *Unstable:* This method does not follow the compatibility guarantees of
|
// *Unstable:* This method does not follow the compatibility guarantees of
|
||||||
// semver. It can be changed or removed without a new major version being
|
// semver. It can be changed or removed without a new major version being
|
||||||
@@ -123,6 +127,7 @@ func (d *Decoder) Decode(v interface{}) error {
|
|||||||
dec := decoder{
|
dec := decoder{
|
||||||
strict: strict{
|
strict: strict{
|
||||||
Enabled: d.strict,
|
Enabled: d.strict,
|
||||||
|
doc: b,
|
||||||
},
|
},
|
||||||
unmarshalerInterface: d.unmarshalerInterface,
|
unmarshalerInterface: d.unmarshalerInterface,
|
||||||
}
|
}
|
||||||
@@ -226,7 +231,7 @@ func (d *decoder) FromParser(v interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.IsNil() {
|
if r.IsNil() {
|
||||||
return fmt.Errorf("toml: decoding pointer target cannot be nil")
|
return errors.New("toml: decoding pointer target cannot be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.Elem()
|
r = r.Elem()
|
||||||
@@ -273,7 +278,7 @@ func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) err
|
|||||||
var err error
|
var err error
|
||||||
var first bool // used for to clear array tables on first use
|
var first bool // used for to clear array tables on first use
|
||||||
|
|
||||||
if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) {
|
if !d.skipUntilTable || expr.Kind != unstable.KeyValue {
|
||||||
first, err = d.seen.CheckExpression(expr)
|
first, err = d.seen.CheckExpression(expr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -378,7 +383,7 @@ func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflec
|
|||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
idx := d.arrayIndex(true, v)
|
idx := d.arrayIndex(true, v)
|
||||||
if idx >= v.Len() {
|
if idx >= v.Len() {
|
||||||
return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx)
|
return v, fmt.Errorf("%w at position %d", d.typeMismatchError("array table", v.Type()), idx)
|
||||||
}
|
}
|
||||||
elem := v.Index(idx)
|
elem := v.Index(idx)
|
||||||
_, err := d.handleArrayTable(key, elem)
|
_, err := d.handleArrayTable(key, elem)
|
||||||
@@ -416,27 +421,51 @@ func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Va
|
|||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
elem := v.Index(v.Len() - 1)
|
// Create a new element when the slice is empty; otherwise operate on
|
||||||
|
// the last element.
|
||||||
|
var (
|
||||||
|
elem reflect.Value
|
||||||
|
created bool
|
||||||
|
)
|
||||||
|
if v.Len() == 0 {
|
||||||
|
created = true
|
||||||
|
elemType := v.Type().Elem()
|
||||||
|
if elemType.Kind() == reflect.Interface {
|
||||||
|
elem = makeMapStringInterface()
|
||||||
|
} else {
|
||||||
|
elem = reflect.New(elemType).Elem()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elem = v.Index(v.Len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
x, err := d.handleArrayTable(key, elem)
|
x, err := d.handleArrayTable(key, elem)
|
||||||
if err != nil || d.skipUntilTable {
|
if err != nil || d.skipUntilTable {
|
||||||
return reflect.Value{}, err
|
return reflect.Value{}, err
|
||||||
}
|
}
|
||||||
if x.IsValid() {
|
if x.IsValid() {
|
||||||
elem.Set(x)
|
if created {
|
||||||
|
elem = x
|
||||||
|
} else {
|
||||||
|
elem.Set(x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
return reflect.Append(v, elem), nil
|
||||||
|
}
|
||||||
return v, err
|
return v, err
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
idx := d.arrayIndex(false, v)
|
idx := d.arrayIndex(false, v)
|
||||||
if idx >= v.Len() {
|
if idx >= v.Len() {
|
||||||
return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx)
|
return v, fmt.Errorf("%w at position %d", d.typeMismatchError("array table", v.Type()), idx)
|
||||||
}
|
}
|
||||||
elem := v.Index(idx)
|
elem := v.Index(idx)
|
||||||
_, err := d.handleArrayTable(key, elem)
|
_, err := d.handleArrayTable(key, elem)
|
||||||
return v, err
|
return v, err
|
||||||
|
default:
|
||||||
|
return d.handleArrayTable(key, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.handleArrayTable(key, v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) {
|
func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) {
|
||||||
@@ -470,7 +499,8 @@ func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn h
|
|||||||
|
|
||||||
mv := v.MapIndex(mk)
|
mv := v.MapIndex(mk)
|
||||||
set := false
|
set := false
|
||||||
if !mv.IsValid() {
|
switch {
|
||||||
|
case !mv.IsValid():
|
||||||
// If there is no value in the map, create a new one according to
|
// If there is no value in the map, create a new one according to
|
||||||
// the map type. If the element type is interface, create either a
|
// the map type. If the element type is interface, create either a
|
||||||
// map[string]interface{} or a []interface{} depending on whether
|
// map[string]interface{} or a []interface{} depending on whether
|
||||||
@@ -483,13 +513,13 @@ func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn h
|
|||||||
mv = reflect.New(t).Elem()
|
mv = reflect.New(t).Elem()
|
||||||
}
|
}
|
||||||
set = true
|
set = true
|
||||||
} else if mv.Kind() == reflect.Interface {
|
case mv.Kind() == reflect.Interface:
|
||||||
mv = mv.Elem()
|
mv = mv.Elem()
|
||||||
if !mv.IsValid() {
|
if !mv.IsValid() {
|
||||||
mv = makeFn()
|
mv = makeFn()
|
||||||
}
|
}
|
||||||
set = true
|
set = true
|
||||||
} else if !mv.CanAddr() {
|
case !mv.CanAddr():
|
||||||
vt := v.Type()
|
vt := v.Type()
|
||||||
t := vt.Elem()
|
t := vt.Elem()
|
||||||
oldmv := mv
|
oldmv := mv
|
||||||
@@ -574,18 +604,28 @@ func (d *decoder) handleArrayTablePart(key unstable.Iterator, v reflect.Value) (
|
|||||||
// cannot handle it.
|
// cannot handle it.
|
||||||
func (d *decoder) handleTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
|
func (d *decoder) handleTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
|
||||||
if v.Kind() == reflect.Slice {
|
if v.Kind() == reflect.Slice {
|
||||||
if v.Len() == 0 {
|
// For non-empty slices, work with the last element
|
||||||
return reflect.Value{}, unstable.NewParserError(key.Node().Data, "cannot store a table in a slice")
|
if v.Len() > 0 {
|
||||||
|
elem := v.Index(v.Len() - 1)
|
||||||
|
x, err := d.handleTable(key, elem)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Value{}, err
|
||||||
|
}
|
||||||
|
if x.IsValid() {
|
||||||
|
elem.Set(x)
|
||||||
|
}
|
||||||
|
return reflect.Value{}, nil
|
||||||
}
|
}
|
||||||
elem := v.Index(v.Len() - 1)
|
// Empty slice - check if it implements Unmarshaler (e.g., RawMessage)
|
||||||
x, err := d.handleTable(key, elem)
|
// and we're at the end of the key path
|
||||||
if err != nil {
|
if d.unmarshalerInterface && !key.Next() {
|
||||||
return reflect.Value{}, err
|
if v.CanAddr() && v.Addr().CanInterface() {
|
||||||
|
if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok {
|
||||||
|
return d.handleKeyValuesUnmarshaler(outi)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if x.IsValid() {
|
return reflect.Value{}, unstable.NewParserError(key.Node().Data, "cannot store a table in a slice")
|
||||||
elem.Set(x)
|
|
||||||
}
|
|
||||||
return reflect.Value{}, nil
|
|
||||||
}
|
}
|
||||||
if key.Next() {
|
if key.Next() {
|
||||||
// Still scoping the key
|
// Still scoping the key
|
||||||
@@ -599,6 +639,24 @@ func (d *decoder) handleTable(key unstable.Iterator, v reflect.Value) (reflect.V
|
|||||||
// Handle root expressions until the end of the document or the next
|
// Handle root expressions until the end of the document or the next
|
||||||
// non-key-value.
|
// non-key-value.
|
||||||
func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
|
func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
|
||||||
|
// Check if target implements Unmarshaler before processing key-values.
|
||||||
|
// This allows types to handle entire tables themselves.
|
||||||
|
if d.unmarshalerInterface {
|
||||||
|
vv := v
|
||||||
|
for vv.Kind() == reflect.Ptr {
|
||||||
|
if vv.IsNil() {
|
||||||
|
vv.Set(reflect.New(vv.Type().Elem()))
|
||||||
|
}
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
if vv.CanAddr() && vv.Addr().CanInterface() {
|
||||||
|
if outi, ok := vv.Addr().Interface().(unstable.Unmarshaler); ok {
|
||||||
|
// Collect all key-value expressions for this table
|
||||||
|
return d.handleKeyValuesUnmarshaler(outi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var rv reflect.Value
|
var rv reflect.Value
|
||||||
for d.nextExpr() {
|
for d.nextExpr() {
|
||||||
expr := d.expr()
|
expr := d.expr()
|
||||||
@@ -628,6 +686,41 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
|
|||||||
return rv, nil
|
return rv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleKeyValuesUnmarshaler collects all key-value expressions for a table
|
||||||
|
// and passes them to the Unmarshaler as raw TOML bytes.
|
||||||
|
func (d *decoder) handleKeyValuesUnmarshaler(u unstable.Unmarshaler) (reflect.Value, error) {
|
||||||
|
// Collect raw bytes from all key-value expressions for this table.
|
||||||
|
// We use the Raw field on each KeyValue expression to preserve the
|
||||||
|
// original formatting (whitespace, quoting style, etc.) from the document.
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
for d.nextExpr() {
|
||||||
|
expr := d.expr()
|
||||||
|
if expr.Kind != unstable.KeyValue {
|
||||||
|
d.stashExpr()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := d.seen.CheckExpression(expr)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the raw bytes from the original document to preserve formatting
|
||||||
|
if expr.Raw.Length > 0 {
|
||||||
|
raw := d.p.Raw(expr.Raw)
|
||||||
|
buf = append(buf, raw...)
|
||||||
|
}
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.UnmarshalTOML(buf); err != nil {
|
||||||
|
return reflect.Value{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.Value{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
handlerFn func(key unstable.Iterator, v reflect.Value) (reflect.Value, error)
|
handlerFn func(key unstable.Iterator, v reflect.Value) (reflect.Value, error)
|
||||||
valueMakerFn func() reflect.Value
|
valueMakerFn func() reflect.Value
|
||||||
@@ -672,14 +765,21 @@ func (d *decoder) handleValue(value *unstable.Node, v reflect.Value) error {
|
|||||||
if d.unmarshalerInterface {
|
if d.unmarshalerInterface {
|
||||||
if v.CanAddr() && v.Addr().CanInterface() {
|
if v.CanAddr() && v.Addr().CanInterface() {
|
||||||
if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok {
|
if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok {
|
||||||
return outi.UnmarshalTOML(value)
|
// Pass raw bytes from the original document
|
||||||
|
return outi.UnmarshalTOML(d.p.Raw(value.Raw))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := d.tryTextUnmarshaler(value, v)
|
// Only try TextUnmarshaler for scalar types. For Array and InlineTable,
|
||||||
if ok || err != nil {
|
// fall through to struct/map unmarshaling to allow flexible unmarshaling
|
||||||
return err
|
// where a type can implement UnmarshalText for string values but still
|
||||||
|
// be populated field-by-field from a table. See issue #974.
|
||||||
|
if value.Kind != unstable.Array && value.Kind != unstable.InlineTable {
|
||||||
|
ok, err := d.tryTextUnmarshaler(value, v)
|
||||||
|
if ok || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch value.Kind {
|
switch value.Kind {
|
||||||
@@ -821,6 +921,9 @@ func (d *decoder) unmarshalDateTime(value *unstable.Node, v reflect.Value) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Interface && v.Type() != timeType {
|
||||||
|
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("datetime", v.Type()))
|
||||||
|
}
|
||||||
v.Set(reflect.ValueOf(dt))
|
v.Set(reflect.ValueOf(dt))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -831,14 +934,14 @@ func (d *decoder) unmarshalLocalDate(value *unstable.Node, v reflect.Value) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Interface && v.Type() != timeType {
|
||||||
|
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local date", v.Type()))
|
||||||
|
}
|
||||||
if v.Type() == timeType {
|
if v.Type() == timeType {
|
||||||
cast := ld.AsTime(time.Local)
|
v.Set(reflect.ValueOf(ld.AsTime(time.Local)))
|
||||||
v.Set(reflect.ValueOf(cast))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Set(reflect.ValueOf(ld))
|
v.Set(reflect.ValueOf(ld))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,6 +955,9 @@ func (d *decoder) unmarshalLocalTime(value *unstable.Node, v reflect.Value) erro
|
|||||||
return unstable.NewParserError(rest, "extra characters at the end of a local time")
|
return unstable.NewParserError(rest, "extra characters at the end of a local time")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Interface {
|
||||||
|
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local time", v.Type()))
|
||||||
|
}
|
||||||
v.Set(reflect.ValueOf(lt))
|
v.Set(reflect.ValueOf(lt))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -866,15 +972,14 @@ func (d *decoder) unmarshalLocalDateTime(value *unstable.Node, v reflect.Value)
|
|||||||
return unstable.NewParserError(rest, "extra characters at the end of a local date time")
|
return unstable.NewParserError(rest, "extra characters at the end of a local date time")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Interface && v.Type() != timeType {
|
||||||
|
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local datetime", v.Type()))
|
||||||
|
}
|
||||||
if v.Type() == timeType {
|
if v.Type() == timeType {
|
||||||
cast := ldt.AsTime(time.Local)
|
v.Set(reflect.ValueOf(ldt.AsTime(time.Local)))
|
||||||
|
|
||||||
v.Set(reflect.ValueOf(cast))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Set(reflect.ValueOf(ldt))
|
v.Set(reflect.ValueOf(ldt))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,8 +1034,9 @@ const (
|
|||||||
// compile time, so it is computed during initialization.
|
// compile time, so it is computed during initialization.
|
||||||
var maxUint int64 = math.MaxInt64
|
var maxUint int64 = math.MaxInt64
|
||||||
|
|
||||||
func init() {
|
func init() { //nolint:gochecknoinits
|
||||||
m := uint64(^uint(0))
|
m := uint64(^uint(0))
|
||||||
|
// #nosec G115
|
||||||
if m < uint64(maxUint) {
|
if m < uint64(maxUint) {
|
||||||
maxUint = int64(m)
|
maxUint = int64(m)
|
||||||
}
|
}
|
||||||
@@ -1010,7 +1116,7 @@ func (d *decoder) unmarshalInteger(value *unstable.Node, v reflect.Value) error
|
|||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
r = reflect.ValueOf(i)
|
r = reflect.ValueOf(i)
|
||||||
default:
|
default:
|
||||||
return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("integer", v.Type()))
|
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("integer", v.Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.Type().AssignableTo(v.Type()) {
|
if !r.Type().AssignableTo(v.Type()) {
|
||||||
@@ -1029,7 +1135,7 @@ func (d *decoder) unmarshalString(value *unstable.Node, v reflect.Value) error {
|
|||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
v.Set(reflect.ValueOf(string(value.Data)))
|
v.Set(reflect.ValueOf(string(value.Data)))
|
||||||
default:
|
default:
|
||||||
return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("string", v.Type()))
|
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("string", v.Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1080,35 +1186,39 @@ func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value,
|
|||||||
return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err)
|
return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err)
|
||||||
}
|
}
|
||||||
return mk.Elem(), nil
|
return mk.Elem(), nil
|
||||||
|
}
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64:
|
switch keyType.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
key, err := strconv.ParseInt(string(data), 10, 64)
|
key, err := strconv.ParseInt(string(data), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err)
|
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(key).Convert(keyType), nil
|
return reflect.ValueOf(key).Convert(keyType), nil
|
||||||
case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
key, err := strconv.ParseUint(string(data), 10, 64)
|
key, err := strconv.ParseUint(string(data), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err)
|
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(key).Convert(keyType), nil
|
return reflect.ValueOf(key).Convert(keyType), nil
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Float32:
|
case reflect.Float32:
|
||||||
key, err := strconv.ParseFloat(string(data), 32)
|
key, err := strconv.ParseFloat(string(data), 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
|
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(float32(key)), nil
|
return reflect.ValueOf(float32(key)), nil
|
||||||
|
|
||||||
case keyType.Kind() == reflect.Float64:
|
case reflect.Float64:
|
||||||
key, err := strconv.ParseFloat(string(data), 64)
|
key, err := strconv.ParseFloat(string(data), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
|
return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err)
|
||||||
}
|
}
|
||||||
return reflect.ValueOf(float64(key)), nil
|
return reflect.ValueOf(float64(key)), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType)
|
||||||
}
|
}
|
||||||
return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) {
|
func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) {
|
||||||
@@ -1154,6 +1264,18 @@ func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node
|
|||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
path, found := structFieldPath(v, string(key.Node().Data))
|
path, found := structFieldPath(v, string(key.Node().Data))
|
||||||
if !found {
|
if !found {
|
||||||
|
// If no matching struct field is found but the target implements the
|
||||||
|
// unstable.Unmarshaler interface (and it is enabled), delegate the
|
||||||
|
// decoding of this value to the custom unmarshaler.
|
||||||
|
if d.unmarshalerInterface {
|
||||||
|
if v.CanAddr() && v.Addr().CanInterface() {
|
||||||
|
if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok {
|
||||||
|
// Pass raw bytes from the original document
|
||||||
|
return reflect.Value{}, outi.UnmarshalTOML(d.p.Raw(value.Raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, keep previous behavior and skip until the next table.
|
||||||
d.skipUntilTable = true
|
d.skipUntilTable = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1259,13 +1381,13 @@ func fieldByIndex(v reflect.Value, path []int) reflect.Value {
|
|||||||
|
|
||||||
type fieldPathsMap = map[string][]int
|
type fieldPathsMap = map[string][]int
|
||||||
|
|
||||||
var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap
|
var globalFieldPathsCache atomic.Value // map[reflect.Type]fieldPathsMap
|
||||||
|
|
||||||
func structFieldPath(v reflect.Value, name string) ([]int, bool) {
|
func structFieldPath(v reflect.Value, name string) ([]int, bool) {
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
|
|
||||||
cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap)
|
cache, _ := globalFieldPathsCache.Load().(map[reflect.Type]fieldPathsMap)
|
||||||
fieldPaths, ok := cache[danger.MakeTypeID(t)]
|
fieldPaths, ok := cache[t]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
fieldPaths = map[string][]int{}
|
fieldPaths = map[string][]int{}
|
||||||
@@ -1276,8 +1398,8 @@ func structFieldPath(v reflect.Value, name string) ([]int, bool) {
|
|||||||
fieldPaths[strings.ToLower(name)] = path
|
fieldPaths[strings.ToLower(name)] = path
|
||||||
})
|
})
|
||||||
|
|
||||||
newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1)
|
newCache := make(map[reflect.Type]fieldPathsMap, len(cache)+1)
|
||||||
newCache[danger.MakeTypeID(t)] = fieldPaths
|
newCache[t] = fieldPaths
|
||||||
for k, v := range cache {
|
for k, v := range cache {
|
||||||
newCache[k] = v
|
newCache[k] = v
|
||||||
}
|
}
|
||||||
@@ -1301,7 +1423,9 @@ func forEachField(t reflect.Type, path []int, do func(name string, path []int))
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldPath := append(path, i)
|
fieldPath := make([]int, 0, len(path)+1)
|
||||||
|
fieldPath = append(fieldPath, path...)
|
||||||
|
fieldPath = append(fieldPath, i)
|
||||||
fieldPath = fieldPath[:len(fieldPath):len(fieldPath)]
|
fieldPath = fieldPath[:len(fieldPath):len(fieldPath)]
|
||||||
|
|
||||||
name := f.Tag.Get("toml")
|
name := f.Tag.Get("toml")
|
||||||
|
|||||||
+42
-29
@@ -1,10 +1,8 @@
|
|||||||
package unstable
|
package unstable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Iterator over a sequence of nodes.
|
// Iterator over a sequence of nodes.
|
||||||
@@ -19,30 +17,43 @@ import (
|
|||||||
// // do something with n
|
// // do something with n
|
||||||
// }
|
// }
|
||||||
type Iterator struct {
|
type Iterator struct {
|
||||||
|
nodes *[]Node
|
||||||
|
idx int32
|
||||||
started bool
|
started bool
|
||||||
node *Node
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next moves the iterator forward and returns true if points to a
|
// Next moves the iterator forward and returns true if points to a
|
||||||
// node, false otherwise.
|
// node, false otherwise.
|
||||||
func (c *Iterator) Next() bool {
|
func (c *Iterator) Next() bool {
|
||||||
|
if c.nodes == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
nodes := *c.nodes
|
||||||
if !c.started {
|
if !c.started {
|
||||||
c.started = true
|
c.started = true
|
||||||
} else if c.node.Valid() {
|
} else {
|
||||||
c.node = c.node.Next()
|
idx := c.idx
|
||||||
|
if idx >= 0 && int(idx) < len(nodes) {
|
||||||
|
c.idx = nodes[idx].next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return c.node.Valid()
|
return c.idx >= 0 && int(c.idx) < len(nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLast returns true if the current node of the iterator is the last
|
// IsLast returns true if the current node of the iterator is the last
|
||||||
// one. Subsequent calls to Next() will return false.
|
// one. Subsequent calls to Next() will return false.
|
||||||
func (c *Iterator) IsLast() bool {
|
func (c *Iterator) IsLast() bool {
|
||||||
return c.node.next == 0
|
return c.nodes == nil || c.idx < 0 || (*c.nodes)[c.idx].next < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node returns a pointer to the node pointed at by the iterator.
|
// Node returns a pointer to the node pointed at by the iterator.
|
||||||
func (c *Iterator) Node() *Node {
|
func (c *Iterator) Node() *Node {
|
||||||
return c.node
|
if c.nodes == nil || c.idx < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
n := &(*c.nodes)[c.idx]
|
||||||
|
n.nodes = c.nodes
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node in a TOML expression AST.
|
// Node in a TOML expression AST.
|
||||||
@@ -65,11 +76,12 @@ type Node struct {
|
|||||||
Raw Range // Raw bytes from the input.
|
Raw Range // Raw bytes from the input.
|
||||||
Data []byte // Node value (either allocated or referencing the input).
|
Data []byte // Node value (either allocated or referencing the input).
|
||||||
|
|
||||||
// References to other nodes, as offsets in the backing array
|
// Absolute indices into the backing nodes slice. -1 means none.
|
||||||
// from this node. References can go backward, so those can be
|
next int32
|
||||||
// negative.
|
child int32
|
||||||
next int // 0 if last element
|
|
||||||
child int // 0 if no child
|
// Reference to the backing nodes slice for navigation.
|
||||||
|
nodes *[]Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range of bytes in the document.
|
// Range of bytes in the document.
|
||||||
@@ -80,24 +92,24 @@ type Range struct {
|
|||||||
|
|
||||||
// Next returns a pointer to the next node, or nil if there is no next node.
|
// Next returns a pointer to the next node, or nil if there is no next node.
|
||||||
func (n *Node) Next() *Node {
|
func (n *Node) Next() *Node {
|
||||||
if n.next == 0 {
|
if n.next < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ptr := unsafe.Pointer(n)
|
next := &(*n.nodes)[n.next]
|
||||||
size := unsafe.Sizeof(Node{})
|
next.nodes = n.nodes
|
||||||
return (*Node)(danger.Stride(ptr, size, n.next))
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
// Child returns a pointer to the first child node of this node. Other children
|
// Child returns a pointer to the first child node of this node. Other children
|
||||||
// can be accessed calling Next on the first child. Returns an nil if this Node
|
// can be accessed calling Next on the first child. Returns nil if this Node
|
||||||
// has no child.
|
// has no child.
|
||||||
func (n *Node) Child() *Node {
|
func (n *Node) Child() *Node {
|
||||||
if n.child == 0 {
|
if n.child < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ptr := unsafe.Pointer(n)
|
child := &(*n.nodes)[n.child]
|
||||||
size := unsafe.Sizeof(Node{})
|
child.nodes = n.nodes
|
||||||
return (*Node)(danger.Stride(ptr, size, n.child))
|
return child
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid returns true if the node's kind is set (not to Invalid).
|
// Valid returns true if the node's kind is set (not to Invalid).
|
||||||
@@ -111,13 +123,14 @@ func (n *Node) Valid() bool {
|
|||||||
func (n *Node) Key() Iterator {
|
func (n *Node) Key() Iterator {
|
||||||
switch n.Kind {
|
switch n.Kind {
|
||||||
case KeyValue:
|
case KeyValue:
|
||||||
value := n.Child()
|
child := n.child
|
||||||
if !value.Valid() {
|
if child < 0 {
|
||||||
panic(fmt.Errorf("KeyValue should have at least two children"))
|
panic(errors.New("KeyValue should have at least two children"))
|
||||||
}
|
}
|
||||||
return Iterator{node: value.Next()}
|
valueNode := &(*n.nodes)[child]
|
||||||
|
return Iterator{nodes: n.nodes, idx: valueNode.next}
|
||||||
case Table, ArrayTable:
|
case Table, ArrayTable:
|
||||||
return Iterator{node: n.Child()}
|
return Iterator{nodes: n.nodes, idx: n.child}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("Key() is not supported on a %s", n.Kind))
|
panic(fmt.Errorf("Key() is not supported on a %s", n.Kind))
|
||||||
}
|
}
|
||||||
@@ -132,5 +145,5 @@ func (n *Node) Value() *Node {
|
|||||||
|
|
||||||
// Children returns an iterator over a node's children.
|
// Children returns an iterator over a node's children.
|
||||||
func (n *Node) Children() Iterator {
|
func (n *Node) Children() Iterator {
|
||||||
return Iterator{node: n.Child()}
|
return Iterator{nodes: n.nodes, idx: n.child}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-17
@@ -7,15 +7,6 @@ type root struct {
|
|||||||
nodes []Node
|
nodes []Node
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterator over the top level nodes.
|
|
||||||
func (r *root) Iterator() Iterator {
|
|
||||||
it := Iterator{}
|
|
||||||
if len(r.nodes) > 0 {
|
|
||||||
it.node = &r.nodes[0]
|
|
||||||
}
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *root) at(idx reference) *Node {
|
func (r *root) at(idx reference) *Node {
|
||||||
return &r.nodes[idx]
|
return &r.nodes[idx]
|
||||||
}
|
}
|
||||||
@@ -33,12 +24,10 @@ type builder struct {
|
|||||||
lastIdx int
|
lastIdx int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) Tree() *root {
|
|
||||||
return &b.tree
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) NodeAt(ref reference) *Node {
|
func (b *builder) NodeAt(ref reference) *Node {
|
||||||
return b.tree.at(ref)
|
n := b.tree.at(ref)
|
||||||
|
n.nodes = &b.tree.nodes
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) Reset() {
|
func (b *builder) Reset() {
|
||||||
@@ -48,24 +37,28 @@ func (b *builder) Reset() {
|
|||||||
|
|
||||||
func (b *builder) Push(n Node) reference {
|
func (b *builder) Push(n Node) reference {
|
||||||
b.lastIdx = len(b.tree.nodes)
|
b.lastIdx = len(b.tree.nodes)
|
||||||
|
n.next = -1
|
||||||
|
n.child = -1
|
||||||
b.tree.nodes = append(b.tree.nodes, n)
|
b.tree.nodes = append(b.tree.nodes, n)
|
||||||
return reference(b.lastIdx)
|
return reference(b.lastIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) PushAndChain(n Node) reference {
|
func (b *builder) PushAndChain(n Node) reference {
|
||||||
newIdx := len(b.tree.nodes)
|
newIdx := len(b.tree.nodes)
|
||||||
|
n.next = -1
|
||||||
|
n.child = -1
|
||||||
b.tree.nodes = append(b.tree.nodes, n)
|
b.tree.nodes = append(b.tree.nodes, n)
|
||||||
if b.lastIdx >= 0 {
|
if b.lastIdx >= 0 {
|
||||||
b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx
|
b.tree.nodes[b.lastIdx].next = int32(newIdx) //nolint:gosec // TOML ASTs are small
|
||||||
}
|
}
|
||||||
b.lastIdx = newIdx
|
b.lastIdx = newIdx
|
||||||
return reference(b.lastIdx)
|
return reference(b.lastIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) AttachChild(parent reference, child reference) {
|
func (b *builder) AttachChild(parent reference, child reference) {
|
||||||
b.tree.nodes[parent].child = int(child) - int(parent)
|
b.tree.nodes[parent].child = int32(child) //nolint:gosec // TOML ASTs are small
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) Chain(from reference, to reference) {
|
func (b *builder) Chain(from reference, to reference) {
|
||||||
b.tree.nodes[from].next = int(to) - int(from)
|
b.tree.nodes[from].next = int32(to) //nolint:gosec // TOML ASTs are small
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-4
@@ -6,28 +6,40 @@ import "fmt"
|
|||||||
type Kind int
|
type Kind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Meta
|
// Invalid represents an invalid meta node.
|
||||||
Invalid Kind = iota
|
Invalid Kind = iota
|
||||||
|
// Comment represents a comment meta node.
|
||||||
Comment
|
Comment
|
||||||
|
// Key represents a key meta node.
|
||||||
Key
|
Key
|
||||||
|
|
||||||
// Top level structures
|
// Table represents a top-level table.
|
||||||
Table
|
Table
|
||||||
|
// ArrayTable represents a top-level array table.
|
||||||
ArrayTable
|
ArrayTable
|
||||||
|
// KeyValue represents a top-level key value.
|
||||||
KeyValue
|
KeyValue
|
||||||
|
|
||||||
// Containers values
|
// Array represents an array container value.
|
||||||
Array
|
Array
|
||||||
|
// InlineTable represents an inline table container value.
|
||||||
InlineTable
|
InlineTable
|
||||||
|
|
||||||
// Values
|
// String represents a string value.
|
||||||
String
|
String
|
||||||
|
// Bool represents a boolean value.
|
||||||
Bool
|
Bool
|
||||||
|
// Float represents a floating point value.
|
||||||
Float
|
Float
|
||||||
|
// Integer represents an integer value.
|
||||||
Integer
|
Integer
|
||||||
|
// LocalDate represents a a local date value.
|
||||||
LocalDate
|
LocalDate
|
||||||
|
// LocalTime represents a local time value.
|
||||||
LocalTime
|
LocalTime
|
||||||
|
// LocalDateTime represents a local date/time value.
|
||||||
LocalDateTime
|
LocalDateTime
|
||||||
|
// DateTime represents a data/time value.
|
||||||
DateTime
|
DateTime
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+71
-41
@@ -3,10 +3,10 @@ package unstable
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2/internal/characters"
|
"github.com/pelletier/go-toml/v2/internal/characters"
|
||||||
"github.com/pelletier/go-toml/v2/internal/danger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParserError describes an error relative to the content of the document.
|
// ParserError describes an error relative to the content of the document.
|
||||||
@@ -70,11 +70,34 @@ func (p *Parser) Data() []byte {
|
|||||||
// panics.
|
// panics.
|
||||||
func (p *Parser) Range(b []byte) Range {
|
func (p *Parser) Range(b []byte) Range {
|
||||||
return Range{
|
return Range{
|
||||||
Offset: uint32(danger.SubsliceOffset(p.data, b)),
|
Offset: uint32(p.subsliceOffset(b)), //nolint:gosec // TOML documents are small
|
||||||
Length: uint32(len(b)),
|
Length: uint32(len(b)), //nolint:gosec // TOML documents are small
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rangeOfToken computes the Range of a token given the remaining bytes after the token.
|
||||||
|
// This is used when the token was extracted from the beginning of some position,
|
||||||
|
// and 'rest' is what remains after the token.
|
||||||
|
func (p *Parser) rangeOfToken(token, rest []byte) Range {
|
||||||
|
offset := len(p.data) - len(token) - len(rest)
|
||||||
|
return Range{Offset: uint32(offset), Length: uint32(len(token))} //nolint:gosec // TOML documents are small
|
||||||
|
}
|
||||||
|
|
||||||
|
// subsliceOffset returns the byte offset of subslice b within p.data.
|
||||||
|
// b must share the same backing array as p.data.
|
||||||
|
func (p *Parser) subsliceOffset(b []byte) int {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return len(p.data)
|
||||||
|
}
|
||||||
|
dataPtr := reflect.ValueOf(p.data).Pointer()
|
||||||
|
subPtr := reflect.ValueOf(b).Pointer()
|
||||||
|
offset := int(subPtr - dataPtr)
|
||||||
|
if offset < 0 || offset > len(p.data) {
|
||||||
|
panic("subslice is not within data")
|
||||||
|
}
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
// Raw returns the slice corresponding to the bytes in the given range.
|
// Raw returns the slice corresponding to the bytes in the given range.
|
||||||
func (p *Parser) Raw(raw Range) []byte {
|
func (p *Parser) Raw(raw Range) []byte {
|
||||||
return p.data[raw.Offset : raw.Offset+raw.Length]
|
return p.data[raw.Offset : raw.Offset+raw.Length]
|
||||||
@@ -158,9 +181,17 @@ type Shape struct {
|
|||||||
End Position
|
End Position
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) position(b []byte) Position {
|
// Shape returns the shape of the given range in the input. Will
|
||||||
offset := danger.SubsliceOffset(p.data, b)
|
// panic if the range is not a subslice of the input.
|
||||||
|
func (p *Parser) Shape(r Range) Shape {
|
||||||
|
return Shape{
|
||||||
|
Start: p.positionAt(int(r.Offset)),
|
||||||
|
End: p.positionAt(int(r.Offset + r.Length)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// positionAt returns the position at the given byte offset in the document.
|
||||||
|
func (p *Parser) positionAt(offset int) Position {
|
||||||
lead := p.data[:offset]
|
lead := p.data[:offset]
|
||||||
|
|
||||||
return Position{
|
return Position{
|
||||||
@@ -170,16 +201,6 @@ func (p *Parser) position(b []byte) Position {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shape returns the shape of the given range in the input. Will
|
|
||||||
// panic if the range is not a subslice of the input.
|
|
||||||
func (p *Parser) Shape(r Range) Shape {
|
|
||||||
raw := p.Raw(r)
|
|
||||||
return Shape{
|
|
||||||
Start: p.position(raw),
|
|
||||||
End: p.position(raw[r.Length:]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parser) parseNewline(b []byte) ([]byte, error) {
|
func (p *Parser) parseNewline(b []byte) ([]byte, error) {
|
||||||
if b[0] == '\n' {
|
if b[0] == '\n' {
|
||||||
return b[1:], nil
|
return b[1:], nil
|
||||||
@@ -199,7 +220,7 @@ func (p *Parser) parseComment(b []byte) (reference, []byte, error) {
|
|||||||
if p.KeepComments && err == nil {
|
if p.KeepComments && err == nil {
|
||||||
ref = p.builder.Push(Node{
|
ref = p.builder.Push(Node{
|
||||||
Kind: Comment,
|
Kind: Comment,
|
||||||
Raw: p.Range(data),
|
Raw: p.rangeOfToken(data, rest),
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -316,6 +337,9 @@ func (p *Parser) parseStdTable(b []byte) (reference, []byte, error) {
|
|||||||
|
|
||||||
func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
|
func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
|
||||||
// keyval = key keyval-sep val
|
// keyval = key keyval-sep val
|
||||||
|
// Track the start position for Raw range
|
||||||
|
startB := b
|
||||||
|
|
||||||
ref := p.builder.Push(Node{
|
ref := p.builder.Push(Node{
|
||||||
Kind: KeyValue,
|
Kind: KeyValue,
|
||||||
})
|
})
|
||||||
@@ -330,7 +354,7 @@ func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
|
|||||||
b = p.parseWhitespace(b)
|
b = p.parseWhitespace(b)
|
||||||
|
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return invalidReference, nil, NewParserError(b, "expected = after a key, but the document ends there")
|
return invalidReference, nil, NewParserError(startB[:len(startB)-len(b)], "expected = after a key, but the document ends there")
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err = expect('=', b)
|
b, err = expect('=', b)
|
||||||
@@ -348,6 +372,11 @@ func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
|
|||||||
p.builder.Chain(valRef, key)
|
p.builder.Chain(valRef, key)
|
||||||
p.builder.AttachChild(ref, valRef)
|
p.builder.AttachChild(ref, valRef)
|
||||||
|
|
||||||
|
// Set Raw to span the entire key-value expression.
|
||||||
|
// Access the node directly in the slice to avoid the write barrier
|
||||||
|
// that NodeAt's nodes-pointer setup would trigger.
|
||||||
|
p.builder.tree.nodes[ref].Raw = p.rangeOfToken(startB[:len(startB)-len(b)], b)
|
||||||
|
|
||||||
return ref, b, err
|
return ref, b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +405,7 @@ func (p *Parser) parseVal(b []byte) (reference, []byte, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ref = p.builder.Push(Node{
|
ref = p.builder.Push(Node{
|
||||||
Kind: String,
|
Kind: String,
|
||||||
Raw: p.Range(raw),
|
Raw: p.rangeOfToken(raw, b),
|
||||||
Data: v,
|
Data: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -394,7 +423,7 @@ func (p *Parser) parseVal(b []byte) (reference, []byte, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ref = p.builder.Push(Node{
|
ref = p.builder.Push(Node{
|
||||||
Kind: String,
|
Kind: String,
|
||||||
Raw: p.Range(raw),
|
Raw: p.rangeOfToken(raw, b),
|
||||||
Data: v,
|
Data: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -456,7 +485,7 @@ func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
|
|||||||
// inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ]
|
// inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ]
|
||||||
parent := p.builder.Push(Node{
|
parent := p.builder.Push(Node{
|
||||||
Kind: InlineTable,
|
Kind: InlineTable,
|
||||||
Raw: p.Range(b[:1]),
|
Raw: p.rangeOfToken(b[:1], b[1:]),
|
||||||
})
|
})
|
||||||
|
|
||||||
first := true
|
first := true
|
||||||
@@ -542,7 +571,7 @@ func (p *Parser) parseValArray(b []byte) (reference, []byte, error) {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
for len(b) > 0 {
|
for len(b) > 0 {
|
||||||
cref := invalidReference
|
var cref reference
|
||||||
cref, b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
cref, b, err = p.parseOptionalWhitespaceCommentNewline(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return parent, nil, err
|
return parent, nil, err
|
||||||
@@ -611,12 +640,13 @@ func (p *Parser) parseOptionalWhitespaceCommentNewline(b []byte) (reference, []b
|
|||||||
latestCommentRef := invalidReference
|
latestCommentRef := invalidReference
|
||||||
|
|
||||||
addComment := func(ref reference) {
|
addComment := func(ref reference) {
|
||||||
if rootCommentRef == invalidReference {
|
switch {
|
||||||
|
case rootCommentRef == invalidReference:
|
||||||
rootCommentRef = ref
|
rootCommentRef = ref
|
||||||
} else if latestCommentRef == invalidReference {
|
case latestCommentRef == invalidReference:
|
||||||
p.builder.AttachChild(rootCommentRef, ref)
|
p.builder.AttachChild(rootCommentRef, ref)
|
||||||
latestCommentRef = ref
|
latestCommentRef = ref
|
||||||
} else {
|
default:
|
||||||
p.builder.Chain(latestCommentRef, ref)
|
p.builder.Chain(latestCommentRef, ref)
|
||||||
latestCommentRef = ref
|
latestCommentRef = ref
|
||||||
}
|
}
|
||||||
@@ -704,11 +734,11 @@ func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er
|
|||||||
|
|
||||||
if !escaped {
|
if !escaped {
|
||||||
str := token[startIdx:endIdx]
|
str := token[startIdx:endIdx]
|
||||||
verr := characters.Utf8TomlValidAlreadyEscaped(str)
|
highlight := characters.Utf8TomlValidAlreadyEscaped(str)
|
||||||
if verr.Zero() {
|
if len(highlight) == 0 {
|
||||||
return token, str, rest, nil
|
return token, str, rest, nil
|
||||||
}
|
}
|
||||||
return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8")
|
return nil, nil, nil, NewParserError(highlight, "invalid UTF-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder bytes.Buffer
|
var builder bytes.Buffer
|
||||||
@@ -744,7 +774,7 @@ func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er
|
|||||||
i += j
|
i += j
|
||||||
for ; i < len(token)-3; i++ {
|
for ; i < len(token)-3; i++ {
|
||||||
c := token[i]
|
c := token[i]
|
||||||
if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') {
|
if c != '\n' && c != '\r' && c != ' ' && c != '\t' {
|
||||||
i--
|
i--
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -820,7 +850,7 @@ func (p *Parser) parseKey(b []byte) (reference, []byte, error) {
|
|||||||
|
|
||||||
ref := p.builder.Push(Node{
|
ref := p.builder.Push(Node{
|
||||||
Kind: Key,
|
Kind: Key,
|
||||||
Raw: p.Range(raw),
|
Raw: p.rangeOfToken(raw, b),
|
||||||
Data: key,
|
Data: key,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -836,7 +866,7 @@ func (p *Parser) parseKey(b []byte) (reference, []byte, error) {
|
|||||||
|
|
||||||
p.builder.PushAndChain(Node{
|
p.builder.PushAndChain(Node{
|
||||||
Kind: Key,
|
Kind: Key,
|
||||||
Raw: p.Range(raw),
|
Raw: p.rangeOfToken(raw, b),
|
||||||
Data: key,
|
Data: key,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -897,11 +927,11 @@ func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
|
|||||||
// validate the string and return a direct reference to the buffer.
|
// validate the string and return a direct reference to the buffer.
|
||||||
if !escaped {
|
if !escaped {
|
||||||
str := token[startIdx:endIdx]
|
str := token[startIdx:endIdx]
|
||||||
verr := characters.Utf8TomlValidAlreadyEscaped(str)
|
highlight := characters.Utf8TomlValidAlreadyEscaped(str)
|
||||||
if verr.Zero() {
|
if len(highlight) == 0 {
|
||||||
return token, str, rest, nil
|
return token, str, rest, nil
|
||||||
}
|
}
|
||||||
return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8")
|
return nil, nil, nil, NewParserError(highlight, "invalid UTF-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
i := startIdx
|
i := startIdx
|
||||||
@@ -972,7 +1002,7 @@ func hexToRune(b []byte, length int) (rune, error) {
|
|||||||
|
|
||||||
var r uint32
|
var r uint32
|
||||||
for i, c := range b {
|
for i, c := range b {
|
||||||
d := uint32(0)
|
var d uint32
|
||||||
switch {
|
switch {
|
||||||
case '0' <= c && c <= '9':
|
case '0' <= c && c <= '9':
|
||||||
d = uint32(c - '0')
|
d = uint32(c - '0')
|
||||||
@@ -1013,7 +1043,7 @@ func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error)
|
|||||||
return p.builder.Push(Node{
|
return p.builder.Push(Node{
|
||||||
Kind: Float,
|
Kind: Float,
|
||||||
Data: b[:3],
|
Data: b[:3],
|
||||||
Raw: p.Range(b[:3]),
|
Raw: p.rangeOfToken(b[:3], b[3:]),
|
||||||
}), b[3:], nil
|
}), b[3:], nil
|
||||||
case 'n':
|
case 'n':
|
||||||
if !scanFollowsNan(b) {
|
if !scanFollowsNan(b) {
|
||||||
@@ -1023,7 +1053,7 @@ func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error)
|
|||||||
return p.builder.Push(Node{
|
return p.builder.Push(Node{
|
||||||
Kind: Float,
|
Kind: Float,
|
||||||
Data: b[:3],
|
Data: b[:3],
|
||||||
Raw: p.Range(b[:3]),
|
Raw: p.rangeOfToken(b[:3], b[3:]),
|
||||||
}), b[3:], nil
|
}), b[3:], nil
|
||||||
case '+', '-':
|
case '+', '-':
|
||||||
return p.scanIntOrFloat(b)
|
return p.scanIntOrFloat(b)
|
||||||
@@ -1076,7 +1106,7 @@ byteLoop:
|
|||||||
}
|
}
|
||||||
case c == 'T' || c == 't' || c == ':' || c == '.':
|
case c == 'T' || c == 't' || c == ':' || c == '.':
|
||||||
hasTime = true
|
hasTime = true
|
||||||
case c == '+' || c == '-' || c == 'Z' || c == 'z':
|
case c == '+' || c == 'Z' || c == 'z':
|
||||||
hasTz = true
|
hasTz = true
|
||||||
case c == ' ':
|
case c == ' ':
|
||||||
if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) {
|
if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) {
|
||||||
@@ -1148,7 +1178,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||||||
return p.builder.Push(Node{
|
return p.builder.Push(Node{
|
||||||
Kind: Integer,
|
Kind: Integer,
|
||||||
Data: b[:i],
|
Data: b[:i],
|
||||||
Raw: p.Range(b[:i]),
|
Raw: p.rangeOfToken(b[:i], b[i:]),
|
||||||
}), b[i:], nil
|
}), b[i:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1172,7 +1202,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||||||
return p.builder.Push(Node{
|
return p.builder.Push(Node{
|
||||||
Kind: Float,
|
Kind: Float,
|
||||||
Data: b[:i+3],
|
Data: b[:i+3],
|
||||||
Raw: p.Range(b[:i+3]),
|
Raw: p.rangeOfToken(b[:i+3], b[i+3:]),
|
||||||
}), b[i+3:], nil
|
}), b[i+3:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1184,7 +1214,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||||||
return p.builder.Push(Node{
|
return p.builder.Push(Node{
|
||||||
Kind: Float,
|
Kind: Float,
|
||||||
Data: b[:i+3],
|
Data: b[:i+3],
|
||||||
Raw: p.Range(b[:i+3]),
|
Raw: p.rangeOfToken(b[:i+3], b[i+3:]),
|
||||||
}), b[i+3:], nil
|
}), b[i+3:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,7 +1237,7 @@ func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
|
|||||||
return p.builder.Push(Node{
|
return p.builder.Push(Node{
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Data: b[:i],
|
Data: b[:i],
|
||||||
Raw: p.Range(b[:i]),
|
Raw: p.rangeOfToken(b[:i], b[i:]),
|
||||||
}), b[i:], nil
|
}), b[i:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-3
@@ -1,7 +1,32 @@
|
|||||||
package unstable
|
package unstable
|
||||||
|
|
||||||
// The Unmarshaler interface may be implemented by types to customize their
|
// Unmarshaler is implemented by types that can unmarshal a TOML
|
||||||
// behavior when being unmarshaled from a TOML document.
|
// description of themselves. The input is a valid TOML document
|
||||||
|
// containing the relevant portion of the parsed document.
|
||||||
|
//
|
||||||
|
// For tables (including split tables defined in multiple places),
|
||||||
|
// the data contains the raw key-value bytes from the original document
|
||||||
|
// with adjusted table headers to be relative to the unmarshaling target.
|
||||||
type Unmarshaler interface {
|
type Unmarshaler interface {
|
||||||
UnmarshalTOML(value *Node) error
|
UnmarshalTOML(data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawMessage is a raw encoded TOML value. It implements Unmarshaler
|
||||||
|
// and can be used to delay TOML decoding or capture raw content.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// type Config struct {
|
||||||
|
// Plugin RawMessage `toml:"plugin"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var cfg Config
|
||||||
|
// toml.NewDecoder(r).EnableUnmarshalerInterface().Decode(&cfg)
|
||||||
|
// // cfg.Plugin now contains the raw TOML bytes for [plugin]
|
||||||
|
type RawMessage []byte
|
||||||
|
|
||||||
|
// UnmarshalTOML implements Unmarshaler.
|
||||||
|
func (m *RawMessage) UnmarshalTOML(data []byte) error {
|
||||||
|
*m = append((*m)[0:0], data...)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
bin/
|
||||||
+23
-20
@@ -1,32 +1,35 @@
|
|||||||
run:
|
version: "2"
|
||||||
timeout: 30m
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- gofmt
|
- depguard
|
||||||
- govet
|
- govet
|
||||||
- goimports
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- misspell
|
- misspell
|
||||||
- unused
|
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- typecheck
|
- unused
|
||||||
disable-all: true
|
settings:
|
||||||
|
depguard:
|
||||||
|
rules:
|
||||||
|
main:
|
||||||
|
deny:
|
||||||
|
- pkg: io/ioutil
|
||||||
|
desc: The io/ioutil package has been deprecated.
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- .*\.pb\.go$
|
||||||
|
|
||||||
linters-settings:
|
formatters:
|
||||||
depguard:
|
enable:
|
||||||
rules:
|
- gofmt
|
||||||
main:
|
- goimports
|
||||||
deny:
|
exclusions:
|
||||||
# The io/ioutil package has been deprecated.
|
generated: lax
|
||||||
# https://go.dev/doc/go1.16#ioutil
|
paths:
|
||||||
- pkg: "io/ioutil"
|
- .*\.pb\.go$
|
||||||
desc: The io/ioutil package has been deprecated.
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-files:
|
|
||||||
- ".*\\.pb\\.go$"
|
|
||||||
|
|
||||||
# show all
|
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
|||||||
+54
-3
@@ -1,11 +1,12 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.23
|
ARG GO_VERSION=1.26
|
||||||
ARG XX_VERSION=1.6.1
|
ARG ALPINE_VERSION=3.23
|
||||||
|
ARG XX_VERSION=1.9.0
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS base
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
COPY --from=xx / /
|
COPY --from=xx / /
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
@@ -35,4 +36,54 @@ COPY --from=test /tmp/coverage.txt /coverage-root.txt
|
|||||||
FROM scratch AS test-noroot-coverage
|
FROM scratch AS test-noroot-coverage
|
||||||
COPY --from=test-noroot /tmp/coverage.txt /coverage-noroot.txt
|
COPY --from=test-noroot /tmp/coverage.txt /coverage-noroot.txt
|
||||||
|
|
||||||
|
FROM base AS bench-base
|
||||||
|
WORKDIR /app
|
||||||
|
RUN --mount=type=bind,source=go.mod,target=/app/go.mod \
|
||||||
|
--mount=type=bind,source=go.sum,target=/app/go.sum \
|
||||||
|
--mount=type=cache,target=/root/.cache \
|
||||||
|
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||||
|
set -ex
|
||||||
|
apk add --no-cache rsync
|
||||||
|
go install tool github.com/jstemmer/go-junit-report/v2
|
||||||
|
EOT
|
||||||
|
|
||||||
|
FROM bench-base AS bench
|
||||||
|
WORKDIR /src
|
||||||
|
ARG BENCH_FILE_SIZE
|
||||||
|
RUN --mount=target=. \
|
||||||
|
--mount=target=/go/pkg/mod,type=cache \
|
||||||
|
--mount=target=/root/.cache,type=cache <<EOT
|
||||||
|
set -ex
|
||||||
|
set -o pipefail
|
||||||
|
mkdir -p /tmp/bench-results
|
||||||
|
CGO_ENABLED=0 xx-go test -benchmem -bench=. -run=^$ . 2>&1 | tee /tmp/fsutil.log
|
||||||
|
go-junit-report -in /tmp/fsutil.log -out /tmp/bench-results/fsutil.junit.xml
|
||||||
|
cd bench
|
||||||
|
CGO_ENABLED=0 xx-go test -benchmem -bench=. -run=^$ . 2>&1 | tee /tmp/bench.log
|
||||||
|
go-junit-report -in /tmp/bench.log -out /tmp/bench-results/bench.junit.xml
|
||||||
|
EOT
|
||||||
|
|
||||||
|
FROM bench-base AS bench-noroot
|
||||||
|
WORKDIR /src
|
||||||
|
RUN mkdir -p /go/pkg && chmod 0777 /go/pkg
|
||||||
|
USER 1000:1000
|
||||||
|
ARG BENCH_FILE_SIZE
|
||||||
|
RUN --mount=target=. \
|
||||||
|
--mount=target=/tmp/.cache,type=cache <<EOT
|
||||||
|
set -ex
|
||||||
|
set -o pipefail
|
||||||
|
mkdir -p /tmp/bench-results
|
||||||
|
CGO_ENABLED=0 GOCACHE=/tmp/gocache xx-go test -bench=. -benchmem -run=^$ . 2>&1 | tee /tmp/fsutil.log
|
||||||
|
go-junit-report -in /tmp/fsutil.log -out /tmp/bench-results/fsutil.junit.xml
|
||||||
|
cd bench
|
||||||
|
CGO_ENABLED=0 GOCACHE=/tmp/gocache xx-go test -bench=. -benchmem -run=^$ . 2>&1 | tee /tmp/bench.log
|
||||||
|
go-junit-report -in /tmp/bench.log -out /tmp/bench-results/bench.junit.xml
|
||||||
|
EOT
|
||||||
|
|
||||||
|
FROM scratch AS bench-root-results
|
||||||
|
COPY --from=bench /tmp/bench-results /bench-root
|
||||||
|
|
||||||
|
FROM scratch AS bench-noroot-results
|
||||||
|
COPY --from=bench-noroot /tmp/bench-results /bench-noroot
|
||||||
|
|
||||||
FROM build
|
FROM build
|
||||||
|
|||||||
+3
@@ -10,3 +10,6 @@ coverage:
|
|||||||
|
|
||||||
github_checks:
|
github_checks:
|
||||||
annotations: false
|
annotations: false
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
- "**/*.pb.go"
|
||||||
|
|||||||
+2
-3
@@ -21,7 +21,7 @@ import (
|
|||||||
const defaultDirectoryMode = 0755
|
const defaultDirectoryMode = 0755
|
||||||
|
|
||||||
var bufferPool = &sync.Pool{
|
var bufferPool = &sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
buffer := make([]byte, 32*1024)
|
buffer := make([]byte, 32*1024)
|
||||||
return &buffer
|
return &buffer
|
||||||
},
|
},
|
||||||
@@ -738,8 +738,7 @@ func rel(basepath, targpath string) (string, error) {
|
|||||||
// filepath.Rel can't handle UUID paths in windows
|
// filepath.Rel can't handle UUID paths in windows
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
pfx := basepath + `\`
|
pfx := basepath + `\`
|
||||||
if strings.HasPrefix(targpath, pfx) {
|
if p, ok := strings.CutPrefix(targpath, pfx); ok {
|
||||||
p := strings.TrimPrefix(targpath, pfx)
|
|
||||||
if p == "" {
|
if p == "" {
|
||||||
p = "."
|
p = "."
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-8
@@ -3,7 +3,10 @@ package fsutil
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"hash"
|
"hash"
|
||||||
|
gofs "io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tonistiigi/fsutil/types"
|
"github.com/tonistiigi/fsutil/types"
|
||||||
@@ -11,26 +14,41 @@ import (
|
|||||||
|
|
||||||
type walkerFn func(ctx context.Context, pathC chan<- *currentPath) error
|
type walkerFn func(ctx context.Context, pathC chan<- *currentPath) error
|
||||||
|
|
||||||
func Changes(ctx context.Context, a, b walkerFn, changeFn ChangeFunc) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandleChangeFn func(ChangeKind, string, os.FileInfo, error) error
|
type HandleChangeFn func(ChangeKind, string, os.FileInfo, error) error
|
||||||
|
|
||||||
type ContentHasher func(*types.Stat) (hash.Hash, error)
|
type ContentHasher func(*types.Stat) (hash.Hash, error)
|
||||||
|
|
||||||
func getWalkerFn(root string) walkerFn {
|
func getWalkerFn(root string) walkerFn {
|
||||||
|
return getFSWalkerFn(func() (FS, error) {
|
||||||
|
return NewFS(root)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRootWalkerFn(root Root) walkerFn {
|
||||||
|
return getFSWalkerFn(func() (FS, error) {
|
||||||
|
return NewRootFS(root), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFSWalkerFn(newFS func() (FS, error)) walkerFn {
|
||||||
return func(ctx context.Context, pathC chan<- *currentPath) error {
|
return func(ctx context.Context, pathC chan<- *currentPath) error {
|
||||||
return errors.Wrap(Walk(ctx, root, nil, func(path string, f os.FileInfo, err error) error {
|
fs, err := newFS()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to walk")
|
||||||
|
}
|
||||||
|
return errors.Wrap(fs.Walk(ctx, "/", func(path string, entry gofs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, ok := f.Sys().(*types.Stat)
|
fi, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stat, ok := fi.Sys().(*types.Stat)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.Errorf("%T invalid file without stat information", f.Sys())
|
return errors.Errorf("%T invalid file without stat information", fi.Sys())
|
||||||
}
|
}
|
||||||
|
|
||||||
p := ¤tPath{
|
p := ¤tPath{
|
||||||
path: path,
|
path: path,
|
||||||
stat: stat,
|
stat: stat,
|
||||||
@@ -46,6 +64,46 @@ func getWalkerFn(root string) walkerFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mkrootstat(root Root, relpath string, fi os.FileInfo, inodemap map[uint64]string) (*types.Stat, error) {
|
||||||
|
stat := &types.Stat{
|
||||||
|
Path: filepath.FromSlash(filepath.ToSlash(relpath)),
|
||||||
|
Mode: uint32(fi.Mode()),
|
||||||
|
ModTime: fi.ModTime().UnixNano(),
|
||||||
|
}
|
||||||
|
|
||||||
|
setUnixOpt(fi, stat, relpath, inodemap)
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
stat.Size = fi.Size()
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
link, err := root.Readlink(relpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
stat.Linkname = link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fi.IsDir() || fi.Mode().IsRegular() {
|
||||||
|
if err := loadRootXattr(root, relpath, stat); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
permPart := stat.Mode & uint32(os.ModePerm)
|
||||||
|
noPermPart := stat.Mode &^ uint32(os.ModePerm)
|
||||||
|
// Add the x bit: make everything +x from windows
|
||||||
|
permPart |= 0111
|
||||||
|
permPart &= 0755
|
||||||
|
stat.Mode = noPermPart | permPart
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the socket bit since archive/tar.FileInfoHeader does not handle it
|
||||||
|
stat.Mode &^= uint32(os.ModeSocket)
|
||||||
|
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
func emptyWalker(ctx context.Context, pathC chan<- *currentPath) error {
|
func emptyWalker(ctx context.Context, pathC chan<- *currentPath) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -6,6 +6,10 @@ variable "DESTDIR" {
|
|||||||
default = "./bin"
|
default = "./bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "BENCH_FILE_SIZE" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
target "_platforms" {
|
target "_platforms" {
|
||||||
platforms = [
|
platforms = [
|
||||||
"darwin/amd64",
|
"darwin/amd64",
|
||||||
@@ -60,6 +64,24 @@ target "test-noroot" {
|
|||||||
output = ["${DESTDIR}/coverage"]
|
output = ["${DESTDIR}/coverage"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target "bench-root" {
|
||||||
|
inherits = ["build"]
|
||||||
|
target = "bench-root-results"
|
||||||
|
output = ["${DESTDIR}/bench"]
|
||||||
|
args = {
|
||||||
|
BENCH_FILE_SIZE = BENCH_FILE_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target "bench-noroot" {
|
||||||
|
inherits = ["build"]
|
||||||
|
target = "bench-noroot-results"
|
||||||
|
output = ["${DESTDIR}/bench"]
|
||||||
|
args = {
|
||||||
|
BENCH_FILE_SIZE = BENCH_FILE_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
group "lint" {
|
group "lint" {
|
||||||
targets = ["lint-golangci", "lint-gopls"]
|
targets = ["lint-golangci", "lint-gopls"]
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -326,10 +326,10 @@ func (fs *filterFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
default:
|
default:
|
||||||
if fs.mapFn != nil {
|
if fs.mapFn != nil {
|
||||||
result := fs.mapFn(stat.Path, stat)
|
switch result := fs.mapFn(stat.Path, stat); result {
|
||||||
if result == MapResultSkipDir {
|
case MapResultSkipDir:
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
} else if result == MapResultExclude {
|
case MapResultExclude:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,10 +355,10 @@ func (fs *filterFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if fs.mapFn != nil {
|
if fs.mapFn != nil {
|
||||||
result := fs.mapFn(parentStat.Path, parentStat)
|
switch result := fs.mapFn(parentStat.Path, parentStat); result {
|
||||||
if result == MapResultExclude {
|
case MapResultExclude:
|
||||||
continue
|
continue
|
||||||
} else if result == MapResultSkipDir {
|
case MapResultSkipDir:
|
||||||
parentDirs[i].skipFn = true
|
parentDirs[i].skipFn = true
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-5
@@ -40,6 +40,11 @@ func NewFS(root string) (FS, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRootFS creates a new FS from a filesystem root.
|
||||||
|
func NewRootFS(root Root) FS {
|
||||||
|
return &rootFS{root: root}
|
||||||
|
}
|
||||||
|
|
||||||
type fs struct {
|
type fs struct {
|
||||||
root string
|
root string
|
||||||
}
|
}
|
||||||
@@ -90,6 +95,67 @@ func (fs *fs) Open(p string) (io.ReadCloser, error) {
|
|||||||
return rc, errors.WithStack(err)
|
return rc, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rootFS struct {
|
||||||
|
root Root
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *rootFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc) error {
|
||||||
|
seenFiles := make(map[uint64]string)
|
||||||
|
target = cleanRootFSTarget(target)
|
||||||
|
|
||||||
|
return gofs.WalkDir(fs.root.FS(), target, func(path string, dirEntry gofs.DirEntry, walkErr error) (retErr error) {
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil && isNotExist(retErr) {
|
||||||
|
retErr = filepath.SkipDir
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
path = filepath.FromSlash(path)
|
||||||
|
if path == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry gofs.DirEntry
|
||||||
|
if dirEntry != nil {
|
||||||
|
fi, err := fs.root.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
stat, err := mkrootstat(fs.root, path, fi, seenFiles)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
entry = &DirEntryInfo{Stat: stat}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
if err := fn(path, entry, walkErr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *rootFS) Open(p string) (io.ReadCloser, error) {
|
||||||
|
rc, err := fs.root.OpenFile(cleanRootPath(p), os.O_RDONLY, 0)
|
||||||
|
return rc, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanRootFSTarget(target string) string {
|
||||||
|
target = cleanRootPath(target)
|
||||||
|
for strings.HasPrefix(target, string(filepath.Separator)) {
|
||||||
|
target = strings.TrimPrefix(target, string(filepath.Separator))
|
||||||
|
}
|
||||||
|
if target == "" || target == "." {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return filepath.ToSlash(target)
|
||||||
|
}
|
||||||
|
|
||||||
type Dir struct {
|
type Dir struct {
|
||||||
Stat *types.Stat
|
Stat *types.Stat
|
||||||
FS FS
|
FS FS
|
||||||
@@ -187,7 +253,7 @@ type StatInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatInfo) Name() string {
|
func (s *StatInfo) Name() string {
|
||||||
return filepath.Base(s.Stat.Path)
|
return filepath.Base(s.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatInfo) Size() int64 {
|
func (s *StatInfo) Size() int64 {
|
||||||
@@ -206,7 +272,7 @@ func (s *StatInfo) IsDir() bool {
|
|||||||
return s.Mode().IsDir()
|
return s.Mode().IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatInfo) Sys() interface{} {
|
func (s *StatInfo) Sys() any {
|
||||||
return s.Stat
|
return s.Stat
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,7 +287,7 @@ type DirEntryInfo struct {
|
|||||||
|
|
||||||
func (s *DirEntryInfo) Name() string {
|
func (s *DirEntryInfo) Name() string {
|
||||||
if s.Stat != nil {
|
if s.Stat != nil {
|
||||||
return filepath.Base(s.Stat.Path)
|
return filepath.Base(s.Path)
|
||||||
}
|
}
|
||||||
return s.entry.Name()
|
return s.entry.Name()
|
||||||
}
|
}
|
||||||
@@ -235,7 +301,7 @@ func (s *DirEntryInfo) IsDir() bool {
|
|||||||
|
|
||||||
func (s *DirEntryInfo) Type() gofs.FileMode {
|
func (s *DirEntryInfo) Type() gofs.FileMode {
|
||||||
if s.Stat != nil {
|
if s.Stat != nil {
|
||||||
return gofs.FileMode(s.Stat.Mode)
|
return gofs.FileMode(s.Mode)
|
||||||
}
|
}
|
||||||
return s.entry.Type()
|
return s.entry.Type()
|
||||||
}
|
}
|
||||||
@@ -253,6 +319,6 @@ func (s *DirEntryInfo) Info() (gofs.FileInfo, error) {
|
|||||||
s.Stat = stat
|
s.Stat = stat
|
||||||
}
|
}
|
||||||
|
|
||||||
st := s.Stat.Clone()
|
st := s.Clone()
|
||||||
return &StatInfo{st}, nil
|
return &StatInfo{st}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-42
@@ -6,45 +6,64 @@
|
|||||||
Incremental file directory sync tools in golang.
|
Incremental file directory sync tools in golang.
|
||||||
|
|
||||||
```
|
```
|
||||||
BENCH_FILE_SIZE=10000 ./bench.test --test.bench .
|
BENCH_FILE_SIZE=10000 docker buildx bake bench-root
|
||||||
BenchmarkCopyWithTar10-4 2000 995242 ns/op
|
...
|
||||||
BenchmarkCopyWithTar50-4 300 4710021 ns/op
|
#17 0.303 + CGO_ENABLED=0 xx-go test -benchmem '-bench=.' '-run=^$' .
|
||||||
BenchmarkCopyWithTar200-4 100 16627260 ns/op
|
#17 0.303 + tee /tmp/fsutil.log
|
||||||
BenchmarkCopyWithTar1000-4 20 60031459 ns/op
|
#17 1.527 BenchmarkWalker/depth_1_target-32 28356 42258 ns/op 9234 B/op 174 allocs/op
|
||||||
BenchmarkCPA10-4 1000 1678367 ns/op
|
#17 3.166 BenchmarkWalker/depth_1_doublestar_target-32 28647 42038 ns/op 9282 B/op 175 allocs/op
|
||||||
BenchmarkCPA50-4 500 3690306 ns/op
|
#17 4.865 BenchmarkWalker/depth_2_star_target-32 1184 1009371 ns/op 200654 B/op 3971 allocs/op
|
||||||
BenchmarkCPA200-4 200 9495066 ns/op
|
#17 6.342 BenchmarkWalker/depth_2_doublestar_target-32 1148 1007115 ns/op 195891 B/op 3908 allocs/op
|
||||||
BenchmarkCPA1000-4 50 29769289 ns/op
|
#17 9.339 BenchmarkWalker/depth_3_star_star_target-32 39 28146516 ns/op 5221363 B/op 100915 allocs/op
|
||||||
BenchmarkDiffCopy10-4 2000 943889 ns/op
|
#17 13.54 BenchmarkWalker/depth_3_doublestar_target-32 40 28496829 ns/op 5206464 B/op 100828 allocs/op
|
||||||
BenchmarkDiffCopy50-4 500 3285950 ns/op
|
#17 17.99 BenchmarkWalker/depth_4_star_star_star_target-32 26 48224854 ns/op 6571213 B/op 119421 allocs/op
|
||||||
BenchmarkDiffCopy200-4 200 8563792 ns/op
|
#17 25.32 BenchmarkWalker/depth_4_doublestar_target-32 24 45061931 ns/op 6488522 B/op 119315 allocs/op
|
||||||
BenchmarkDiffCopy1000-4 50 29511340 ns/op
|
#17 28.67 BenchmarkWalker/depth_5_star_star_star_star_target-32 54 22124864 ns/op 2476377 B/op 42818 allocs/op
|
||||||
BenchmarkDiffCopyProto10-4 2000 944615 ns/op
|
#17 32.59 BenchmarkWalker/depth_5_doublestar_target-32 49 21479412 ns/op 2460690 B/op 42699 allocs/op
|
||||||
BenchmarkDiffCopyProto50-4 500 3334940 ns/op
|
#17 35.09 BenchmarkWalker/depth_6_star_star_star_star_star_target-32 28 38307776 ns/op 3998884 B/op 67772 allocs/op
|
||||||
BenchmarkDiffCopyProto200-4 200 9420038 ns/op
|
#17 38.05 BenchmarkWalker/depth_6_doublestar_target-32 31 38242074 ns/op 3980841 B/op 67634 allocs/op
|
||||||
BenchmarkDiffCopyProto1000-4 50 30632429 ns/op
|
#17 42.92 BenchmarkWalker/depth_6_doublestar_exclude_star_star_doublestar-32 2925 393602 ns/op 47439 B/op 1006 allocs/op
|
||||||
BenchmarkIncrementalDiffCopy10-4 2000 691993 ns/op
|
#17 45.99 + cd bench
|
||||||
BenchmarkIncrementalDiffCopy50-4 1000 1304253 ns/op
|
#17 45.99 + CGO_ENABLED=0 xx-go test -benchmem '-bench=.' '-run=^$' .
|
||||||
BenchmarkIncrementalDiffCopy200-4 500 3306519 ns/op
|
#17 45.99 + tee /tmp/bench.log
|
||||||
BenchmarkIncrementalDiffCopy1000-4 200 10211343 ns/op
|
#17 46.84 BenchmarkCopyWithTar10-32 283 4291776 ns/op 906824 B/op 843 allocs/op
|
||||||
BenchmarkIncrementalDiffCopy5000-4 20 55194427 ns/op
|
#17 50.05 BenchmarkCopyWithTar50-32 50 24077499 ns/op 4999874 B/op 4514 allocs/op
|
||||||
BenchmarkIncrementalDiffCopy10000-4 20 91759289 ns/op
|
#17 54.00 BenchmarkCopyWithTar200-32 15 74350687 ns/op 18757006 B/op 15347 allocs/op
|
||||||
BenchmarkIncrementalCopyWithTar10-4 2000 1020258 ns/op
|
#17 56.31 BenchmarkCopyWithTar1000-32 5 259188166 ns/op 72393427 B/op 55045 allocs/op
|
||||||
BenchmarkIncrementalCopyWithTar50-4 300 5348786 ns/op
|
#17 61.31 BenchmarkCPA10-32 339 3496169 ns/op 7102 B/op 77 allocs/op
|
||||||
BenchmarkIncrementalCopyWithTar200-4 100 19495000 ns/op
|
#17 64.68 BenchmarkCPA50-32 74 15808000 ns/op 7102 B/op 77 allocs/op
|
||||||
BenchmarkIncrementalCopyWithTar1000-4 20 70338507 ns/op
|
#17 67.26 BenchmarkCPA200-32 26 45892546 ns/op 7101 B/op 77 allocs/op
|
||||||
BenchmarkIncrementalRsync10-4 30 45215754 ns/op
|
#17 70.08 BenchmarkCPA1000-32 8 147602854 ns/op 7103 B/op 77 allocs/op
|
||||||
BenchmarkIncrementalRsync50-4 30 45837260 ns/op
|
#17 72.60 BenchmarkDiffCopy10-32 392 3045072 ns/op 219789 B/op 1123 allocs/op
|
||||||
BenchmarkIncrementalRsync200-4 30 48780614 ns/op
|
#17 76.15 BenchmarkDiffCopy50-32 82 14024073 ns/op 1253831 B/op 5460 allocs/op
|
||||||
BenchmarkIncrementalRsync1000-4 20 54801892 ns/op
|
#17 78.83 BenchmarkDiffCopy200-32 27 41259003 ns/op 4683591 B/op 18640 allocs/op
|
||||||
BenchmarkIncrementalRsync5000-4 20 84782542 ns/op
|
#17 81.60 BenchmarkDiffCopy1000-32 8 125244042 ns/op 17285147 B/op 67649 allocs/op
|
||||||
BenchmarkIncrementalRsync10000-4 10 103355108 ns/op
|
#17 83.90 BenchmarkDiffCopyProto10-32 412 2934103 ns/op 232184 B/op 1143 allocs/op
|
||||||
BenchmarkRsync10-4 30 46776470 ns/op
|
#17 87.46 BenchmarkDiffCopyProto50-32 80 13809955 ns/op 1273311 B/op 5565 allocs/op
|
||||||
BenchmarkRsync50-4 30 48601555 ns/op
|
#17 90.02 BenchmarkDiffCopyProto200-32 30 41169665 ns/op 4697476 B/op 18995 allocs/op
|
||||||
BenchmarkRsync200-4 20 59642691 ns/op
|
#17 93.05 BenchmarkDiffCopyProto1000-32 8 127126319 ns/op 17334920 B/op 68883 allocs/op
|
||||||
BenchmarkRsync1000-4 20 101343010 ns/op
|
#17 95.37 BenchmarkIncrementalDiffCopy10-32 1540 779568 ns/op 119068 B/op 1015 allocs/op
|
||||||
BenchmarkGnuTar10-4 500 3171448 ns/op
|
#17 97.84 BenchmarkIncrementalDiffCopy50-32 782 1513121 ns/op 455740 B/op 4285 allocs/op
|
||||||
BenchmarkGnuTar50-4 300 5030296 ns/op
|
#17 99.97 BenchmarkIncrementalDiffCopy200-32 271 4248983 ns/op 1329875 B/op 13603 allocs/op
|
||||||
BenchmarkGnuTar200-4 100 10464313 ns/op
|
#17 102.3 BenchmarkIncrementalDiffCopy1000-32 84 14027390 ns/op 4552790 B/op 46849 allocs/op
|
||||||
BenchmarkGnuTar1000-4 50 30375257 ns/op
|
#17 105.4 BenchmarkIncrementalDiffCopy5000-32 14 73269136 ns/op 24500669 B/op 266772 allocs/op
|
||||||
```
|
#17 110.6 BenchmarkIncrementalDiffCopy10000-32 9 128400623 ns/op 43706410 B/op 472982 allocs/op
|
||||||
|
#17 114.4 BenchmarkIncrementalCopyWithTar10-32 396 2946217 ns/op 915096 B/op 826 allocs/op
|
||||||
|
#17 116.3 BenchmarkIncrementalCopyWithTar50-32 69 16767967 ns/op 5093048 B/op 4472 allocs/op
|
||||||
|
#17 117.7 BenchmarkIncrementalCopyWithTar200-32 19 62520307 ns/op 19081658 B/op 15270 allocs/op
|
||||||
|
#17 119.7 BenchmarkIncrementalCopyWithTar1000-32 5 239712851 ns/op 73113704 B/op 54938 allocs/op
|
||||||
|
#17 122.8 BenchmarkIncrementalRsync10-32 26 43773727 ns/op 6608 B/op 69 allocs/op
|
||||||
|
#17 124.1 BenchmarkIncrementalRsync50-32 25 45985011 ns/op 6608 B/op 69 allocs/op
|
||||||
|
#17 125.6 BenchmarkIncrementalRsync200-32 22 49976819 ns/op 6608 B/op 69 allocs/op
|
||||||
|
#17 127.3 BenchmarkIncrementalRsync1000-32 19 63618139 ns/op 6600 B/op 69 allocs/op
|
||||||
|
#17 130.6 BenchmarkIncrementalRsync5000-32 8 132002745 ns/op 6608 B/op 69 allocs/op
|
||||||
|
#17 136.3 BenchmarkIncrementalRsync10000-32 6 187247351 ns/op 6608 B/op 69 allocs/op
|
||||||
|
#17 140.3 BenchmarkRsync10-32 25 46054741 ns/op 6606 B/op 69 allocs/op
|
||||||
|
#17 141.6 BenchmarkRsync50-32 19 58922835 ns/op 6605 B/op 69 allocs/op
|
||||||
|
#17 143.2 BenchmarkRsync200-32 13 91176938 ns/op 6606 B/op 69 allocs/op
|
||||||
|
#17 145.5 BenchmarkRsync1000-32 6 198319527 ns/op 6606 B/op 69 allocs/op
|
||||||
|
#17 147.6 BenchmarkGnuTar10-32 268 4489528 ns/op 14192 B/op 151 allocs/op
|
||||||
|
#17 150.8 BenchmarkGnuTar50-32 54 20528041 ns/op 14192 B/op 151 allocs/op
|
||||||
|
#17 152.9 BenchmarkGnuTar200-32 19 60394926 ns/op 14192 B/op 151 allocs/op
|
||||||
|
#17 155.4 BenchmarkGnuTar1000-32 6 198630328 ns/op 14192 B/op 151 allocs/op
|
||||||
|
```
|
||||||
|
|||||||
+67
-6
@@ -64,13 +64,32 @@ type ReceiveOpt struct {
|
|||||||
MetadataOnly FilterFunc
|
MetadataOnly FilterFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type receiveDiskWriter interface {
|
||||||
|
HandleChange(ChangeKind, string, os.FileInfo, error) error
|
||||||
|
Wait(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
func Receive(ctx context.Context, conn Stream, dest string, opt ReceiveOpt) error {
|
func Receive(ctx context.Context, conn Stream, dest string, opt ReceiveOpt) error {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
r := &receiver{
|
r := newReceiver(conn, opt)
|
||||||
|
r.dest = dest
|
||||||
|
return r.run(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReceiveRoot(ctx context.Context, conn Stream, dest Root, opt ReceiveOpt) error {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
r := newReceiver(conn, opt)
|
||||||
|
r.root = dest
|
||||||
|
return r.run(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newReceiver(conn Stream, opt ReceiveOpt) *receiver {
|
||||||
|
return &receiver{
|
||||||
conn: &syncStream{Stream: conn},
|
conn: &syncStream{Stream: conn},
|
||||||
dest: dest,
|
|
||||||
files: make(map[string]uint32),
|
files: make(map[string]uint32),
|
||||||
pipes: make(map[uint32]io.WriteCloser),
|
pipes: make(map[uint32]io.WriteCloser),
|
||||||
notifyHashed: opt.NotifyHashed,
|
notifyHashed: opt.NotifyHashed,
|
||||||
@@ -81,11 +100,11 @@ func Receive(ctx context.Context, conn Stream, dest string, opt ReceiveOpt) erro
|
|||||||
differ: opt.Differ,
|
differ: opt.Differ,
|
||||||
metadataOnly: opt.MetadataOnly,
|
metadataOnly: opt.MetadataOnly,
|
||||||
}
|
}
|
||||||
return r.run(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type receiver struct {
|
type receiver struct {
|
||||||
dest string
|
dest string
|
||||||
|
root Root
|
||||||
conn Stream
|
conn Stream
|
||||||
files map[string]uint32
|
files map[string]uint32
|
||||||
pipes map[uint32]io.WriteCloser
|
pipes map[uint32]io.WriteCloser
|
||||||
@@ -159,7 +178,7 @@ func (w *dynamicWalker) fill(ctx context.Context, pathC chan<- *currentPath) err
|
|||||||
func (r *receiver) run(ctx context.Context) error {
|
func (r *receiver) run(ctx context.Context) error {
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
dw, err := NewDiskWriter(ctx, r.dest, DiskWriterOpt{
|
dw, err := r.newDiskWriter(ctx, DiskWriterOpt{
|
||||||
AsyncDataCb: r.asyncDataFunc,
|
AsyncDataCb: r.asyncDataFunc,
|
||||||
NotifyCb: r.notifyHashed,
|
NotifyCb: r.notifyHashed,
|
||||||
ContentHasher: r.contentHasher,
|
ContentHasher: r.contentHasher,
|
||||||
@@ -179,12 +198,21 @@ func (r *receiver) run(ctx context.Context) error {
|
|||||||
g.Go(func() (retErr error) {
|
g.Go(func() (retErr error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
|
// If we're unwinding because the errgroup context was
|
||||||
|
// cancelled by another goroutine's failure, report that root
|
||||||
|
// cause instead of the bare "context canceled", which would
|
||||||
|
// otherwise overwrite the real error on the sender side.
|
||||||
|
if errors.Is(retErr, context.Canceled) {
|
||||||
|
if cause := context.Cause(ctx); cause != nil {
|
||||||
|
retErr = cause
|
||||||
|
}
|
||||||
|
}
|
||||||
r.conn.SendMsg(&types.Packet{Type: types.PACKET_ERR, Data: []byte(retErr.Error())})
|
r.conn.SendMsg(&types.Packet{Type: types.PACKET_ERR, Data: []byte(retErr.Error())})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
destWalker := emptyWalker
|
destWalker := emptyWalker
|
||||||
if !r.merge {
|
if !r.merge {
|
||||||
destWalker = getWalkerFn(r.dest)
|
destWalker = r.destWalker()
|
||||||
}
|
}
|
||||||
err := doubleWalkDiff(ctx, dw.HandleChange, destWalker, w.fill, r.filter, r.differ)
|
err := doubleWalkDiff(ctx, dw.HandleChange, destWalker, w.fill, r.filter, r.differ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -333,12 +361,45 @@ func (r *receiver) run(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return r.writeMetadata(metadataBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *receiver) newDiskWriter(ctx context.Context, opt DiskWriterOpt) (receiveDiskWriter, error) {
|
||||||
|
if r.root != nil {
|
||||||
|
return NewRootDiskWriter(ctx, r.root, opt)
|
||||||
|
}
|
||||||
|
return NewDiskWriter(ctx, r.dest, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *receiver) destWalker() walkerFn {
|
||||||
|
if r.root != nil {
|
||||||
|
return getRootWalkerFn(r.root)
|
||||||
|
}
|
||||||
|
return getWalkerFn(r.dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *receiver) writeMetadata(metadataBuffer *buffer) error {
|
||||||
|
if r.root != nil {
|
||||||
|
// although we don't allow tranferring metadataPath, make sure there was no preexisting file/symlink
|
||||||
|
_ = r.root.Remove(metadataPath)
|
||||||
|
|
||||||
|
f, err := r.root.OpenFile(metadataPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if _, err := metadataBuffer.WriteTo(f); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// although we don't allow tranferring metadataPath, make sure there was no preexisting file/symlink
|
// although we don't allow tranferring metadataPath, make sure there was no preexisting file/symlink
|
||||||
os.Remove(filepath.Join(r.dest, metadataPath))
|
os.Remove(filepath.Join(r.dest, metadataPath))
|
||||||
|
|
||||||
f, err := os.OpenFile(filepath.Join(r.dest, metadataPath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
f, err := os.OpenFile(filepath.Join(r.dest, metadataPath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
if _, err := metadataBuffer.WriteTo(f); err != nil {
|
if _, err := metadataBuffer.WriteTo(f); err != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
gofs "io/fs"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Root interface {
|
||||||
|
Close() error
|
||||||
|
FS() gofs.FS
|
||||||
|
Remove(string) error
|
||||||
|
RemoveAll(string) error
|
||||||
|
Lstat(string) (os.FileInfo, error)
|
||||||
|
Stat(string) (os.FileInfo, error)
|
||||||
|
Mkdir(string, os.FileMode) error
|
||||||
|
Symlink(string, string) error
|
||||||
|
Link(string, string) error
|
||||||
|
OpenRoot(string) (*os.Root, error)
|
||||||
|
OpenFile(string, int, os.FileMode) (*os.File, error)
|
||||||
|
Readlink(string) (string, error)
|
||||||
|
Rename(string, string) error
|
||||||
|
Lchown(string, int, int) error
|
||||||
|
Chmod(string, os.FileMode) error
|
||||||
|
Chtimes(string, time.Time, time.Time) error
|
||||||
|
RootXattr
|
||||||
|
RootMknod
|
||||||
|
RootLChtimes
|
||||||
|
}
|
||||||
|
|
||||||
|
type RootXattr interface {
|
||||||
|
LSetxattr(name, key string, value []byte, flags int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RootMknod interface {
|
||||||
|
Mknod(name string, mode uint32, dev int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RootLChtimes interface {
|
||||||
|
LChtimes(name string, mtime time.Time) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type root struct {
|
||||||
|
*os.Root
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
closed bool
|
||||||
|
rootDirState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoot(osroot *os.Root) Root {
|
||||||
|
return &root{Root: osroot}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *root) Close() error {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
if r.closed {
|
||||||
|
r.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.closed = true
|
||||||
|
rootDir := r.rootDir
|
||||||
|
r.rootDir = nil
|
||||||
|
osroot := r.Root
|
||||||
|
r.Root = nil
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if rootDir != nil {
|
||||||
|
err = rootDir.Close()
|
||||||
|
}
|
||||||
|
if osroot != nil {
|
||||||
|
if err2 := osroot.Close(); err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Root = (*root)(nil)
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rootDirState struct {
|
||||||
|
rootDirOnce sync.Once
|
||||||
|
rootDir *os.File
|
||||||
|
rootDirErr error
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !linux && !darwin && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
type rootDirState struct {
|
||||||
|
rootDir *os.File
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !linux && !freebsd && !netbsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unsupportedRootOp(op, name string, err error) error {
|
||||||
|
return errors.WithStack(&os.PathError{Op: op, Path: name, Err: err})
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build !linux && !darwin && !freebsd && !netbsd && !openbsd && !dragonfly && !windows
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RootLChtimes = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) LChtimes(name string, mtime time.Time) error {
|
||||||
|
return unsupportedRootOp("utimensat", name, syscall.ENOSYS)
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RootLChtimes = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) LChtimes(name string, mtime time.Time) error {
|
||||||
|
fi, err := r.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.Chtimes(name, mtime, mtime); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build freebsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RootMknod = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) Mknod(name string, mode uint32, dev int) error {
|
||||||
|
parent, base, closeParent, err := r.openRootParent(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if closeParent {
|
||||||
|
defer parent.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.Mknodat(int(parent.Fd()), base, mode, uint64(dev)); err != nil {
|
||||||
|
return errors.WithStack(&os.PathError{Op: "mknodat", Path: name, Err: err})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build linux || netbsd || openbsd || dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RootMknod = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) Mknod(name string, mode uint32, dev int) error {
|
||||||
|
parent, base, closeParent, err := r.openRootParent(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if closeParent {
|
||||||
|
defer parent.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.Mknodat(int(parent.Fd()), base, mode, dev); err != nil {
|
||||||
|
return errors.WithStack(&os.PathError{Op: "mknodat", Path: name, Err: err})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build !linux && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
var _ RootMknod = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) Mknod(name string, mode uint32, dev int) error {
|
||||||
|
return unsupportedRootOp("mknodat", name, syscall.ENOSYS)
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
//go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RootLChtimes = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) LChtimes(name string, mtime time.Time) error {
|
||||||
|
parent, base, closeParent, err := r.openRootParent(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if closeParent {
|
||||||
|
defer parent.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := unix.NsecToTimespec(mtime.UnixNano())
|
||||||
|
times := []unix.Timespec{ts, ts}
|
||||||
|
if err := unix.UtimesNanoAt(int(parent.Fd()), base, times, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||||
|
return errors.WithStack(&os.PathError{Op: "utimensat", Path: name, Err: err})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *root) openRootParent(name string) (*os.File, string, bool, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, "", false, errors.New("nil root")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fast path for direct basename
|
||||||
|
if !strings.ContainsRune(name, filepath.Separator) {
|
||||||
|
if name == "" || name == "." || name == ".." {
|
||||||
|
return nil, "", false, errors.WithStack(&os.PathError{Op: "openat", Path: name, Err: syscall.EINVAL})
|
||||||
|
}
|
||||||
|
parent, err := r.rootDirFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", false, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return parent, name, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned := filepath.Clean(name)
|
||||||
|
base := filepath.Base(cleaned)
|
||||||
|
if base == "." || base == ".." {
|
||||||
|
return nil, "", false, errors.WithStack(&os.PathError{Op: "openat", Path: name, Err: syscall.EINVAL})
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(cleaned)
|
||||||
|
if dir == "." {
|
||||||
|
parent, err := r.rootDirFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", false, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return parent, base, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parent, err := r.OpenFile(dir, os.O_RDONLY|unix.O_DIRECTORY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", false, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return parent, base, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *root) rootDirFile() (*os.File, error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
if r.closed {
|
||||||
|
return nil, os.ErrClosed
|
||||||
|
}
|
||||||
|
if r.Root == nil {
|
||||||
|
return nil, errors.New("nil root")
|
||||||
|
}
|
||||||
|
r.rootDirOnce.Do(func() {
|
||||||
|
r.rootDir, r.rootDirErr = r.OpenFile(".", os.O_RDONLY|unix.O_DIRECTORY, 0)
|
||||||
|
})
|
||||||
|
if r.rootDirErr != nil {
|
||||||
|
return nil, r.rootDirErr
|
||||||
|
}
|
||||||
|
return r.rootDir, nil
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
//go:build linux || freebsd || netbsd || openbsd || dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/tonistiigi/fsutil/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleRootTarTypeBlockCharFifo(root RootMknod, path string, stat *types.Stat) error {
|
||||||
|
mode := uint32(stat.Mode & 07777)
|
||||||
|
if os.FileMode(stat.Mode)&os.ModeCharDevice != 0 {
|
||||||
|
mode |= syscall.S_IFCHR
|
||||||
|
} else if os.FileMode(stat.Mode)&os.ModeNamedPipe != 0 {
|
||||||
|
mode |= syscall.S_IFIFO
|
||||||
|
} else {
|
||||||
|
mode |= syscall.S_IFBLK
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.Mknod(path, mode, int(mkdev(stat.Devmajor, stat.Devminor)))
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !linux && !freebsd && !netbsd && !openbsd && !dragonfly
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/tonistiigi/fsutil/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleRootTarTypeBlockCharFifo(RootMknod, string, *types.Stat) error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
//go:build linux || darwin || freebsd || netbsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RootXattr = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) LSetxattr(name, key string, value []byte, flags int) error {
|
||||||
|
f, err := r.OpenFile(name, os.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := unix.Fsetxattr(int(f.Fd()), key, value, flags); err != nil {
|
||||||
|
return errors.WithStack(&os.PathError{Op: "fsetxattr", Path: name, Err: err})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
//go:build linux || darwin || freebsd || netbsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/tonistiigi/fsutil/types"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootXattrBufferSize = 128
|
||||||
|
|
||||||
|
func loadRootXattr(root Root, path string, stat *types.Stat) error {
|
||||||
|
f, err := root.OpenFile(path, os.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
|
||||||
|
if err != nil {
|
||||||
|
if ignoreRootXattrOpenError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
xattrs, err := rootListxattr(int(f.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, syscall.ENOTSUP) || errors.Is(err, unix.EOPNOTSUPP) || errors.Is(err, unix.ENOSYS) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrapf(err, "failed to xattr %s", path)
|
||||||
|
}
|
||||||
|
if len(xattrs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string][]byte)
|
||||||
|
for _, key := range xattrs {
|
||||||
|
if skipXattr(key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v, err := rootGetxattr(int(f.Fd()), key); err == nil {
|
||||||
|
m[key] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(m) > 0 {
|
||||||
|
stat.Xattrs = m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignoreRootXattrOpenError(err error) bool {
|
||||||
|
return errors.Is(err, syscall.ELOOP) ||
|
||||||
|
errors.Is(err, syscall.EACCES) ||
|
||||||
|
errors.Is(err, syscall.EPERM) ||
|
||||||
|
errors.Is(err, syscall.ENXIO)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootListxattr(fd int) ([]string, error) {
|
||||||
|
return rootListxattrWith(fd, rootFlistxattr, rootParseListxattr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootGetxattr(fd int, key string) ([]byte, error) {
|
||||||
|
buf := make([]byte, rootXattrBufferSize)
|
||||||
|
n, err := unix.Fgetxattr(fd, key, buf)
|
||||||
|
for err == unix.ERANGE {
|
||||||
|
n, err = unix.Fgetxattr(fd, key, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = make([]byte, n)
|
||||||
|
n, err = unix.Fgetxattr(fd, key, buf)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootListxattrFunc func(int, []byte) (int, error)
|
||||||
|
type rootParseListxattrFunc func([]byte) []string
|
||||||
|
|
||||||
|
func rootListxattrWith(fd int, list rootListxattrFunc, parse rootParseListxattrFunc) ([]string, error) {
|
||||||
|
buf := make([]byte, rootXattrBufferSize)
|
||||||
|
n, err := list(fd, buf)
|
||||||
|
for err == unix.ERANGE {
|
||||||
|
n, err = list(fd, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = make([]byte, n)
|
||||||
|
n, err = list(fd, buf)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parse(buf[:n]), nil
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
//go:build freebsd || netbsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
func rootFlistxattr(fd int, buf []byte) (int, error) {
|
||||||
|
return unix.FlistxattrNS(fd, unix.EXTATTR_NAMESPACE_USER, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootParseListxattr(buf []byte) []string {
|
||||||
|
var xattrs []string
|
||||||
|
for i := 0; i < len(buf); {
|
||||||
|
next := i + 1 + int(buf[i])
|
||||||
|
if next > len(buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if next > i+1 {
|
||||||
|
xattrs = append(xattrs, "user."+string(buf[i+1:next]))
|
||||||
|
}
|
||||||
|
i = next
|
||||||
|
}
|
||||||
|
return xattrs
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rootFlistxattr(fd int, buf []byte) (int, error) {
|
||||||
|
return unix.Flistxattr(fd, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootParseListxattr(buf []byte) []string {
|
||||||
|
parts := bytes.Split(bytes.TrimSuffix(buf, []byte{0}), []byte{0})
|
||||||
|
var xattrs []string
|
||||||
|
for _, part := range parts {
|
||||||
|
if len(part) > 0 {
|
||||||
|
xattrs = append(xattrs, string(part))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return xattrs
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !linux && !darwin && !freebsd && !netbsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import "github.com/tonistiigi/fsutil/types"
|
||||||
|
|
||||||
|
func loadRootXattr(Root, string, *types.Stat) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build !linux && !darwin && !freebsd && !netbsd
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
var _ RootXattr = (*root)(nil)
|
||||||
|
|
||||||
|
func (r *root) LSetxattr(name, key string, value []byte, flags int) error {
|
||||||
|
return unsupportedRootOp("lsetxattr", name, syscall.ENOSYS)
|
||||||
|
}
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootCacheDefaultSize = 128
|
||||||
|
|
||||||
|
type rootCache struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
maxSize int
|
||||||
|
entries map[string]*rootCacheEntry
|
||||||
|
lru *list.List
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootCacheEntry struct {
|
||||||
|
path string
|
||||||
|
root Root
|
||||||
|
refs int
|
||||||
|
evicted bool
|
||||||
|
elem *list.Element
|
||||||
|
closeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootLease struct {
|
||||||
|
root Root
|
||||||
|
base string
|
||||||
|
entry *rootCacheEntry
|
||||||
|
cache *rootCache
|
||||||
|
release sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRootCache(root Root, maxSize int) *rootCache {
|
||||||
|
if maxSize <= 0 {
|
||||||
|
maxSize = rootCacheDefaultSize
|
||||||
|
}
|
||||||
|
return &rootCache{
|
||||||
|
maxSize: maxSize,
|
||||||
|
entries: map[string]*rootCacheEntry{
|
||||||
|
".": {path: ".", root: root},
|
||||||
|
},
|
||||||
|
lru: list.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCache) get(path string) (*rootLease, error) {
|
||||||
|
path = cleanRootPath(path)
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
base := filepath.Base(path)
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return nil, errors.WithStack(os.ErrClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := c.getDirLocked(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.refs++
|
||||||
|
|
||||||
|
return &rootLease{
|
||||||
|
root: entry.root,
|
||||||
|
base: base,
|
||||||
|
entry: entry,
|
||||||
|
cache: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCache) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
c.closed = true
|
||||||
|
var err error
|
||||||
|
for path, entry := range c.entries {
|
||||||
|
if path == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(c.entries, path)
|
||||||
|
if entry.elem != nil {
|
||||||
|
c.lru.Remove(entry.elem)
|
||||||
|
entry.elem = nil
|
||||||
|
}
|
||||||
|
entry.evicted = true
|
||||||
|
if entry.refs == 0 {
|
||||||
|
if err2 := closeRootCacheEntry(entry); err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *rootLease) Release() error {
|
||||||
|
var err error
|
||||||
|
l.release.Do(func() {
|
||||||
|
l.cache.mu.Lock()
|
||||||
|
defer l.cache.mu.Unlock()
|
||||||
|
|
||||||
|
l.entry.refs--
|
||||||
|
if l.entry.refs == 0 && l.entry.evicted {
|
||||||
|
err = closeRootCacheEntry(l.entry)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCache) getDirLocked(dir string) (*rootCacheEntry, error) {
|
||||||
|
entry := c.entries["."]
|
||||||
|
if dir == "." {
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := "."
|
||||||
|
for _, component := range rootCacheComponents(dir) {
|
||||||
|
nextPath := component
|
||||||
|
if path != "." {
|
||||||
|
nextPath = filepath.Join(path, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextEntry, ok := c.entries[nextPath]
|
||||||
|
if ok && !nextEntry.evicted {
|
||||||
|
c.touchLocked(nextEntry)
|
||||||
|
entry = nextEntry
|
||||||
|
path = nextPath
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
osroot, err := entry.root.OpenRoot(component)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
nextEntry = &rootCacheEntry{
|
||||||
|
path: nextPath,
|
||||||
|
root: NewRoot(osroot),
|
||||||
|
}
|
||||||
|
nextEntry.elem = c.lru.PushFront(nextEntry)
|
||||||
|
c.entries[nextPath] = nextEntry
|
||||||
|
entry = nextEntry
|
||||||
|
path = nextPath
|
||||||
|
c.evictLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCache) touchLocked(entry *rootCacheEntry) {
|
||||||
|
if entry.elem != nil {
|
||||||
|
c.lru.MoveToFront(entry.elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *rootCache) evictLocked() {
|
||||||
|
for len(c.entries)-1 > c.maxSize {
|
||||||
|
elem := c.lru.Back()
|
||||||
|
if elem == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entry := elem.Value.(*rootCacheEntry)
|
||||||
|
c.lru.Remove(elem)
|
||||||
|
entry.elem = nil
|
||||||
|
entry.evicted = true
|
||||||
|
delete(c.entries, entry.path)
|
||||||
|
if entry.refs == 0 {
|
||||||
|
entry.closeErr = closeRootCacheEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeRootCacheEntry(entry *rootCacheEntry) error {
|
||||||
|
if entry.closeErr != nil {
|
||||||
|
return entry.closeErr
|
||||||
|
}
|
||||||
|
if err := entry.root.Close(); err != nil {
|
||||||
|
entry.closeErr = errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return entry.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootCacheComponents(dir string) []string {
|
||||||
|
if dir == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(dir, string(filepath.Separator))
|
||||||
|
}
|
||||||
+339
@@ -0,0 +1,339 @@
|
|||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
gofs "io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/tonistiigi/fsutil/types"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RootDiskWriter struct {
|
||||||
|
opt DiskWriterOpt
|
||||||
|
dest Root
|
||||||
|
rootStack *rootStack
|
||||||
|
rootCache *rootCache
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel func()
|
||||||
|
eg *errgroup.Group
|
||||||
|
egCtx context.Context
|
||||||
|
filter FilterFunc
|
||||||
|
dirModTimes map[string]int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRootDiskWriter(ctx context.Context, dest Root, opt DiskWriterOpt) (*RootDiskWriter, error) {
|
||||||
|
if opt.SyncDataCb == nil && opt.AsyncDataCb == nil {
|
||||||
|
return nil, errors.New("no data callback specified")
|
||||||
|
}
|
||||||
|
if opt.SyncDataCb != nil && opt.AsyncDataCb != nil {
|
||||||
|
return nil, errors.New("can't specify both sync and async data callbacks")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
eg, egCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
return &RootDiskWriter{
|
||||||
|
opt: opt,
|
||||||
|
dest: dest,
|
||||||
|
rootStack: newRootStack(dest),
|
||||||
|
rootCache: newRootCache(dest, rootCacheDefaultSize),
|
||||||
|
eg: eg,
|
||||||
|
ctx: ctx,
|
||||||
|
egCtx: egCtx,
|
||||||
|
cancel: cancel,
|
||||||
|
filter: opt.Filter,
|
||||||
|
dirModTimes: map[string]int64{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dw *RootDiskWriter) Wait(ctx context.Context) error {
|
||||||
|
err := dw.eg.Wait()
|
||||||
|
if closeErr := dw.rootCache.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
if closeErr := dw.rootStack.Close(); err == nil {
|
||||||
|
err = closeErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gofs.WalkDir(dw.dest.FS(), ".", func(path string, d gofs.DirEntry, prevErr error) error {
|
||||||
|
if prevErr != nil {
|
||||||
|
return prevErr
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mtime, ok := dw.dirModTimes[path]; ok {
|
||||||
|
return rootChtimes(dw.dest, filepath.FromSlash(path), mtime)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dw *RootDiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, err error) (retErr error) {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-dw.ctx.Done():
|
||||||
|
return dw.ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
dw.cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
destPath := cleanRootPath(p)
|
||||||
|
destRoot, base, err := dw.rootStack.get(destPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == ChangeKindDelete {
|
||||||
|
if dw.filter != nil {
|
||||||
|
var empty types.Stat
|
||||||
|
if ok := dw.filter(p, &empty); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// todo: no need to validate if diff is trusted but is it always?
|
||||||
|
if err := destRoot.RemoveAll(base); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to remove: %s", destPath)
|
||||||
|
}
|
||||||
|
if dw.opt.NotifyCb != nil {
|
||||||
|
if err := dw.opt.NotifyCb(kind, p, nil, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, ok := fi.Sys().(*types.Stat)
|
||||||
|
if !ok {
|
||||||
|
return errors.WithStack(&os.PathError{Path: p, Err: syscall.EBADMSG, Op: "change without stat info"})
|
||||||
|
}
|
||||||
|
|
||||||
|
statCopy := stat.Clone()
|
||||||
|
|
||||||
|
if dw.filter != nil {
|
||||||
|
if ok := dw.filter(p, statCopy); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rename := true
|
||||||
|
oldFi, err := destRoot.Lstat(base)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
if kind != ChangeKindAdd {
|
||||||
|
return errors.Wrap(err, "modify/rm")
|
||||||
|
}
|
||||||
|
rename = false
|
||||||
|
} else {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldFi != nil && fi.IsDir() && oldFi.IsDir() {
|
||||||
|
if err := rewriteRootMetadata(destRoot, base, statCopy); err != nil {
|
||||||
|
return errors.Wrapf(err, "error setting dir metadata for %s", destPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newPath := base
|
||||||
|
if rename {
|
||||||
|
newPath = ".tmp." + nextSuffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
isRegularFile := false
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.IsDir():
|
||||||
|
if err := destRoot.Mkdir(newPath, fi.Mode().Perm()); err != nil {
|
||||||
|
if errors.Is(err, syscall.EEXIST) {
|
||||||
|
// we saw a race to create this directory, so try again
|
||||||
|
return dw.HandleChange(kind, p, fi, nil)
|
||||||
|
}
|
||||||
|
return errors.Wrapf(err, "failed to create dir %s", newPath)
|
||||||
|
}
|
||||||
|
dw.dirModTimes[filepath.ToSlash(destPath)] = statCopy.ModTime
|
||||||
|
case fi.Mode()&os.ModeDevice != 0 || fi.Mode()&os.ModeNamedPipe != 0:
|
||||||
|
if err := handleRootTarTypeBlockCharFifo(destRoot, newPath, statCopy); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create device %s", newPath)
|
||||||
|
}
|
||||||
|
case fi.Mode()&os.ModeSymlink != 0:
|
||||||
|
if err := destRoot.Symlink(statCopy.Linkname, newPath); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to symlink %s", newPath)
|
||||||
|
}
|
||||||
|
case statCopy.Linkname != "":
|
||||||
|
linkNewName := destPath
|
||||||
|
if rename {
|
||||||
|
linkNewName = filepath.Join(filepath.Dir(destPath), newPath)
|
||||||
|
}
|
||||||
|
if err := dw.dest.Link(statCopy.Linkname, linkNewName); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to link %s to %s", newPath, statCopy.Linkname)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
isRegularFile = true
|
||||||
|
file, err := destRoot.OpenFile(newPath, os.O_CREATE|os.O_WRONLY, fi.Mode().Perm())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create %s", newPath)
|
||||||
|
}
|
||||||
|
if dw.opt.SyncDataCb != nil {
|
||||||
|
if err := dw.processChange(dw.ctx, ChangeKindAdd, p, fi, file); err != nil {
|
||||||
|
file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to close %s", newPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rewriteRootMetadata(destRoot, newPath, statCopy); err != nil {
|
||||||
|
return errors.Wrapf(err, "error setting metadata for %s", newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rename {
|
||||||
|
if oldFi.IsDir() != fi.IsDir() {
|
||||||
|
if err := destRoot.RemoveAll(base); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to remove %s", destPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := destRoot.Rename(newPath, base); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to rename %s to %s", newPath, destPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRegularFile {
|
||||||
|
if dw.opt.AsyncDataCb != nil {
|
||||||
|
dw.requestAsyncFileData(p, destPath, fi, statCopy)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return dw.processChange(dw.ctx, kind, p, fi, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dw *RootDiskWriter) requestAsyncFileData(p, dest string, fi os.FileInfo, st *types.Stat) {
|
||||||
|
// todo: limit worker threads
|
||||||
|
dw.eg.Go(func() error {
|
||||||
|
lease, err := dw.rootCache.get(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lease.Release()
|
||||||
|
|
||||||
|
w := &rootLazyFileWriter{lease: lease}
|
||||||
|
if err := dw.processChange(dw.egCtx, ChangeKindAdd, p, fi, w); err != nil {
|
||||||
|
w.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return rootChtimes(lease.root, lease.base, st.ModTime) // TODO: parent dirs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dw *RootDiskWriter) processChange(ctx context.Context, kind ChangeKind, p string, fi os.FileInfo, w io.WriteCloser) error {
|
||||||
|
origw := w
|
||||||
|
var hw *hashedWriter
|
||||||
|
if dw.opt.NotifyCb != nil {
|
||||||
|
var err error
|
||||||
|
if hw, err = newHashWriter(dw.opt.ContentHasher, fi, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w = hw
|
||||||
|
}
|
||||||
|
if origw != nil {
|
||||||
|
fn := dw.opt.SyncDataCb
|
||||||
|
if fn == nil && dw.opt.AsyncDataCb != nil {
|
||||||
|
fn = dw.opt.AsyncDataCb
|
||||||
|
}
|
||||||
|
if err := fn(ctx, p, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if hw != nil {
|
||||||
|
hw.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hw != nil {
|
||||||
|
return dw.opt.NotifyCb(kind, p, hw, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanRootPath(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return filepath.Clean(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootChtimes(root Root, p string, un int64) error {
|
||||||
|
t := time.Unix(0, un)
|
||||||
|
if err := root.Chtimes(p, t, t); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootLazyFileWriter struct {
|
||||||
|
lease *rootLease
|
||||||
|
f *os.File
|
||||||
|
fileMode *os.FileMode
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfw *rootLazyFileWriter) Write(dt []byte) (int, error) {
|
||||||
|
if lfw.f == nil {
|
||||||
|
file, err := lfw.lease.root.OpenFile(lfw.lease.base, os.O_WRONLY, 0)
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
// retry after chmod
|
||||||
|
fi, er := lfw.lease.root.Stat(lfw.lease.base)
|
||||||
|
if er == nil {
|
||||||
|
mode := fi.Mode()
|
||||||
|
lfw.fileMode = &mode
|
||||||
|
er = lfw.lease.root.Chmod(lfw.lease.base, mode|0222)
|
||||||
|
if er == nil {
|
||||||
|
file, err = lfw.lease.root.OpenFile(lfw.lease.base, os.O_WRONLY, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "failed to open %s", lfw.lease.base)
|
||||||
|
}
|
||||||
|
lfw.f = file
|
||||||
|
}
|
||||||
|
return lfw.f.Write(dt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lfw *rootLazyFileWriter) Close() error {
|
||||||
|
if lfw.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
lfw.closed = true
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if lfw.f != nil {
|
||||||
|
err = lfw.f.Close()
|
||||||
|
}
|
||||||
|
if err == nil && lfw.fileMode != nil {
|
||||||
|
err = lfw.lease.root.Chmod(lfw.lease.base, *lfw.fileMode)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/tonistiigi/fsutil/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rewriteRootMetadata(root Root, p string, stat *types.Stat) error {
|
||||||
|
for key, value := range stat.Xattrs {
|
||||||
|
root.LSetxattr(p, key, value, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := root.Lchown(p, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.FileMode(stat.Mode)&os.ModeSymlink != 0 {
|
||||||
|
return root.LChtimes(p, time.Unix(0, stat.ModTime))
|
||||||
|
}
|
||||||
|
if err := root.Chmod(p, os.FileMode(stat.Mode)); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
if err := rootChtimes(root, p, stat.ModTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tonistiigi/fsutil/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rewriteRootMetadata(root Root, p string, stat *types.Stat) error {
|
||||||
|
return root.LChtimes(p, time.Unix(0, stat.ModTime))
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package fsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rootStack struct {
|
||||||
|
items []rootStackItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootStackItem struct {
|
||||||
|
path string
|
||||||
|
root Root
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRootStack(root Root) *rootStack {
|
||||||
|
return &rootStack{
|
||||||
|
items: []rootStackItem{{path: ".", root: root}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootStack) get(path string) (Root, string, error) {
|
||||||
|
path = cleanRootPath(path)
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
base := filepath.Base(path)
|
||||||
|
|
||||||
|
for {
|
||||||
|
top := s.items[len(s.items)-1]
|
||||||
|
if top.path == dir {
|
||||||
|
return top.root, base, nil
|
||||||
|
}
|
||||||
|
if rel, ok := rootStackRel(top.path, dir); ok {
|
||||||
|
osroot, err := top.root.OpenRoot(rel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
root := NewRoot(osroot)
|
||||||
|
s.items = append(s.items, rootStackItem{path: dir, root: root})
|
||||||
|
return root, base, nil
|
||||||
|
}
|
||||||
|
if len(s.items) == 1 {
|
||||||
|
return nil, "", errors.WithStack(&os.PathError{Op: "openroot", Path: dir, Err: os.ErrNotExist})
|
||||||
|
}
|
||||||
|
if err := s.pop(); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootStack) Close() error {
|
||||||
|
var err error
|
||||||
|
for len(s.items) > 1 {
|
||||||
|
if err2 := s.pop(); err == nil {
|
||||||
|
err = err2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rootStack) pop() error {
|
||||||
|
i := len(s.items) - 1
|
||||||
|
item := s.items[i]
|
||||||
|
s.items = s.items[:i]
|
||||||
|
return item.root.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootStackRel returns target relative to base when target is below base.
|
||||||
|
func rootStackRel(base, target string) (string, bool) {
|
||||||
|
if base == "." {
|
||||||
|
if target == "." {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return target, true
|
||||||
|
}
|
||||||
|
rel, err := filepath.Rel(base, target)
|
||||||
|
if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return rel, true
|
||||||
|
}
|
||||||
+5
-5
@@ -14,15 +14,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var bufPool = sync.Pool{
|
var bufPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
buf := make([]byte, 32*1<<10)
|
buf := make([]byte, 32*1<<10)
|
||||||
return &buf
|
return &buf
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stream interface {
|
type Stream interface {
|
||||||
RecvMsg(interface{}) error
|
RecvMsg(any) error
|
||||||
SendMsg(m interface{}) error
|
SendMsg(m any) error
|
||||||
Context() context.Context
|
Context() context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ func (s *sender) run(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
for i := 0; i < 4; i++ {
|
for range 4 {
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for h := range s.sendpipeline {
|
for h := range s.sendpipeline {
|
||||||
select {
|
select {
|
||||||
@@ -210,7 +210,7 @@ type syncStream struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *syncStream) SendMsg(m interface{}) error {
|
func (ss *syncStream) SendMsg(m any) error {
|
||||||
ss.mu.Lock()
|
ss.mu.Lock()
|
||||||
err := ss.Stream.SendMsg(m)
|
err := ss.Stream.SendMsg(m)
|
||||||
ss.mu.Unlock()
|
ss.mu.Unlock()
|
||||||
|
|||||||
+1
-4
@@ -43,10 +43,7 @@ func loadXattr(origpath string, stat *types.Stat) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func skipXattr(key string) bool {
|
func skipXattr(key string) bool {
|
||||||
if strings.HasPrefix(key, xattrApplePrefix) {
|
return strings.HasPrefix(key, xattrApplePrefix)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setUnixOpt(fi os.FileInfo, stat *types.Stat, path string, seenFiles map[uint64]string) {
|
func setUnixOpt(fi os.FileInfo, stat *types.Stat, path string, seenFiles map[uint64]string) {
|
||||||
|
|||||||
+41
-73
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.31.0
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v3.11.4
|
// protoc v3.11.4
|
||||||
// source: github.com/tonistiigi/fsutil/types/stat.proto
|
// source: github.com/tonistiigi/fsutil/types/stat.proto
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -22,30 +23,27 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Stat struct {
|
type Stat struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
Mode uint32 `protobuf:"varint,2,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||||
|
Uid uint32 `protobuf:"varint,3,opt,name=uid,proto3" json:"uid,omitempty"`
|
||||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
Gid uint32 `protobuf:"varint,4,opt,name=gid,proto3" json:"gid,omitempty"`
|
||||||
Mode uint32 `protobuf:"varint,2,opt,name=mode,proto3" json:"mode,omitempty"`
|
Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
|
||||||
Uid uint32 `protobuf:"varint,3,opt,name=uid,proto3" json:"uid,omitempty"`
|
ModTime int64 `protobuf:"varint,6,opt,name=modTime,proto3" json:"modTime,omitempty"`
|
||||||
Gid uint32 `protobuf:"varint,4,opt,name=gid,proto3" json:"gid,omitempty"`
|
|
||||||
Size int64 `protobuf:"varint,5,opt,name=size,proto3" json:"size,omitempty"`
|
|
||||||
ModTime int64 `protobuf:"varint,6,opt,name=modTime,proto3" json:"modTime,omitempty"`
|
|
||||||
// int32 typeflag = 7;
|
// int32 typeflag = 7;
|
||||||
Linkname string `protobuf:"bytes,7,opt,name=linkname,proto3" json:"linkname,omitempty"`
|
Linkname string `protobuf:"bytes,7,opt,name=linkname,proto3" json:"linkname,omitempty"`
|
||||||
Devmajor int64 `protobuf:"varint,8,opt,name=devmajor,proto3" json:"devmajor,omitempty"`
|
Devmajor int64 `protobuf:"varint,8,opt,name=devmajor,proto3" json:"devmajor,omitempty"`
|
||||||
Devminor int64 `protobuf:"varint,9,opt,name=devminor,proto3" json:"devminor,omitempty"`
|
Devminor int64 `protobuf:"varint,9,opt,name=devminor,proto3" json:"devminor,omitempty"`
|
||||||
Xattrs map[string][]byte `protobuf:"bytes,10,rep,name=xattrs,proto3" json:"xattrs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
Xattrs map[string][]byte `protobuf:"bytes,10,rep,name=xattrs,proto3" json:"xattrs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Stat) Reset() {
|
func (x *Stat) Reset() {
|
||||||
*x = Stat{}
|
*x = Stat{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
||||||
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Stat) String() string {
|
func (x *Stat) String() string {
|
||||||
@@ -56,7 +54,7 @@ func (*Stat) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *Stat) ProtoReflect() protoreflect.Message {
|
func (x *Stat) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
mi := &file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -143,54 +141,39 @@ func (x *Stat) GetXattrs() map[string][]byte {
|
|||||||
|
|
||||||
var File_github_com_tonistiigi_fsutil_types_stat_proto protoreflect.FileDescriptor
|
var File_github_com_tonistiigi_fsutil_types_stat_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc = []byte{
|
const file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc = "" +
|
||||||
0x0a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x6e,
|
"\n" +
|
||||||
0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x74,
|
"-github.com/tonistiigi/fsutil/types/stat.proto\x12\ffsutil.types\x1a3github.com/planetscale/vtprotobuf/vtproto/ext.proto\"\xc7\x02\n" +
|
||||||
0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
"\x04Stat\x12\x12\n" +
|
||||||
0x0c, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x33, 0x67,
|
"\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" +
|
||||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x74,
|
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x10\n" +
|
||||||
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
"\x03uid\x18\x03 \x01(\rR\x03uid\x12\x10\n" +
|
||||||
0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f,
|
"\x03gid\x18\x04 \x01(\rR\x03gid\x12\x12\n" +
|
||||||
0x74, 0x6f, 0x22, 0xc7, 0x02, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70,
|
"\x04size\x18\x05 \x01(\x03R\x04size\x12\x18\n" +
|
||||||
0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12,
|
"\amodTime\x18\x06 \x01(\x03R\amodTime\x12\x1a\n" +
|
||||||
0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x6d,
|
"\blinkname\x18\a \x01(\tR\blinkname\x12\x1a\n" +
|
||||||
0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d,
|
"\bdevmajor\x18\b \x01(\x03R\bdevmajor\x12\x1a\n" +
|
||||||
0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
|
"\bdevminor\x18\t \x01(\x03R\bdevminor\x126\n" +
|
||||||
0x28, 0x0d, 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18,
|
"\x06xattrs\x18\n" +
|
||||||
0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d,
|
" \x03(\v2\x1e.fsutil.types.Stat.XattrsEntryR\x06xattrs\x1a9\n" +
|
||||||
0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x6f,
|
"\vXattrsEntry\x12\x10\n" +
|
||||||
0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x6e, 0x61, 0x6d,
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x6e, 0x61, 0x6d,
|
"\x05value\x18\x02 \x01(\fR\x05value:\x028\x01B$Z\"github.com/tonistiigi/fsutil/typesb\x06proto3"
|
||||||
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x76, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x08, 0x20,
|
|
||||||
0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x76, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x1a, 0x0a,
|
|
||||||
0x08, 0x64, 0x65, 0x76, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52,
|
|
||||||
0x08, 0x64, 0x65, 0x76, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x06, 0x78, 0x61, 0x74,
|
|
||||||
0x74, 0x72, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x66, 0x73, 0x75, 0x74,
|
|
||||||
0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x2e, 0x58, 0x61,
|
|
||||||
0x74, 0x74, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x78, 0x61, 0x74, 0x74, 0x72,
|
|
||||||
0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x58, 0x61, 0x74, 0x74, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
|
||||||
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
|
|
||||||
0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
|
||||||
0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x24, 0x5a, 0x22,
|
|
||||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x6e, 0x69, 0x73,
|
|
||||||
0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x74, 0x79, 0x70,
|
|
||||||
0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescOnce sync.Once
|
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescOnce sync.Once
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData = file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc
|
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescGZIP() []byte {
|
func file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescGZIP() []byte {
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescOnce.Do(func() {
|
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescOnce.Do(func() {
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData)
|
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData
|
return file_github_com_tonistiigi_fsutil_types_stat_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
var file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
var file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = []interface{}{
|
var file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = []any{
|
||||||
(*Stat)(nil), // 0: fsutil.types.Stat
|
(*Stat)(nil), // 0: fsutil.types.Stat
|
||||||
nil, // 1: fsutil.types.Stat.XattrsEntry
|
nil, // 1: fsutil.types.Stat.XattrsEntry
|
||||||
}
|
}
|
||||||
@@ -208,25 +191,11 @@ func file_github_com_tonistiigi_fsutil_types_stat_proto_init() {
|
|||||||
if File_github_com_tonistiigi_fsutil_types_stat_proto != nil {
|
if File_github_com_tonistiigi_fsutil_types_stat_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Stat); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 2,
|
NumMessages: 2,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
@@ -237,7 +206,6 @@ func file_github_com_tonistiigi_fsutil_types_stat_proto_init() {
|
|||||||
MessageInfos: file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes,
|
MessageInfos: file_github_com_tonistiigi_fsutil_types_stat_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_github_com_tonistiigi_fsutil_types_stat_proto = out.File
|
File_github_com_tonistiigi_fsutil_types_stat_proto = out.File
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_rawDesc = nil
|
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = nil
|
file_github_com_tonistiigi_fsutil_types_stat_proto_goTypes = nil
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_depIdxs = nil
|
file_github_com_tonistiigi_fsutil_types_stat_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-64
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.31.0
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v3.11.4
|
// protoc v3.11.4
|
||||||
// source: github.com/tonistiigi/fsutil/types/wire.proto
|
// source: github.com/tonistiigi/fsutil/types/wire.proto
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -77,23 +78,20 @@ func (Packet_PacketType) EnumDescriptor() ([]byte, []int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
sizeCache protoimpl.SizeCache
|
Type Packet_PacketType `protobuf:"varint,1,opt,name=type,proto3,enum=fsutil.types.Packet_PacketType" json:"type,omitempty"`
|
||||||
|
Stat *Stat `protobuf:"bytes,2,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||||
|
ID uint32 `protobuf:"varint,3,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
|
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
Type Packet_PacketType `protobuf:"varint,1,opt,name=type,proto3,enum=fsutil.types.Packet_PacketType" json:"type,omitempty"`
|
|
||||||
Stat *Stat `protobuf:"bytes,2,opt,name=stat,proto3" json:"stat,omitempty"`
|
|
||||||
ID uint32 `protobuf:"varint,3,opt,name=ID,proto3" json:"ID,omitempty"`
|
|
||||||
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) Reset() {
|
func (x *Packet) Reset() {
|
||||||
*x = Packet{}
|
*x = Packet{}
|
||||||
if protoimpl.UnsafeEnabled {
|
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
||||||
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms.StoreMessageInfo(mi)
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Packet) String() string {
|
func (x *Packet) String() string {
|
||||||
@@ -104,7 +102,7 @@ func (*Packet) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *Packet) ProtoReflect() protoreflect.Message {
|
func (x *Packet) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
mi := &file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@@ -149,53 +147,40 @@ func (x *Packet) GetData() []byte {
|
|||||||
|
|
||||||
var File_github_com_tonistiigi_fsutil_types_wire_proto protoreflect.FileDescriptor
|
var File_github_com_tonistiigi_fsutil_types_wire_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc = []byte{
|
const file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc = "" +
|
||||||
0x0a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x6e,
|
"\n" +
|
||||||
0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2f, 0x74,
|
"-github.com/tonistiigi/fsutil/types/wire.proto\x12\ffsutil.types\x1a3github.com/planetscale/vtprotobuf/vtproto/ext.proto\x1a-github.com/tonistiigi/fsutil/types/stat.proto\"\xef\x01\n" +
|
||||||
0x79, 0x70, 0x65, 0x73, 0x2f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
"\x06Packet\x123\n" +
|
||||||
0x0c, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x33, 0x67,
|
"\x04type\x18\x01 \x01(\x0e2\x1f.fsutil.types.Packet.PacketTypeR\x04type\x12&\n" +
|
||||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x74,
|
"\x04stat\x18\x02 \x01(\v2\x12.fsutil.types.StatR\x04stat\x12\x0e\n" +
|
||||||
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
"\x02ID\x18\x03 \x01(\rR\x02ID\x12\x12\n" +
|
||||||
0x2f, 0x76, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, 0x74, 0x2e, 0x70, 0x72, 0x6f,
|
"\x04data\x18\x04 \x01(\fR\x04data\"^\n" +
|
||||||
0x74, 0x6f, 0x1a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74,
|
"\n" +
|
||||||
0x6f, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c,
|
"PacketType\x12\x0f\n" +
|
||||||
0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
"\vPACKET_STAT\x10\x00\x12\x0e\n" +
|
||||||
0x6f, 0x22, 0xef, 0x01, 0x0a, 0x06, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x33, 0x0a, 0x04,
|
"\n" +
|
||||||
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x66, 0x73, 0x75,
|
"PACKET_REQ\x10\x01\x12\x0f\n" +
|
||||||
0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
|
"\vPACKET_DATA\x10\x02\x12\x0e\n" +
|
||||||
0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70,
|
"\n" +
|
||||||
0x65, 0x12, 0x26, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
"PACKET_FIN\x10\x03\x12\x0e\n" +
|
||||||
0x12, 0x2e, 0x66, 0x73, 0x75, 0x74, 0x69, 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53,
|
"\n" +
|
||||||
0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18,
|
"PACKET_ERR\x10\x04:\x04\xa8\xa6\x1f\x01B$Z\"github.com/tonistiigi/fsutil/typesb\x06proto3"
|
||||||
0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74,
|
|
||||||
0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x5e, 0x0a,
|
|
||||||
0x0a, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x50,
|
|
||||||
0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a,
|
|
||||||
0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b,
|
|
||||||
0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x02, 0x12, 0x0e, 0x0a,
|
|
||||||
0x0a, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x46, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x0e, 0x0a,
|
|
||||||
0x0a, 0x50, 0x41, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x04, 0x3a, 0x04, 0xa8,
|
|
||||||
0xa6, 0x1f, 0x01, 0x42, 0x24, 0x5a, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
|
||||||
0x6d, 0x2f, 0x74, 0x6f, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x69, 0x67, 0x69, 0x2f, 0x66, 0x73, 0x75,
|
|
||||||
0x74, 0x69, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
|
||||||
0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescOnce sync.Once
|
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescOnce sync.Once
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData = file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc
|
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescGZIP() []byte {
|
func file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescGZIP() []byte {
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescOnce.Do(func() {
|
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescOnce.Do(func() {
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData)
|
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc)))
|
||||||
})
|
})
|
||||||
return file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData
|
return file_github_com_tonistiigi_fsutil_types_wire_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_github_com_tonistiigi_fsutil_types_wire_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
var file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = []interface{}{
|
var file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = []any{
|
||||||
(Packet_PacketType)(0), // 0: fsutil.types.Packet.PacketType
|
(Packet_PacketType)(0), // 0: fsutil.types.Packet.PacketType
|
||||||
(*Packet)(nil), // 1: fsutil.types.Packet
|
(*Packet)(nil), // 1: fsutil.types.Packet
|
||||||
(*Stat)(nil), // 2: fsutil.types.Stat
|
(*Stat)(nil), // 2: fsutil.types.Stat
|
||||||
@@ -216,25 +201,11 @@ func file_github_com_tonistiigi_fsutil_types_wire_proto_init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
file_github_com_tonistiigi_fsutil_types_stat_proto_init()
|
file_github_com_tonistiigi_fsutil_types_stat_proto_init()
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Packet); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc,
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc), len(file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc)),
|
||||||
NumEnums: 1,
|
NumEnums: 1,
|
||||||
NumMessages: 1,
|
NumMessages: 1,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
@@ -246,7 +217,6 @@ func file_github_com_tonistiigi_fsutil_types_wire_proto_init() {
|
|||||||
MessageInfos: file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes,
|
MessageInfos: file_github_com_tonistiigi_fsutil_types_wire_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_github_com_tonistiigi_fsutil_types_wire_proto = out.File
|
File_github_com_tonistiigi_fsutil_types_wire_proto = out.File
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_rawDesc = nil
|
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = nil
|
file_github_com_tonistiigi_fsutil_types_wire_proto_goTypes = nil
|
||||||
file_github_com_tonistiigi_fsutil_types_wire_proto_depIdxs = nil
|
file_github_com_tonistiigi_fsutil_types_wire_proto_depIdxs = nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-9
@@ -67,8 +67,8 @@ func (v *Validator) HandleChange(kind ChangeKind, p string, fi os.FileInfo, err
|
|||||||
|
|
||||||
func ComparePath(p1, p2 string) int {
|
func ComparePath(p1, p2 string) int {
|
||||||
// byte-by-byte comparison to be compatible with str<>str
|
// byte-by-byte comparison to be compatible with str<>str
|
||||||
min := min(len(p1), len(p2))
|
n := min(len(p1), len(p2))
|
||||||
for i := 0; i < min; i++ {
|
for i := range n {
|
||||||
switch {
|
switch {
|
||||||
case p1[i] == p2[i]:
|
case p1[i] == p2[i]:
|
||||||
continue
|
continue
|
||||||
@@ -80,10 +80,3 @@ func ComparePath(p1, p2 string) int {
|
|||||||
}
|
}
|
||||||
return len(p1) - len(p2)
|
return len(p1) - len(p2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(x, y int) int {
|
|
||||||
if x < y {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|||||||
Generated
Vendored
-11
@@ -47,7 +47,6 @@ type config struct {
|
|||||||
TracerProvider trace.TracerProvider
|
TracerProvider trace.TracerProvider
|
||||||
MeterProvider metric.MeterProvider
|
MeterProvider metric.MeterProvider
|
||||||
SpanKind trace.SpanKind
|
SpanKind trace.SpanKind
|
||||||
SpanStartOptions []trace.SpanStartOption
|
|
||||||
SpanAttributes []attribute.KeyValue
|
SpanAttributes []attribute.KeyValue
|
||||||
MetricAttributes []attribute.KeyValue
|
MetricAttributes []attribute.KeyValue
|
||||||
MetricAttributesFn func(ctx context.Context) []attribute.KeyValue
|
MetricAttributesFn func(ctx context.Context) []attribute.KeyValue
|
||||||
@@ -204,16 +203,6 @@ func WithMessageEvents(events ...Event) Option {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSpanOptions configures an additional set of
|
|
||||||
// trace.SpanOptions, which are applied to each new span.
|
|
||||||
//
|
|
||||||
// Deprecated: It is only used by the deprecated interceptor, and is unused by [NewClientHandler] and [NewServerHandler].
|
|
||||||
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
|
|
||||||
return optionFunc(func(c *config) {
|
|
||||||
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSpanKind returns an Option to set the span kind for spans created by
|
// WithSpanKind returns an Option to set the span kind for spans created by
|
||||||
// the handler.
|
// the handler.
|
||||||
//
|
//
|
||||||
|
|||||||
Generated
Vendored
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||||
grpc_codes "google.golang.org/grpc/codes"
|
grpc_codes "google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|||||||
vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/parse.go
Generated
Vendored
+1
-1
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
oldsemconv "go.opentelemetry.io/otel/semconv/v1.37.0" //nolint:depguard // Use of v1.37.0 is required for backward compatibility stability opt-in.
|
oldsemconv "go.opentelemetry.io/otel/semconv/v1.37.0" //nolint:depguard // Use of v1.37.0 is required for backward compatibility stability opt-in.
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseFullMethod returns a span name following the OpenTelemetry semantic
|
// ParseFullMethod returns a span name following the OpenTelemetry semantic
|
||||||
|
|||||||
Generated
Vendored
+31
-19
@@ -13,8 +13,8 @@ import (
|
|||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
oldrpcconv "go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv" //nolint:depguard // Use of v1.37.0 is required for backward compatibility stability opt-in.
|
oldrpcconv "go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv" //nolint:depguard // Use of v1.37.0 is required for backward compatibility stability opt-in.
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||||
"go.opentelemetry.io/otel/semconv/v1.40.0/rpcconv"
|
"go.opentelemetry.io/otel/semconv/v1.41.0/rpcconv"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
grpc_codes "google.golang.org/grpc/codes"
|
grpc_codes "google.golang.org/grpc/codes"
|
||||||
@@ -367,27 +367,39 @@ func (*config) handleRPC(
|
|||||||
span.End()
|
span.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
var metricAttrs []attribute.KeyValue
|
var durationEnabled bool
|
||||||
if gctx != nil {
|
var oldDurationEnabled bool
|
||||||
// Don't use gctx.metricAttrSet here, because it requires passing
|
|
||||||
// multiple RecordOptions, which would call metric.mergeSets and
|
|
||||||
// allocate a new set for each Record call.
|
|
||||||
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
|
|
||||||
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
|
|
||||||
}
|
|
||||||
metricAttrs = append(metricAttrs, rpcStatusAttr)
|
|
||||||
// Allocate vararg slice once.
|
|
||||||
recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))}
|
|
||||||
|
|
||||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
|
||||||
// Measure right before calling Record() to capture as much elapsed time as possible.
|
|
||||||
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Second)
|
|
||||||
|
|
||||||
if duration != nil {
|
if duration != nil {
|
||||||
duration.Record(ctx, elapsedTime, recordOpts...)
|
durationEnabled = duration.Enabled(ctx)
|
||||||
}
|
}
|
||||||
if oldDuration != nil {
|
if oldDuration != nil {
|
||||||
oldDuration.Record(ctx, elapsedTime*1000.0, recordOpts...)
|
oldDurationEnabled = oldDuration.Enabled(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if durationEnabled || oldDurationEnabled {
|
||||||
|
var metricAttrs []attribute.KeyValue
|
||||||
|
if gctx != nil {
|
||||||
|
// Don't use gctx.metricAttrSet here, because it requires passing
|
||||||
|
// multiple RecordOptions, which would call metric.mergeSets and
|
||||||
|
// allocate a new set for each Record call.
|
||||||
|
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
|
||||||
|
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
|
||||||
|
}
|
||||||
|
metricAttrs = append(metricAttrs, rpcStatusAttr)
|
||||||
|
// Allocate vararg slice once.
|
||||||
|
recordOpts := []metric.RecordOption{metric.WithAttributeSet(attribute.NewSet(metricAttrs...))}
|
||||||
|
|
||||||
|
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||||
|
// Measure right before calling Record() to capture as much elapsed time as possible.
|
||||||
|
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Second)
|
||||||
|
|
||||||
|
if durationEnabled {
|
||||||
|
duration.Record(ctx, elapsedTime, recordOpts...)
|
||||||
|
}
|
||||||
|
if oldDurationEnabled {
|
||||||
|
oldDuration.Record(ctx, elapsedTime*1000.0, recordOpts...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
Generated
Vendored
+1
-1
@@ -4,4 +4,4 @@
|
|||||||
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||||
|
|
||||||
// Version is the current release version of the gRPC instrumentation.
|
// Version is the current release version of the gRPC instrumentation.
|
||||||
const Version = "0.68.0"
|
const Version = "0.69.0"
|
||||||
|
|||||||
Generated
Vendored
+22
-11
@@ -63,8 +63,20 @@ func (fn clientTraceOptionFunc) apply(c *clientTracer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithoutSubSpans will modify the httptrace.ClientTrace to only collect data
|
// WithoutSubSpans will modify the httptrace.ClientTrace to only collect data
|
||||||
// as Events and Attributes on a span found in the context. By default
|
// as Events and Attributes on a span found in the context. By default
|
||||||
// sub-spans will be generated.
|
// sub-spans will be generated.
|
||||||
|
//
|
||||||
|
// This option is recommended for services that make a large number of
|
||||||
|
// outbound HTTP requests per incoming request (e.g., API gateways,
|
||||||
|
// fan-out proxies, or GraphQL servers). Each outbound request creates
|
||||||
|
// up to 7 sub-spans (http.getconn, http.dns, http.connect, http.tls,
|
||||||
|
// http.headers, http.send, http.receive) as separate heap-allocated
|
||||||
|
// trace.Span objects. In high fan-out services this multiplied span
|
||||||
|
// volume can overwhelm the span processor queue and increase GC
|
||||||
|
// pressure, resulting in elevated and continuously growing memory usage.
|
||||||
|
// Using WithoutSubSpans replaces those spans with lightweight events on
|
||||||
|
// the parent span, preserving the diagnostic information at a fraction
|
||||||
|
// of the cost.
|
||||||
func WithoutSubSpans() ClientTraceOption {
|
func WithoutSubSpans() ClientTraceOption {
|
||||||
return clientTraceOptionFunc(func(ct *clientTracer) {
|
return clientTraceOptionFunc(func(ct *clientTracer) {
|
||||||
ct.useSpans = false
|
ct.useSpans = false
|
||||||
@@ -189,10 +201,11 @@ func NewClientTrace(ctx context.Context, opts ...ClientTraceOption) *httptrace.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ct *clientTracer) start(hook, spanName string, attrs ...attribute.KeyValue) {
|
func (ct *clientTracer) start(hook, spanName string, attrs ...attribute.KeyValue) {
|
||||||
|
if ct.root == nil {
|
||||||
|
ct.root = trace.SpanFromContext(ct.Context)
|
||||||
|
}
|
||||||
|
|
||||||
if !ct.useSpans {
|
if !ct.useSpans {
|
||||||
if ct.root == nil {
|
|
||||||
ct.root = trace.SpanFromContext(ct.Context)
|
|
||||||
}
|
|
||||||
ct.root.AddEvent(hook+".start", trace.WithAttributes(attrs...))
|
ct.root.AddEvent(hook+".start", trace.WithAttributes(attrs...))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -201,11 +214,7 @@ func (ct *clientTracer) start(hook, spanName string, attrs ...attribute.KeyValue
|
|||||||
defer ct.mtx.Unlock()
|
defer ct.mtx.Unlock()
|
||||||
|
|
||||||
if hookCtx, found := ct.activeHooks[hook]; !found {
|
if hookCtx, found := ct.activeHooks[hook]; !found {
|
||||||
var sp trace.Span
|
ct.activeHooks[hook], _ = ct.tr.Start(ct.getParentContext(hook), spanName, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient))
|
||||||
ct.activeHooks[hook], sp = ct.tr.Start(ct.getParentContext(hook), spanName, trace.WithAttributes(attrs...), trace.WithSpanKind(trace.SpanKindClient))
|
|
||||||
if ct.root == nil {
|
|
||||||
ct.root = sp
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// end was called before start finished, add the start attributes and end the span here
|
// end was called before start finished, add the start attributes and end the span here
|
||||||
span := trace.SpanFromContext(hookCtx)
|
span := trace.SpanFromContext(hookCtx)
|
||||||
@@ -306,14 +315,16 @@ func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ct *clientTracer) connectStart(network, addr string) {
|
func (ct *clientTracer) connectStart(network, addr string) {
|
||||||
ct.start("http.connect."+addr, "http.connect",
|
ct.start(
|
||||||
|
"http.connect."+addr, "http.connect",
|
||||||
HTTPRemoteAddr.String(addr),
|
HTTPRemoteAddr.String(addr),
|
||||||
HTTPConnectionStartNetwork.String(network),
|
HTTPConnectionStartNetwork.String(network),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ct *clientTracer) connectDone(network, addr string, err error) {
|
func (ct *clientTracer) connectDone(network, addr string, err error) {
|
||||||
ct.end("http.connect."+addr, err,
|
ct.end(
|
||||||
|
"http.connect."+addr, err,
|
||||||
HTTPConnectionDoneAddr.String(addr),
|
HTTPConnectionDoneAddr.String(addr),
|
||||||
HTTPConnectionDoneNetwork.String(network),
|
HTTPConnectionDoneNetwork.String(network),
|
||||||
)
|
)
|
||||||
|
|||||||
Generated
Vendored
+41
-4
@@ -12,6 +12,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -19,8 +20,8 @@ import (
|
|||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
"go.opentelemetry.io/otel/semconv/v1.40.0"
|
"go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||||
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
|
"go.opentelemetry.io/otel/semconv/v1.41.0/httpconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPClient struct {
|
type HTTPClient struct {
|
||||||
@@ -166,7 +167,7 @@ func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue
|
|||||||
|
|
||||||
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
|
||||||
if method == "" {
|
if method == "" {
|
||||||
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
|
return semconv.HTTPRequestMethodOther, attribute.KeyValue{}
|
||||||
}
|
}
|
||||||
if attr, ok := methodLookup[method]; ok {
|
if attr, ok := methodLookup[method]; ok {
|
||||||
return attr, attribute.KeyValue{}
|
return attr, attribute.KeyValue{}
|
||||||
@@ -176,7 +177,7 @@ func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValu
|
|||||||
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
|
||||||
return attr, orig
|
return attr, orig
|
||||||
}
|
}
|
||||||
return semconv.HTTPRequestMethodGet, orig
|
return semconv.HTTPRequestMethodOther, orig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
|
||||||
@@ -249,6 +250,9 @@ func (o MetricOpts) AddOptions() metric.AddOption {
|
|||||||
|
|
||||||
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
||||||
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
|
||||||
|
if ma.StatusCode == 0 && ma.Err != nil {
|
||||||
|
attributes = append(attributes, n.ErrorType(ma.Err))
|
||||||
|
}
|
||||||
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
|
||||||
|
|
||||||
return MetricOpts{
|
return MetricOpts{
|
||||||
@@ -257,6 +261,39 @@ func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorType returns an error.type attribute for the given error. The otelhttp
|
||||||
|
// Transport calls the underlying RoundTripper directly, so transport failures
|
||||||
|
// arrive as *net.OpError (connection refused, timeout, etc.) rather than
|
||||||
|
// *url.Error (which http.Client.Do adds above the Transport layer). This
|
||||||
|
// function intentionally does not unwrap further: reporting a single concrete
|
||||||
|
// type per failure keeps attribute cardinality bounded, as required by the
|
||||||
|
// OTel spec (error.type SHOULD have low cardinality). Callers that need
|
||||||
|
// finer-grained error distinctions should inspect the error themselves.
|
||||||
|
func (n HTTPClient) ErrorType(err error) attribute.KeyValue {
|
||||||
|
t := reflect.TypeOf(err)
|
||||||
|
if t == nil {
|
||||||
|
return semconv.ErrorTypeOther
|
||||||
|
}
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
var value string
|
||||||
|
if t.PkgPath() == "" || t.Name() == "" {
|
||||||
|
// t.PkgPath() == "" covers builtin and unnamed types.
|
||||||
|
// t.Name() == "" covers anonymous struct types that implement error,
|
||||||
|
// which are uncommon but possible. Fall back to t.String() for both.
|
||||||
|
value = t.String()
|
||||||
|
} else {
|
||||||
|
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
return semconv.ErrorTypeOther
|
||||||
|
}
|
||||||
|
|
||||||
|
return semconv.ErrorTypeKey.String(value)
|
||||||
|
}
|
||||||
|
|
||||||
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
|
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
|
||||||
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user