From f3775c00461443254e9c796123ad38cd3d3bd564 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:35:12 +0200 Subject: [PATCH] bump compose-go version to v1.17.0 to fix issue with depends_on Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- bake/compose_test.go | 18 + go.mod | 9 +- go.sum | 18 +- .../compose-spec/compose-go/cli/options.go | 62 +-- .../compose-spec/compose-go/dotenv/env.go | 84 +++ .../compose-spec/compose-go/dotenv/parser.go | 13 +- .../compose-go/loader/full-example.yml | 3 +- .../compose-spec/compose-go/loader/include.go | 120 +++++ .../compose-spec/compose-go/loader/loader.go | 203 ++++---- .../compose-spec/compose-go/loader/merge.go | 11 +- .../compose-go/loader/normalize.go | 71 +-- .../compose-spec/compose-go/loader/paths.go | 135 +++++ .../compose-go/schema/compose-spec.json | 35 +- .../compose-spec/compose-go/schema/schema.go | 7 +- .../compose-go/template/template.go | 182 +++++-- .../compose-spec/compose-go/types/config.go | 24 +- .../compose-spec/compose-go/types/project.go | 50 +- .../compose-spec/compose-go/types/types.go | 23 +- .../compose-go/utils/collectionutils.go | 51 ++ vendor/github.com/imdario/mergo/README.md | 20 +- vendor/golang.org/x/exp/LICENSE | 27 + vendor/golang.org/x/exp/PATENTS | 22 + .../x/exp/constraints/constraints.go | 50 ++ vendor/golang.org/x/exp/slices/slices.go | 282 ++++++++++ vendor/golang.org/x/exp/slices/sort.go | 128 +++++ vendor/golang.org/x/exp/slices/zsortfunc.go | 479 +++++++++++++++++ .../golang.org/x/exp/slices/zsortordered.go | 481 ++++++++++++++++++ vendor/golang.org/x/sync/errgroup/errgroup.go | 10 +- vendor/golang.org/x/sync/errgroup/go120.go | 14 + .../golang.org/x/sync/errgroup/pre_go120.go | 15 + vendor/modules.txt | 14 +- 31 files changed, 2313 insertions(+), 348 deletions(-) create mode 100644 vendor/github.com/compose-spec/compose-go/dotenv/env.go create mode 100644 vendor/github.com/compose-spec/compose-go/loader/include.go create mode 100644 vendor/github.com/compose-spec/compose-go/loader/paths.go create mode 100644 vendor/github.com/compose-spec/compose-go/utils/collectionutils.go create mode 100644 vendor/golang.org/x/exp/LICENSE create mode 100644 vendor/golang.org/x/exp/PATENTS create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go create mode 100644 vendor/golang.org/x/exp/slices/slices.go create mode 100644 vendor/golang.org/x/exp/slices/sort.go create mode 100644 vendor/golang.org/x/exp/slices/zsortfunc.go create mode 100644 vendor/golang.org/x/exp/slices/zsortordered.go create mode 100644 vendor/golang.org/x/sync/errgroup/go120.go create mode 100644 vendor/golang.org/x/sync/errgroup/pre_go120.go diff --git a/bake/compose_test.go b/bake/compose_test.go index b5f9961b..4c11a203 100644 --- a/bake/compose_test.go +++ b/bake/compose_test.go @@ -656,6 +656,24 @@ services: require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args) } +func TestDependsOn(t *testing.T) { + var dt = []byte(` +services: + foo: + build: + context: . + ports: + - 3306:3306 + depends_on: + - bar + bar: + build: + context: . +`) + _, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil) + require.NoError(t, err) +} + // chdir changes the current working directory to the named directory, // and then restore the original working directory at the end of the test. func chdir(t *testing.T, dir string) { diff --git a/go.mod b/go.mod index c8b0f4c4..6a258311 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/aws/aws-sdk-go-v2/config v1.18.16 - github.com/compose-spec/compose-go v1.14.0 + github.com/compose-spec/compose-go v1.17.0 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.7.2 github.com/containerd/continuity v0.4.1 @@ -39,8 +39,8 @@ require ( github.com/zclconf/go-cty v1.10.0 go.opentelemetry.io/otel v1.14.0 go.opentelemetry.io/otel/trace v1.14.0 - golang.org/x/mod v0.9.0 - golang.org/x/sync v0.2.0 + golang.org/x/mod v0.11.0 + golang.org/x/sync v0.3.0 golang.org/x/term v0.8.0 google.golang.org/grpc v1.53.0 gopkg.in/yaml.v3 v3.0.1 @@ -105,7 +105,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect - github.com/imdario/mergo v0.3.15 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/in-toto/in-toto-golang v0.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/gorm v1.9.2 // indirect @@ -157,6 +157,7 @@ require ( go.opentelemetry.io/otel/sdk v1.14.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect golang.org/x/crypto v0.2.0 // indirect + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.8.0 // indirect diff --git a/go.sum b/go.sum index 5b1a1a5f..30b12578 100644 --- a/go.sum +++ b/go.sum @@ -122,8 +122,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= -github.com/compose-spec/compose-go v1.14.0 h1:/+tQxBEPIrfsi87Qh7/VjMzcJN3BRNER/RO71ku+u6E= -github.com/compose-spec/compose-go v1.14.0/go.mod h1:m0o4G6MQDHjjz9rY7No9FpnNi+9sKic262rzrwuCqic= +github.com/compose-spec/compose-go v1.17.0 h1:cvje90CU94dQyTnJoHJYjx9yE4Iggse1XmGcO3Qi5ts= +github.com/compose-spec/compose-go v1.17.0/go.mod h1:zR2tP1+kZHi5vJz7PjpW6oMoDji/Js3GHjP+hfjf70Q= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= @@ -316,8 +316,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY= github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF/XEFBbY= github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -565,6 +565,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -585,8 +587,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -638,8 +640,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/compose-spec/compose-go/cli/options.go b/vendor/github.com/compose-spec/compose-go/cli/options.go index 0815dcc0..e6d49cdf 100644 --- a/vendor/github.com/compose-spec/compose-go/cli/options.go +++ b/vendor/github.com/compose-spec/compose-go/cli/options.go @@ -17,7 +17,6 @@ package cli import ( - "bytes" "io" "os" "path/filepath" @@ -250,7 +249,7 @@ func WithDotEnv(o *ProjectOptions) error { if err != nil { return err } - envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFiles) + envMap, err := dotenv.GetEnvFromFile(o.Environment, wd, o.EnvFiles) if err != nil { return err } @@ -262,65 +261,6 @@ func WithDotEnv(o *ProjectOptions) error { return nil } -func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) { - envMap := make(map[string]string) - - dotEnvFiles := filenames - if len(dotEnvFiles) == 0 { - dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env")) - } - for _, dotEnvFile := range dotEnvFiles { - abs, err := filepath.Abs(dotEnvFile) - if err != nil { - return envMap, err - } - dotEnvFile = abs - - s, err := os.Stat(dotEnvFile) - if os.IsNotExist(err) { - if len(filenames) == 0 { - return envMap, nil - } - return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile) - } - if err != nil { - return envMap, err - } - - if s.IsDir() { - if len(filenames) == 0 { - return envMap, nil - } - return envMap, errors.Errorf("%s is a directory", dotEnvFile) - } - - b, err := os.ReadFile(dotEnvFile) - if os.IsNotExist(err) { - return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile) - } - if err != nil { - return envMap, err - } - - env, err := dotenv.ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) { - v, ok := currentEnv[k] - if ok { - return v, true - } - v, ok = envMap[k] - return v, ok - }) - if err != nil { - return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) - } - for k, v := range env { - envMap[k] = v - } - } - - return envMap, nil -} - // WithInterpolation set ProjectOptions to enable/skip interpolation func WithInterpolation(interpolation bool) ProjectOptionsFn { return func(o *ProjectOptions) error { diff --git a/vendor/github.com/compose-spec/compose-go/dotenv/env.go b/vendor/github.com/compose-spec/compose-go/dotenv/env.go new file mode 100644 index 00000000..c8a538bc --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/dotenv/env.go @@ -0,0 +1,84 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package dotenv + +import ( + "bytes" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) { + envMap := make(map[string]string) + + dotEnvFiles := filenames + if len(dotEnvFiles) == 0 { + dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env")) + } + for _, dotEnvFile := range dotEnvFiles { + abs, err := filepath.Abs(dotEnvFile) + if err != nil { + return envMap, err + } + dotEnvFile = abs + + s, err := os.Stat(dotEnvFile) + if os.IsNotExist(err) { + if len(filenames) == 0 { + return envMap, nil + } + return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile) + } + if err != nil { + return envMap, err + } + + if s.IsDir() { + if len(filenames) == 0 { + return envMap, nil + } + return envMap, errors.Errorf("%s is a directory", dotEnvFile) + } + + b, err := os.ReadFile(dotEnvFile) + if os.IsNotExist(err) { + return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile) + } + if err != nil { + return envMap, err + } + + env, err := ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) { + v, ok := currentEnv[k] + if ok { + return v, true + } + v, ok = envMap[k] + return v, ok + }) + if err != nil { + return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) + } + for k, v := range env { + envMap[k] = v + } + } + + return envMap, nil +} diff --git a/vendor/github.com/compose-spec/compose-go/dotenv/parser.go b/vendor/github.com/compose-spec/compose-go/dotenv/parser.go index bbcd318f..aec72a88 100644 --- a/vendor/github.com/compose-spec/compose-go/dotenv/parser.go +++ b/vendor/github.com/compose-spec/compose-go/dotenv/parser.go @@ -123,8 +123,8 @@ loop: } return "", "", inherited, fmt.Errorf( - `line %d: unexpected character %q in variable name`, - p.line, string(rune)) + `line %d: unexpected character %q in variable name %q`, + p.line, string(rune), strings.Split(src, "\n")[0]) } } @@ -153,17 +153,24 @@ func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn return retVal, rest, err } + previousCharIsEscape := false // lookup quoted string terminator for i := 1; i < len(src); i++ { if src[i] == '\n' { p.line++ } if char := src[i]; char != quote { + if !previousCharIsEscape && char == '\\' { + previousCharIsEscape = true + } else { + previousCharIsEscape = false + } continue } // skip escaped quote symbol (\" or \', depends on quote) - if prevChar := src[i-1]; prevChar == '\\' { + if previousCharIsEscape { + previousCharIsEscape = false continue } diff --git a/vendor/github.com/compose-spec/compose-go/loader/full-example.yml b/vendor/github.com/compose-spec/compose-go/loader/full-example.yml index 9f818f8c..24d95457 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/full-example.yml +++ b/vendor/github.com/compose-spec/compose-go/loader/full-example.yml @@ -24,7 +24,7 @@ services: - bar labels: [FOO=BAR] additional_contexts: - foo: /bar + foo: ./bar secrets: - secret1 - source: secret2 @@ -181,6 +181,7 @@ services: timeout: 1s retries: 5 start_period: 15s + start_interval: 5s # Any valid image reference - repo, tag, id, sha image: redis diff --git a/vendor/github.com/compose-spec/compose-go/loader/include.go b/vendor/github.com/compose-spec/compose-go/loader/include.go new file mode 100644 index 00000000..eeddf8b1 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/loader/include.go @@ -0,0 +1,120 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package loader + +import ( + "fmt" + "path/filepath" + + "github.com/compose-spec/compose-go/dotenv" + "github.com/compose-spec/compose-go/types" + "github.com/pkg/errors" +) + +// LoadIncludeConfig parse the require config from raw yaml +func LoadIncludeConfig(source []interface{}) ([]types.IncludeConfig, error) { + var requires []types.IncludeConfig + err := Transform(source, &requires) + return requires, err +} + +var transformIncludeConfig TransformerFunc = func(data interface{}) (interface{}, error) { + switch value := data.(type) { + case string: + return map[string]interface{}{"path": value}, nil + case map[string]interface{}: + return value, nil + default: + return data, errors.Errorf("invalid type %T for `include` configuration", value) + } +} + +func loadInclude(configDetails types.ConfigDetails, model *types.Config, options *Options, loaded []string) (*types.Config, error) { + for _, r := range model.Include { + for i, p := range r.Path { + if !filepath.IsAbs(p) { + r.Path[i] = filepath.Join(configDetails.WorkingDir, p) + } + } + if r.ProjectDirectory == "" { + r.ProjectDirectory = filepath.Dir(r.Path[0]) + } + + loadOptions := options.clone() + loadOptions.SetProjectName(model.Name, true) + loadOptions.ResolvePaths = true + loadOptions.SkipNormalization = true + loadOptions.SkipConsistencyCheck = true + + env, err := dotenv.GetEnvFromFile(configDetails.Environment, r.ProjectDirectory, r.EnvFile) + if err != nil { + return nil, err + } + + imported, err := load(types.ConfigDetails{ + WorkingDir: r.ProjectDirectory, + ConfigFiles: types.ToConfigFiles(r.Path), + Environment: env, + }, loadOptions, loaded) + if err != nil { + return nil, err + } + + err = importResources(model, imported, r.Path) + if err != nil { + return nil, err + } + } + model.Include = nil + return model, nil +} + +// importResources import into model all resources defined by imported, and report error on conflict +func importResources(model *types.Config, imported *types.Project, path []string) error { + services := mapByName(model.Services) + for _, service := range imported.Services { + if _, ok := services[service.Name]; ok { + return fmt.Errorf("imported compose file %s defines conflicting service %s", path, service.Name) + } + model.Services = append(model.Services, service) + } + for n, network := range imported.Networks { + if _, ok := model.Networks[n]; ok { + return fmt.Errorf("imported compose file %s defines conflicting network %s", path, n) + } + model.Networks[n] = network + } + for n, volume := range imported.Volumes { + if _, ok := model.Volumes[n]; ok { + return fmt.Errorf("imported compose file %s defines conflicting volume %s", path, n) + } + model.Volumes[n] = volume + } + for n, secret := range imported.Secrets { + if _, ok := model.Secrets[n]; ok { + return fmt.Errorf("imported compose file %s defines conflicting secret %s", path, n) + } + model.Secrets[n] = secret + } + for n, config := range imported.Configs { + if _, ok := model.Configs[n]; ok { + return fmt.Errorf("imported compose file %s defines conflicting config %s", path, n) + } + model.Configs[n] = config + } + return nil +} diff --git a/vendor/github.com/compose-spec/compose-go/loader/loader.go b/vendor/github.com/compose-spec/compose-go/loader/loader.go index 8873b29b..76dbe2ff 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/loader.go +++ b/vendor/github.com/compose-spec/compose-go/loader/loader.go @@ -56,6 +56,8 @@ type Options struct { SkipConsistencyCheck bool // Skip extends SkipExtends bool + // SkipInclude will ignore `include` and only load model from file(s) set by ConfigDetails + SkipInclude bool // Interpolation options Interpolate *interp.Options // Discard 'env_file' entries after resolving to 'environment' section @@ -68,6 +70,24 @@ type Options struct { Profiles []string } +func (o *Options) clone() *Options { + return &Options{ + SkipValidation: o.SkipValidation, + SkipInterpolation: o.SkipInterpolation, + SkipNormalization: o.SkipNormalization, + ResolvePaths: o.ResolvePaths, + ConvertWindowsPaths: o.ConvertWindowsPaths, + SkipConsistencyCheck: o.SkipConsistencyCheck, + SkipExtends: o.SkipExtends, + SkipInclude: o.SkipInclude, + Interpolate: o.Interpolate, + discardEnvFiles: o.discardEnvFiles, + projectName: o.projectName, + projectNameImperativelySet: o.projectNameImperativelySet, + Profiles: o.Profiles, + } +} + func (o *Options) SetProjectName(name string, imperativelySet bool) { o.projectName = name o.projectNameImperativelySet = imperativelySet @@ -185,6 +205,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. LookupValue: configDetails.LookupEnv, TypeCastMapping: interpolateTypeCastMapping, }, + ResolvePaths: true, } for _, op := range options { @@ -195,8 +216,22 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. if err != nil { return nil, err } + opts.projectName = projectName + return load(configDetails, opts, nil) +} +func load(configDetails types.ConfigDetails, opts *Options, loaded []string) (*types.Project, error) { var model *types.Config + + mainFile := configDetails.ConfigFiles[0].Filename + for _, f := range loaded { + if f == mainFile { + loaded = append(loaded, mainFile) + return nil, errors.Errorf("include cycle detected:\n%s\n include %s", loaded[0], strings.Join(loaded[1:], "\n include ")) + } + } + loaded = append(loaded, mainFile) + for i, file := range configDetails.ConfigFiles { var postProcessor PostProcessor configDict := file.Config @@ -231,10 +266,18 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. return nil, err } + if !opts.SkipInclude { + cfg, err = loadInclude(configDetails, cfg, opts, loaded) + if err != nil { + return nil, err + } + } + if i == 0 { model = cfg continue } + merged, err := merge([]*types.Config{model, cfg}) if err != nil { return nil, err @@ -248,16 +291,8 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. model = merged } - for _, s := range model.Services { - var newEnvFiles types.StringList - for _, ef := range s.EnvFile { - newEnvFiles = append(newEnvFiles, absPath(configDetails.WorkingDir, ef)) - } - s.EnvFile = newEnvFiles - } - project := &types.Project{ - Name: projectName, + Name: opts.projectName, WorkingDir: configDetails.WorkingDir, Services: model.Services, Networks: model.Networks, @@ -269,14 +304,30 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. } if !opts.SkipNormalization { - err = Normalize(project, opts.ResolvePaths) + err := Normalize(project) if err != nil { return nil, err } } + if opts.ResolvePaths { + err := ResolveRelativePaths(project) + if err != nil { + return nil, err + } + } + + if opts.ConvertWindowsPaths { + for i, service := range project.Services { + for j, volume := range service.Volumes { + service.Volumes[j] = convertVolumePath(volume) + } + project.Services[i] = service + } + } + if !opts.SkipConsistencyCheck { - err = checkConsistency(project) + err := checkConsistency(project) if err != nil { return nil, err } @@ -287,7 +338,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. } project.ApplyProfiles(opts.Profiles) - err = project.ResolveServicesEnvironment(opts.discardEnvFiles) + err := project.ResolveServicesEnvironment(opts.discardEnvFiles) return project, err } @@ -419,7 +470,6 @@ func loadSections(filename string, config map[string]interface{}, configDetails if err != nil { return nil, err } - cfg.Networks, err = LoadNetworks(getSection(config, "networks")) if err != nil { return nil, err @@ -428,11 +478,15 @@ func loadSections(filename string, config map[string]interface{}, configDetails if err != nil { return nil, err } - cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths) + cfg.Secrets, err = LoadSecrets(getSection(config, "secrets")) if err != nil { return nil, err } - cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths) + cfg.Configs, err = LoadConfigObjs(getSection(config, "configs")) + if err != nil { + return nil, err + } + cfg.Include, err = LoadIncludeConfig(getSequence(config, "include")) if err != nil { return nil, err } @@ -451,6 +505,14 @@ func getSection(config map[string]interface{}, key string) map[string]interface{ return section.(map[string]interface{}) } +func getSequence(config map[string]interface{}, key string) []interface{} { + section, ok := config[key] + if !ok { + return make([]interface{}, 0) + } + return section.([]interface{}) +} + // ForbiddenPropertiesError is returned when there are properties in the Compose // file that are forbidden. type ForbiddenPropertiesError struct { @@ -515,6 +577,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig, reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest, reflect.TypeOf(types.SSHConfig{}): transformSSHConfig, + reflect.TypeOf(types.IncludeConfig{}): transformIncludeConfig, } for _, transformer := range additionalTransformers { @@ -605,6 +668,7 @@ func LoadServices(filename string, servicesDict map[string]interface{}, workingD for k, v := range x.(map[string]interface{}) { servicesDict[k] = v } + delete(servicesDict, extensions) } for name := range servicesDict { @@ -633,7 +697,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter target = map[string]interface{}{} } - serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths) + serviceConfig, err := LoadService(name, target.(map[string]interface{})) if err != nil { return nil, err } @@ -671,21 +735,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter // make the paths relative to `file` rather than `baseFilePath` so // that the resulting paths won't be absolute if `file` isn't an // absolute path. - baseFileParent := filepath.Dir(file) - if baseService.Build != nil { - baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context) - } - - for i, vol := range baseService.Volumes { - if vol.Type != types.VolumeTypeBind { - continue - } - baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv) - } - for i, envFile := range baseService.EnvFile { - baseService.EnvFile[i] = resolveMaybeUnixPath(envFile, baseFileParent, lookupEnv) - } + baseFileParent := filepath.Dir(file) + ResolveServiceRelativePaths(baseFileParent, baseService) } serviceConfig, err = _merge(baseService, serviceConfig) @@ -698,22 +750,9 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter return serviceConfig, nil } -func resolveBuildContextPath(baseFileParent string, context string) string { - // Checks if the context is an HTTP(S) URL or a remote git repository URL - for _, prefix := range []string{"https://", "http://", "git://", "github.com/", "git@"} { - if strings.HasPrefix(context, prefix) { - return context - } - } - - // Note that the Dockerfile is always defined relative to the - // build context, so there's no need to update the Dockerfile field. - return absPath(baseFileParent, context) -} - // LoadService produces a single ServiceConfig from a compose file Dict // the serviceDict is not validated if directly used. Use Load() to enable validation -func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) { +func LoadService(name string, serviceDict map[string]interface{}) (*types.ServiceConfig, error) { serviceConfig := &types.ServiceConfig{ Scale: 1, } @@ -730,13 +769,6 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`) } - if resolvePaths || convertPaths { - volume = resolveVolumePath(volume, workingDir, lookupEnv) - } - - if convertPaths { - volume = convertVolumePath(volume) - } serviceConfig.Volumes[i] = volume } @@ -758,8 +790,8 @@ func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConf return volume } -func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Mapping) string { - filePath := expandUser(path, lookupEnv) +func resolveMaybeUnixPath(workingDir string, path string) string { + filePath := expandUser(path) // Check if source is an absolute path (either Unix or Windows), to // handle a Windows client with a Unix daemon or vice-versa. // @@ -772,20 +804,8 @@ func resolveMaybeUnixPath(path string, workingDir string, lookupEnv template.Map return filePath } -func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig { - volume.Source = resolveMaybeUnixPath(volume.Source, workingDir, lookupEnv) - return volume -} - -func resolveSecretsPath(secret types.SecretConfig, workingDir string, lookupEnv template.Mapping) types.SecretConfig { - if !secret.External.External && secret.File != "" { - secret.File = resolveMaybeUnixPath(secret.File, workingDir, lookupEnv) - } - return secret -} - // TODO: make this more robust -func expandUser(path string, lookupEnv template.Mapping) string { +func expandUser(path string) string { if strings.HasPrefix(path, "~") { home, err := os.UserHomeDir() if err != nil { @@ -885,44 +905,39 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig, // LoadSecrets produces a SecretConfig map from a compose file Dict // the source Dict is not validated if directly used. Use Load() to enable validation -func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) { +func LoadSecrets(source map[string]interface{}) (map[string]types.SecretConfig, error) { secrets := make(map[string]types.SecretConfig) if err := Transform(source, &secrets); err != nil { return secrets, err } for name, secret := range secrets { - obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, false) + obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret)) if err != nil { return nil, err } - secretConfig := types.SecretConfig(obj) - if resolvePaths { - secretConfig = resolveSecretsPath(secretConfig, details.WorkingDir, details.LookupEnv) - } - secrets[name] = secretConfig + secrets[name] = types.SecretConfig(obj) } return secrets, nil } // LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict // the source Dict is not validated if directly used. Use Load() to enable validation -func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) { +func LoadConfigObjs(source map[string]interface{}) (map[string]types.ConfigObjConfig, error) { configs := make(map[string]types.ConfigObjConfig) if err := Transform(source, &configs); err != nil { return configs, err } for name, config := range configs { - obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths) + obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config)) if err != nil { return nil, err } - configConfig := types.ConfigObjConfig(obj) - configs[name] = configConfig + configs[name] = types.ConfigObjConfig(obj) } return configs, nil } -func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) { +func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig) (types.FileObjectConfig, error) { // if "external: true" switch { case obj.External.External: @@ -942,26 +957,11 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi if obj.File != "" { return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name) } - default: - if obj.File != "" && resolvePaths { - obj.File = absPath(details.WorkingDir, obj.File) - } } return obj, nil } -func absPath(workingDir string, filePath string) string { - if strings.HasPrefix(filePath, "~") { - home, _ := os.UserHomeDir() - return filepath.Join(home, filePath[1:]) - } - if filepath.IsAbs(filePath) { - return filePath - } - return filepath.Join(workingDir, filePath) -} - var transformMapStringString TransformerFunc = func(data interface{}) (interface{}, error) { switch value := data.(type) { case map[string]interface{}: @@ -1088,13 +1088,24 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface for _, serviceIntf := range value { service, ok := serviceIntf.(string) if !ok { - return data, errors.Errorf("invalid type %T for service depends_on elementn, expected string", value) + return data, errors.Errorf("invalid type %T for service depends_on element, expected string", value) } - transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted} + transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted, "required": true} } return transformed, nil case map[string]interface{}: - return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil + transformed := map[string]interface{}{} + for service, val := range value { + dependsConfigIntf, ok := val.(map[string]interface{}) + if !ok { + return data, errors.Errorf("invalid type %T for service depends_on element", value) + } + if _, ok := dependsConfigIntf["required"]; !ok { + dependsConfigIntf["required"] = true + } + transformed[service] = dependsConfigIntf + } + return groupXFieldsIntoExtensions(transformed), nil default: return data, errors.Errorf("invalid type %T for service depends_on", value) } diff --git a/vendor/github.com/compose-spec/compose-go/loader/merge.go b/vendor/github.com/compose-spec/compose-go/loader/merge.go index c6b25f5e..3c4848e0 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/merge.go +++ b/vendor/github.com/compose-spec/compose-go/loader/merge.go @@ -150,13 +150,12 @@ func unique(slice []string) []string { return nil } uniqMap := make(map[string]struct{}) + var uniqSlice []string for _, v := range slice { - uniqMap[v] = struct{}{} - } - - uniqSlice := make([]string, 0, len(uniqMap)) - for v := range uniqMap { - uniqSlice = append(uniqSlice, v) + if _, ok := uniqMap[v]; !ok { + uniqSlice = append(uniqSlice, v) + uniqMap[v] = struct{}{} + } } return uniqSlice } diff --git a/vendor/github.com/compose-spec/compose-go/loader/normalize.go b/vendor/github.com/compose-spec/compose-go/loader/normalize.go index f6610b14..58863b5f 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/normalize.go +++ b/vendor/github.com/compose-spec/compose-go/loader/normalize.go @@ -18,8 +18,6 @@ package loader import ( "fmt" - "os" - "path/filepath" "strings" "github.com/compose-spec/compose-go/errdefs" @@ -29,19 +27,7 @@ import ( ) // Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults -func Normalize(project *types.Project, resolvePaths bool) error { - absWorkingDir, err := filepath.Abs(project.WorkingDir) - if err != nil { - return err - } - project.WorkingDir = absWorkingDir - - absComposeFiles, err := absComposeFiles(project.ComposeFiles) - if err != nil { - return err - } - project.ComposeFiles = absComposeFiles - +func Normalize(project *types.Project) error { if project.Networks == nil { project.Networks = make(map[string]types.NetworkConfig) } @@ -51,8 +37,7 @@ func Normalize(project *types.Project, resolvePaths bool) error { project.Networks["default"] = types.NetworkConfig{} } - err = relocateExternalName(project) - if err != nil { + if err := relocateExternalName(project); err != nil { return err } @@ -72,38 +57,16 @@ func Normalize(project *types.Project, resolvePaths bool) error { } if s.Build != nil { + if s.Build.Context == "" { + s.Build.Context = "." + } if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" { s.Build.Dockerfile = "Dockerfile" } - if resolvePaths { - // Build context might be a remote http/git context. Unfortunately supported "remote" - // syntax is highly ambiguous in moby/moby and not defined by compose-spec, - // so let's assume runtime will check - localContext := absPath(project.WorkingDir, s.Build.Context) - if _, err := os.Stat(localContext); err == nil { - s.Build.Context = localContext - } - for name, path := range s.Build.AdditionalContexts { - if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type - continue - } - path = absPath(project.WorkingDir, path) - if _, err := os.Stat(path); err == nil { - s.Build.AdditionalContexts[name] = path - } - } - } s.Build.Args = s.Build.Args.Resolve(fn) } - for j, f := range s.EnvFile { - s.EnvFile[j] = absPath(project.WorkingDir, f) - } s.Environment = s.Environment.Resolve(fn) - if s.Extends != nil && s.Extends.File != "" { - s.Extends.File = absPath(project.WorkingDir, s.Extends.File) - } - for _, link := range s.Links { parts := strings.Split(link, ":") if len(parts) == 2 { @@ -112,6 +75,7 @@ func Normalize(project *types.Project, resolvePaths bool) error { s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{ Condition: types.ServiceConditionStarted, Restart: true, + Required: true, }) } @@ -121,6 +85,7 @@ func Normalize(project *types.Project, resolvePaths bool) error { s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{ Condition: types.ServiceConditionStarted, Restart: true, + Required: true, }) } } @@ -131,6 +96,7 @@ func Normalize(project *types.Project, resolvePaths bool) error { s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{ Condition: types.ServiceConditionStarted, Restart: false, + Required: true, }) } } @@ -160,14 +126,6 @@ func Normalize(project *types.Project, resolvePaths bool) error { project.Services[i] = s } - for name, config := range project.Volumes { - if config.Driver == "local" && config.DriverOpts["o"] == "bind" { - // This is actually a bind mount - config.DriverOpts["device"] = absPath(project.WorkingDir, config.DriverOpts["device"]) - project.Volumes[name] = config - } - } - setNameFromKey(project) return nil @@ -223,6 +181,7 @@ func inferImplicitDependencies(service *types.ServiceConfig) { if _, ok := service.DependsOn[d]; !ok { service.DependsOn[d] = types.ServiceDependency{ Condition: types.ServiceConditionStarted, + Required: true, } } } @@ -254,18 +213,6 @@ func relocateScale(s *types.ServiceConfig) error { return nil } -func absComposeFiles(composeFiles []string) ([]string, error) { - absComposeFiles := make([]string, len(composeFiles)) - for i, composeFile := range composeFiles { - absComposefile, err := filepath.Abs(composeFile) - if err != nil { - return nil, err - } - absComposeFiles[i] = absComposefile - } - return absComposeFiles, nil -} - // Resources with no explicit name are actually named by their key in map func setNameFromKey(project *types.Project) { for i, n := range project.Networks { diff --git a/vendor/github.com/compose-spec/compose-go/loader/paths.go b/vendor/github.com/compose-spec/compose-go/loader/paths.go new file mode 100644 index 00000000..45bbc545 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/loader/paths.go @@ -0,0 +1,135 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package loader + +import ( + "os" + "path/filepath" + "strings" + + "github.com/compose-spec/compose-go/types" +) + +// ResolveRelativePaths resolves relative paths based on project WorkingDirectory +func ResolveRelativePaths(project *types.Project) error { + absWorkingDir, err := filepath.Abs(project.WorkingDir) + if err != nil { + return err + } + project.WorkingDir = absWorkingDir + + absComposeFiles, err := absComposeFiles(project.ComposeFiles) + if err != nil { + return err + } + project.ComposeFiles = absComposeFiles + + for i, s := range project.Services { + ResolveServiceRelativePaths(project.WorkingDir, &s) + project.Services[i] = s + } + + for i, obj := range project.Configs { + if obj.File != "" { + obj.File = absPath(project.WorkingDir, obj.File) + project.Configs[i] = obj + } + } + + for i, obj := range project.Secrets { + if obj.File != "" { + obj.File = resolveMaybeUnixPath(project.WorkingDir, obj.File) + project.Secrets[i] = obj + } + } + + for name, config := range project.Volumes { + if config.Driver == "local" && config.DriverOpts["o"] == "bind" { + // This is actually a bind mount + config.DriverOpts["device"] = resolveMaybeUnixPath(project.WorkingDir, config.DriverOpts["device"]) + project.Volumes[name] = config + } + } + return nil +} + +func ResolveServiceRelativePaths(workingDir string, s *types.ServiceConfig) { + if s.Build != nil { + if !isRemoteContext(s.Build.Context) { + s.Build.Context = absPath(workingDir, s.Build.Context) + } + for name, path := range s.Build.AdditionalContexts { + if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type + continue + } + if isRemoteContext(path) { + continue + } + s.Build.AdditionalContexts[name] = absPath(workingDir, path) + } + } + for j, f := range s.EnvFile { + s.EnvFile[j] = absPath(workingDir, f) + } + + if s.Extends != nil && s.Extends.File != "" { + s.Extends.File = absPath(workingDir, s.Extends.File) + } + + for i, vol := range s.Volumes { + if vol.Type != types.VolumeTypeBind { + continue + } + s.Volumes[i].Source = resolveMaybeUnixPath(workingDir, vol.Source) + } +} + +func absPath(workingDir string, filePath string) string { + if strings.HasPrefix(filePath, "~") { + home, _ := os.UserHomeDir() + return filepath.Join(home, filePath[1:]) + } + if filepath.IsAbs(filePath) { + return filePath + } + return filepath.Join(workingDir, filePath) +} + +func absComposeFiles(composeFiles []string) ([]string, error) { + for i, composeFile := range composeFiles { + absComposefile, err := filepath.Abs(composeFile) + if err != nil { + return nil, err + } + composeFiles[i] = absComposefile + } + return composeFiles, nil +} + +// isRemoteContext returns true if the value is a Git reference or HTTP(S) URL. +// +// Any other value is assumed to be a local filesystem path and returns false. +// +// See: https://github.com/moby/buildkit/blob/18fc875d9bfd6e065cd8211abc639434ba65aa56/frontend/dockerui/context.go#L76-L79 +func isRemoteContext(maybeURL string) bool { + for _, prefix := range []string{"https://", "http://", "git://", "ssh://", "github.com/", "git@"} { + if strings.HasPrefix(maybeURL, prefix) { + return true + } + } + return false +} diff --git a/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json b/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json index 53f3c1db..d39aa35e 100644 --- a/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json +++ b/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2019-09/schema#", + "$schema": "https://json-schema.org/draft/2019-09/schema#", "id": "compose_spec.json", "type": "object", "title": "Compose Specification", @@ -17,6 +17,15 @@ "description": "define the Compose project name, until user defines one explicitly." }, + "include": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/include" + }, + "description": "compose sub-projects to be included." + }, + "services": { "id": "#/properties/services", "type": "object", @@ -84,6 +93,7 @@ "properties": { "deploy": {"$ref": "#/definitions/deployment"}, "annotations": {"$ref": "#/definitions/list_or_dict"}, + "attach": {"type": "boolean"}, "build": { "oneOf": [ {"type": "string"}, @@ -181,6 +191,10 @@ "additionalProperties": false, "properties": { "restart": {"type": "boolean"}, + "required": { + "type": "boolean", + "default": true + }, "condition": { "type": "string", "enum": ["service_started", "service_healthy", "service_completed_successfully"] @@ -443,7 +457,8 @@ ] }, "timeout": {"type": "string", "format": "duration"}, - "start_period": {"type": "string", "format": "duration"} + "start_period": {"type": "string", "format": "duration"}, + "start_interval": {"type": "string", "format": "duration"} }, "additionalProperties": false, "patternProperties": {"^x-": {}} @@ -588,6 +603,22 @@ } }, + "include": { + "id": "#/definitions/include", + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "properties": { + "path": {"$ref": "#/definitions/string_or_list"}, + "env_file": {"$ref": "#/definitions/string_or_list"}, + "project_directory": {"type": "string"} + }, + "additionalProperties": false + } + ] + }, + "network": { "id": "#/definitions/network", "type": ["object", "null"], diff --git a/vendor/github.com/compose-spec/compose-go/schema/schema.go b/vendor/github.com/compose-spec/compose-go/schema/schema.go index 23394a34..bfbaa935 100644 --- a/vendor/github.com/compose-spec/compose-go/schema/schema.go +++ b/vendor/github.com/compose-spec/compose-go/schema/schema.go @@ -17,19 +17,18 @@ package schema import ( + // Enable support for embedded static resources + _ "embed" "fmt" "strings" "time" "github.com/xeipuuv/gojsonschema" - - // Enable support for embedded static resources - _ "embed" ) type portsFormatChecker struct{} -func (checker portsFormatChecker) IsFormat(input interface{}) bool { +func (checker portsFormatChecker) IsFormat(_ interface{}) bool { // TODO: implement this return true } diff --git a/vendor/github.com/compose-spec/compose-go/template/template.go b/vendor/github.com/compose-spec/compose-go/template/template.go index 3d548e09..cce4c625 100644 --- a/vendor/github.com/compose-spec/compose-go/template/template.go +++ b/vendor/github.com/compose-spec/compose-go/template/template.go @@ -17,6 +17,7 @@ package template import ( + "errors" "fmt" "regexp" "sort" @@ -71,77 +72,148 @@ type Mapping func(string) (string, bool) // the substitution and an error. type SubstituteFunc func(string, Mapping) (string, bool, error) -// SubstituteWith substitute variables in the string with their values. -// It accepts additional substitute function. -func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) { - var outerErr error - var returnErr error +// ReplacementFunc is a user-supplied function that is apply to the matching +// substring. Returns the value as a string and an error. +type ReplacementFunc func(string, Mapping, *Config) (string, error) - result := pattern.ReplaceAllStringFunc(template, func(substring string) string { - _, subsFunc := getSubstitutionFunctionForTemplate(substring) - if len(subsFuncs) > 0 { - subsFunc = subsFuncs[0] - } +type Config struct { + pattern *regexp.Regexp + substituteFunc SubstituteFunc + replacementFunc ReplacementFunc + logging bool +} - closingBraceIndex := getFirstBraceClosingIndex(substring) - rest := "" - if closingBraceIndex > -1 { - rest = substring[closingBraceIndex+1:] - substring = substring[0 : closingBraceIndex+1] - } +type Option func(*Config) - matches := pattern.FindStringSubmatch(substring) - groups := matchGroups(matches, pattern) - if escaped := groups["escaped"]; escaped != "" { - return escaped - } +func WithPattern(pattern *regexp.Regexp) Option { + return func(cfg *Config) { + cfg.pattern = pattern + } +} - braced := false - substitution := groups["named"] - if substitution == "" { - substitution = groups["braced"] - braced = true - } +func WithSubstitutionFunction(subsFunc SubstituteFunc) Option { + return func(cfg *Config) { + cfg.substituteFunc = subsFunc + } +} - if substitution == "" { - outerErr = &InvalidTemplateError{Template: template} - if returnErr == nil { - returnErr = outerErr - } - return "" - } +func WithReplacementFunction(replacementFunc ReplacementFunc) Option { + return func(cfg *Config) { + cfg.replacementFunc = replacementFunc + } +} + +func WithoutLogging(cfg *Config) { + cfg.logging = false +} + +// SubstituteWithOptions substitute variables in the string with their values. +// It accepts additional options such as a custom function or pattern. +func SubstituteWithOptions(template string, mapping Mapping, options ...Option) (string, error) { + var returnErr error + + cfg := &Config{ + pattern: defaultPattern, + replacementFunc: DefaultReplacementFunc, + logging: true, + } + for _, o := range options { + o(cfg) + } - if braced { - var ( - value string - applied bool - ) - value, applied, outerErr = subsFunc(substitution, mapping) - if outerErr != nil { - if returnErr == nil { - returnErr = outerErr + result := cfg.pattern.ReplaceAllStringFunc(template, func(substring string) string { + replacement, err := cfg.replacementFunc(substring, mapping, cfg) + if err != nil { + // Add the template for template errors + var tmplErr *InvalidTemplateError + if errors.As(err, &tmplErr) { + if tmplErr.Template == "" { + tmplErr.Template = template } - return "" } - if applied { - interpolatedNested, err := SubstituteWith(rest, mapping, pattern) - if err != nil { - return "" - } - return value + interpolatedNested + // Save the first error to be returned + if returnErr == nil { + returnErr = err } - } - value, ok := mapping(substitution) - if !ok { - logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution) } - return value + return replacement }) return result, returnErr } +func DefaultReplacementFunc(substring string, mapping Mapping, cfg *Config) (string, error) { + value, _, err := DefaultReplacementAppliedFunc(substring, mapping, cfg) + return value, err +} + +func DefaultReplacementAppliedFunc(substring string, mapping Mapping, cfg *Config) (string, bool, error) { + pattern := cfg.pattern + subsFunc := cfg.substituteFunc + if subsFunc == nil { + _, subsFunc = getSubstitutionFunctionForTemplate(substring) + } + + closingBraceIndex := getFirstBraceClosingIndex(substring) + rest := "" + if closingBraceIndex > -1 { + rest = substring[closingBraceIndex+1:] + substring = substring[0 : closingBraceIndex+1] + } + + matches := pattern.FindStringSubmatch(substring) + groups := matchGroups(matches, pattern) + if escaped := groups["escaped"]; escaped != "" { + return escaped, true, nil + } + + braced := false + substitution := groups["named"] + if substitution == "" { + substitution = groups["braced"] + braced = true + } + + if substitution == "" { + return "", false, &InvalidTemplateError{} + } + + if braced { + value, applied, err := subsFunc(substitution, mapping) + if err != nil { + return "", false, err + } + if applied { + interpolatedNested, err := SubstituteWith(rest, mapping, pattern) + if err != nil { + return "", false, err + } + return value + interpolatedNested, true, nil + } + } + + value, ok := mapping(substitution) + if !ok && cfg.logging { + logrus.Warnf("The %q variable is not set. Defaulting to a blank string.", substitution) + } + + return value, ok, nil +} + +// SubstituteWith substitute variables in the string with their values. +// It accepts additional substitute function. +func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) { + options := []Option{ + WithPattern(pattern), + } + if len(subsFuncs) > 0 { + options = append(options, WithSubstitutionFunction(subsFuncs[0])) + } + + return SubstituteWithOptions(template, mapping, options...) +} + func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) { interpolationMapping := []struct { string diff --git a/vendor/github.com/compose-spec/compose-go/types/config.go b/vendor/github.com/compose-spec/compose-go/types/config.go index 020e67dd..517e6122 100644 --- a/vendor/github.com/compose-spec/compose-go/types/config.go +++ b/vendor/github.com/compose-spec/compose-go/types/config.go @@ -67,16 +67,24 @@ type ConfigFile struct { Config map[string]interface{} } +func ToConfigFiles(path []string) (f []ConfigFile) { + for _, p := range path { + f = append(f, ConfigFile{Filename: p}) + } + return +} + // Config is a full compose file configuration and model type Config struct { - Filename string `yaml:"-" json:"-"` - Name string `yaml:",omitempty" json:"name,omitempty"` - Services Services `json:"services"` - Networks Networks `yaml:",omitempty" json:"networks,omitempty"` - Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"` - Secrets Secrets `yaml:",omitempty" json:"secrets,omitempty"` - Configs Configs `yaml:",omitempty" json:"configs,omitempty"` - Extensions Extensions `yaml:",inline" json:"-"` + Filename string `yaml:"-" json:"-"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Services Services `yaml:"services" json:"services"` + Networks Networks `yaml:"networks,omitempty" json:"networks,omitempty"` + Volumes Volumes `yaml:"volumes,omitempty" json:"volumes,omitempty"` + Secrets Secrets `yaml:"secrets,omitempty" json:"secrets,omitempty"` + Configs Configs `yaml:"configs,omitempty" json:"configs,omitempty"` + Extensions Extensions `yaml:",inline" json:"-"` + Include []IncludeConfig `yaml:"include,omitempty" json:"include,omitempty"` } // Volumes is a map of VolumeConfig diff --git a/vendor/github.com/compose-spec/compose-go/types/project.go b/vendor/github.com/compose-spec/compose-go/types/project.go index 896bcd69..42c24714 100644 --- a/vendor/github.com/compose-spec/compose-go/types/project.go +++ b/vendor/github.com/compose-spec/compose-go/types/project.go @@ -24,6 +24,8 @@ import ( "path/filepath" "sort" + "github.com/compose-spec/compose-go/utils" + "github.com/compose-spec/compose-go/dotenv" "github.com/distribution/distribution/v3/reference" godigest "github.com/opencontainers/go-digest" @@ -102,10 +104,19 @@ func (p *Project) ConfigNames() []string { // GetServices retrieve services by names, or return all services if no name specified func (p *Project) GetServices(names ...string) (Services, error) { + services, servicesNotFound := p.getServicesByNames(names...) + if len(servicesNotFound) > 0 { + return services, fmt.Errorf("no such service: %s", servicesNotFound[0]) + } + return services, nil +} + +func (p *Project) getServicesByNames(names ...string) (Services, []string) { if len(names) == 0 { return p.Services, nil } services := Services{} + var servicesNotFound []string for _, name := range names { var serviceConfig *ServiceConfig for _, s := range p.Services { @@ -115,11 +126,12 @@ func (p *Project) GetServices(names ...string) (Services, error) { } } if serviceConfig == nil { - return services, fmt.Errorf("no such service: %s", name) + servicesNotFound = append(servicesNotFound, name) + continue } services = append(services, *serviceConfig) } - return services, nil + return services, servicesNotFound } // GetDisabledService retrieve disabled service by name @@ -159,26 +171,30 @@ func (p *Project) WithServices(names []string, fn ServiceFunc, options ...Depend // backward compatibility options = []DependencyOption{IncludeDependencies} } - return p.withServices(names, fn, map[string]bool{}, options) + return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{}) } -func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption) error { - services, err := p.GetServices(names...) - if err != nil { - return err +func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption, dependencies map[string]ServiceDependency) error { + services, servicesNotFound := p.getServicesByNames(names...) + if len(servicesNotFound) > 0 { + for _, serviceNotFound := range servicesNotFound { + if dependency, ok := dependencies[serviceNotFound]; !ok || dependency.Required { + return fmt.Errorf("no such service: %s", serviceNotFound) + } + } } for _, service := range services { if seen[service.Name] { continue } seen[service.Name] = true - var dependencies []string + var dependencies map[string]ServiceDependency for _, policy := range options { switch policy { case IncludeDependents: - dependencies = append(dependencies, p.GetDependentsForService(service)...) + dependencies = utils.MapsAppend(dependencies, p.dependentsForService(service)) case IncludeDependencies: - dependencies = append(dependencies, service.GetDependencies()...) + dependencies = utils.MapsAppend(dependencies, service.DependsOn) case IgnoreDependencies: // Noop default: @@ -186,7 +202,7 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b } } if len(dependencies) > 0 { - err := p.withServices(dependencies, fn, seen, options) + err := p.withServices(utils.MapKeys(dependencies), fn, seen, options, dependencies) if err != nil { return err } @@ -199,11 +215,15 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b } func (p *Project) GetDependentsForService(s ServiceConfig) []string { - var dependent []string + return utils.MapKeys(p.dependentsForService(s)) +} + +func (p *Project) dependentsForService(s ServiceConfig) map[string]ServiceDependency { + dependent := make(map[string]ServiceDependency) for _, service := range p.Services { - for name := range service.DependsOn { + for name, dependency := range service.DependsOn { if name == s.Name { - dependent = append(dependent, service.Name) + dependent[service.Name] = dependency } } } @@ -507,7 +527,7 @@ func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error { fileVars, err := dotenv.ParseWithLookup(bytes.NewBuffer(b), resolve) if err != nil { - return err + return errors.Wrapf(err, "failed to read %s", envFile) } environment.OverrideBy(Mapping(fileVars).ToMappingWithEquals()) } diff --git a/vendor/github.com/compose-spec/compose-go/types/types.go b/vendor/github.com/compose-spec/compose-go/types/types.go index 4c67bc66..a9a515f0 100644 --- a/vendor/github.com/compose-spec/compose-go/types/types.go +++ b/vendor/github.com/compose-spec/compose-go/types/types.go @@ -89,6 +89,7 @@ type ServiceConfig struct { Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"` Annotations Mapping `yaml:"annotations,omitempty" json:"annotations,omitempty"` + Attach *bool `yaml:"attach,omitempty" json:"attach,omitempty"` Build *BuildConfig `yaml:"build,omitempty" json:"build,omitempty"` BlkioConfig *BlkioConfig `yaml:"blkio_config,omitempty" json:"blkio_config,omitempty"` CapAdd []string `yaml:"cap_add,omitempty" json:"cap_add,omitempty"` @@ -602,12 +603,13 @@ type DeployConfig struct { // HealthCheckConfig the healthcheck configuration for a service type HealthCheckConfig struct { - Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"` - Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` - Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"` - Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"` - StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"` - Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"` + Test HealthCheckTest `yaml:"test,omitempty" json:"test,omitempty"` + Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` + Interval *Duration `yaml:"interval,omitempty" json:"interval,omitempty"` + Retries *uint64 `yaml:"retries,omitempty" json:"retries,omitempty"` + StartPeriod *Duration `yaml:"start_period,omitempty" json:"start_period,omitempty"` + StartInterval *Duration `yaml:"start_interval,omitempty" json:"start_interval,omitempty"` + Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"` Extensions Extensions `yaml:"#extensions,inline" json:"-"` } @@ -815,6 +817,8 @@ const ( VolumeTypeTmpfs = "tmpfs" // VolumeTypeNamedPipe is the type for mounting Windows named pipes VolumeTypeNamedPipe = "npipe" + // VolumeTypeCluster is the type for mounting container storage interface (CSI) volumes + VolumeTypeCluster = "cluster" // SElinuxShared share the volume content SElinuxShared = "z" @@ -1023,6 +1027,7 @@ type ServiceDependency struct { Condition string `yaml:"condition,omitempty" json:"condition,omitempty"` Restart bool `yaml:"restart,omitempty" json:"restart,omitempty"` Extensions Extensions `yaml:"#extensions,inline" json:"-"` + Required bool `yaml:"required" json:"required"` } type ExtendsConfig struct { @@ -1035,3 +1040,9 @@ type SecretConfig FileObjectConfig // ConfigObjConfig is the config for the swarm "Config" object type ConfigObjConfig FileObjectConfig + +type IncludeConfig struct { + Path StringList `yaml:"path,omitempty" json:"path,omitempty"` + ProjectDirectory string `yaml:"project_directory,omitempty" json:"project_directory,omitempty"` + EnvFile StringList `yaml:"env_file,omitempty" json:"env_file,omitempty"` +} diff --git a/vendor/github.com/compose-spec/compose-go/utils/collectionutils.go b/vendor/github.com/compose-spec/compose-go/utils/collectionutils.go new file mode 100644 index 00000000..34369225 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/utils/collectionutils.go @@ -0,0 +1,51 @@ +/* + Copyright 2020 The Compose Specification Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package utils + +import "golang.org/x/exp/slices" + +func MapKeys[T comparable, U any](theMap map[T]U) []T { + var result []T + for key := range theMap { + result = append(result, key) + } + return result +} + +func MapsAppend[T comparable, U any](target map[T]U, source map[T]U) map[T]U { + if target == nil { + return source + } + if source == nil { + return target + } + for key, value := range source { + if _, ok := target[key]; !ok { + target[key] = value + } + } + return target +} + +func ArrayContains[T comparable](source []T, toCheck []T) bool { + for _, value := range toCheck { + if !slices.Contains(source, value) { + return false + } + } + return true +} diff --git a/vendor/github.com/imdario/mergo/README.md b/vendor/github.com/imdario/mergo/README.md index 4f028749..ffbbb62c 100644 --- a/vendor/github.com/imdario/mergo/README.md +++ b/vendor/github.com/imdario/mergo/README.md @@ -1,17 +1,20 @@ # Mergo -[![GoDoc][3]][4] [![GitHub release][5]][6] [![GoCard][7]][8] -[![Build Status][1]][2] -[![Coverage Status][9]][10] +[![Test status][1]][2] +[![OpenSSF Scorecard][21]][22] +[![OpenSSF Best Practices][19]][20] +[![Coverage status][9]][10] [![Sourcegraph][11]][12] -[![FOSSA Status][13]][14] +[![FOSSA status][13]][14] + +[![GoDoc][3]][4] [![Become my sponsor][15]][16] [![Tidelift][17]][18] -[1]: https://travis-ci.org/imdario/mergo.png -[2]: https://travis-ci.org/imdario/mergo +[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master +[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml [3]: https://godoc.org/github.com/imdario/mergo?status.svg [4]: https://godoc.org/github.com/imdario/mergo [5]: https://img.shields.io/github/release/imdario/mergo.svg @@ -28,6 +31,10 @@ [16]: https://github.com/sponsors/imdario [17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo [18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo +[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge +[20]: https://bestpractices.coreinfrastructure.org/projects/7177 +[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge +[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. @@ -232,5 +239,4 @@ Written by [Dario Castañé](http://dario.im). [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). - [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) diff --git a/vendor/golang.org/x/exp/LICENSE b/vendor/golang.org/x/exp/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/exp/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/exp/PATENTS b/vendor/golang.org/x/exp/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/exp/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go new file mode 100644 index 00000000..2c033dff --- /dev/null +++ b/vendor/golang.org/x/exp/constraints/constraints.go @@ -0,0 +1,50 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package constraints defines a set of useful constraints to be used +// with type parameters. +package constraints + +// Signed is a constraint that permits any signed integer type. +// If future releases of Go add new predeclared signed integer types, +// this constraint will be modified to include them. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned is a constraint that permits any unsigned integer type. +// If future releases of Go add new predeclared unsigned integer types, +// this constraint will be modified to include them. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// Integer is a constraint that permits any integer type. +// If future releases of Go add new predeclared integer types, +// this constraint will be modified to include them. +type Integer interface { + Signed | Unsigned +} + +// Float is a constraint that permits any floating-point type. +// If future releases of Go add new predeclared floating-point types, +// this constraint will be modified to include them. +type Float interface { + ~float32 | ~float64 +} + +// Complex is a constraint that permits any complex numeric type. +// If future releases of Go add new predeclared complex numeric types, +// this constraint will be modified to include them. +type Complex interface { + ~complex64 | ~complex128 +} + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +// If future releases of Go add new ordered types, +// this constraint will be modified to include them. +type Ordered interface { + Integer | Float | ~string +} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go new file mode 100644 index 00000000..8a7cf20d --- /dev/null +++ b/vendor/golang.org/x/exp/slices/slices.go @@ -0,0 +1,282 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package slices defines various functions useful with slices of any type. +// Unless otherwise specified, these functions all apply to the elements +// of a slice at index 0 <= i < len(s). +// +// Note that the less function in IsSortedFunc, SortFunc, SortStableFunc requires a +// strict weak ordering (https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings), +// or the sorting may fail to sort correctly. A common case is when sorting slices of +// floating-point numbers containing NaN values. +package slices + +import "golang.org/x/exp/constraints" + +// Equal reports whether two slices are equal: the same length and all +// elements equal. If the lengths are different, Equal returns false. +// Otherwise, the elements are compared in increasing index order, and the +// comparison stops at the first unequal pair. +// Floating point NaNs are not considered equal. +func Equal[E comparable](s1, s2 []E) bool { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + if s1[i] != s2[i] { + return false + } + } + return true +} + +// EqualFunc reports whether two slices are equal using a comparison +// function on each pair of elements. If the lengths are different, +// EqualFunc returns false. Otherwise, the elements are compared in +// increasing index order, and the comparison stops at the first index +// for which eq returns false. +func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { + if len(s1) != len(s2) { + return false + } + for i, v1 := range s1 { + v2 := s2[i] + if !eq(v1, v2) { + return false + } + } + return true +} + +// Compare compares the elements of s1 and s2. +// The elements are compared sequentially, starting at index 0, +// until one element is not equal to the other. +// The result of comparing the first non-matching elements is returned. +// If both slices are equal until one of them ends, the shorter slice is +// considered less than the longer one. +// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. +// Comparisons involving floating point NaNs are ignored. +func Compare[E constraints.Ordered](s1, s2 []E) int { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + switch { + case v1 < v2: + return -1 + case v1 > v2: + return +1 + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// CompareFunc is like Compare but uses a comparison function +// on each pair of elements. The elements are compared in increasing +// index order, and the comparisons stop after the first time cmp +// returns non-zero. +// The result is the first non-zero result of cmp; if cmp always +// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), +// and +1 if len(s1) > len(s2). +func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + if c := cmp(v1, v2); c != 0 { + return c + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// Index returns the index of the first occurrence of v in s, +// or -1 if not present. +func Index[E comparable](s []E, v E) int { + for i := range s { + if v == s[i] { + return i + } + } + return -1 +} + +// IndexFunc returns the first index i satisfying f(s[i]), +// or -1 if none do. +func IndexFunc[E any](s []E, f func(E) bool) int { + for i := range s { + if f(s[i]) { + return i + } + } + return -1 +} + +// Contains reports whether v is present in s. +func Contains[E comparable](s []E, v E) bool { + return Index(s, v) >= 0 +} + +// ContainsFunc reports whether at least one +// element e of s satisfies f(e). +func ContainsFunc[E any](s []E, f func(E) bool) bool { + return IndexFunc(s, f) >= 0 +} + +// Insert inserts the values v... into s at index i, +// returning the modified slice. +// In the returned slice r, r[i] == v[0]. +// Insert panics if i is out of range. +// This function is O(len(s) + len(v)). +func Insert[S ~[]E, E any](s S, i int, v ...E) S { + tot := len(s) + len(v) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[i:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[i:]) + return s2 +} + +// Delete removes the elements s[i:j] from s, returning the modified slice. +// Delete panics if s[i:j] is not a valid slice of s. +// Delete modifies the contents of the slice s; it does not create a new slice. +// Delete is O(len(s)-j), so if many items must be deleted, it is better to +// make a single call deleting them all together than to delete one at a time. +// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those +// elements contain pointers you might consider zeroing those elements so that +// objects they reference can be garbage collected. +func Delete[S ~[]E, E any](s S, i, j int) S { + _ = s[i:j] // bounds check + + return append(s[:i], s[j:]...) +} + +// DeleteFunc removes any elements from s for which del returns true, +// returning the modified slice. +// When DeleteFunc removes m elements, it might not modify the elements +// s[len(s)-m:len(s)]. If those elements contain pointers you might consider +// zeroing those elements so that objects they reference can be garbage +// collected. +func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + // Don't start copying elements until we find one to delete. + for i, v := range s { + if del(v) { + j := i + for i++; i < len(s); i++ { + v = s[i] + if !del(v) { + s[j] = v + j++ + } + } + return s[:j] + } + } + return s +} + +// Replace replaces the elements s[i:j] by the given v, and returns the +// modified slice. Replace panics if s[i:j] is not a valid slice of s. +func Replace[S ~[]E, E any](s S, i, j int, v ...E) S { + _ = s[i:j] // verify that i:j is a valid subslice + tot := len(s[:i]) + len(v) + len(s[j:]) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[j:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[j:]) + return s2 +} + +// Clone returns a copy of the slice. +// The elements are copied using assignment, so this is a shallow clone. +func Clone[S ~[]E, E any](s S) S { + // Preserve nil in case it matters. + if s == nil { + return nil + } + return append(S([]E{}), s...) +} + +// Compact replaces consecutive runs of equal elements with a single copy. +// This is like the uniq command found on Unix. +// Compact modifies the contents of the slice s; it does not create a new slice. +// When Compact discards m elements in total, it might not modify the elements +// s[len(s)-m:len(s)]. If those elements contain pointers you might consider +// zeroing those elements so that objects they reference can be garbage collected. +func Compact[S ~[]E, E comparable](s S) S { + if len(s) < 2 { + return s + } + i := 1 + for k := 1; k < len(s); k++ { + if s[k] != s[k-1] { + if i != k { + s[i] = s[k] + } + i++ + } + } + return s[:i] +} + +// CompactFunc is like Compact but uses a comparison function. +func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { + if len(s) < 2 { + return s + } + i := 1 + for k := 1; k < len(s); k++ { + if !eq(s[k], s[k-1]) { + if i != k { + s[i] = s[k] + } + i++ + } + } + return s[:i] +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. If n is negative or too large to +// allocate the memory, Grow panics. +func Grow[S ~[]E, E any](s S, n int) S { + if n < 0 { + panic("cannot be negative") + } + if n -= cap(s) - len(s); n > 0 { + // TODO(https://go.dev/issue/53888): Make using []E instead of S + // to workaround a compiler bug where the runtime.growslice optimization + // does not take effect. Revert when the compiler is fixed. + s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)] + } + return s +} + +// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. +func Clip[S ~[]E, E any](s S) S { + return s[:len(s):len(s)] +} diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go new file mode 100644 index 00000000..231b6448 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/sort.go @@ -0,0 +1,128 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import ( + "math/bits" + + "golang.org/x/exp/constraints" +) + +// Sort sorts a slice of any ordered type in ascending order. +// Sort may fail to sort correctly when sorting slices of floating-point +// numbers containing Not-a-number (NaN) values. +// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))}) +// instead if the input may contain NaNs. +func Sort[E constraints.Ordered](x []E) { + n := len(x) + pdqsortOrdered(x, 0, n, bits.Len(uint(n))) +} + +// SortFunc sorts the slice x in ascending order as determined by the less function. +// This sort is not guaranteed to be stable. +// +// SortFunc requires that less is a strict weak ordering. +// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings. +func SortFunc[E any](x []E, less func(a, b E) bool) { + n := len(x) + pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less) +} + +// SortStableFunc sorts the slice x while keeping the original order of equal +// elements, using less to compare elements. +func SortStableFunc[E any](x []E, less func(a, b E) bool) { + stableLessFunc(x, len(x), less) +} + +// IsSorted reports whether x is sorted in ascending order. +func IsSorted[E constraints.Ordered](x []E) bool { + for i := len(x) - 1; i > 0; i-- { + if x[i] < x[i-1] { + return false + } + } + return true +} + +// IsSortedFunc reports whether x is sorted in ascending order, with less as the +// comparison function. +func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool { + for i := len(x) - 1; i > 0; i-- { + if less(x[i], x[i-1]) { + return false + } + } + return true +} + +// BinarySearch searches for target in a sorted slice and returns the position +// where target is found, or the position where target would appear in the +// sort order; it also returns a bool saying whether the target is really found +// in the slice. The slice must be sorted in increasing order. +func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) { + // Inlining is faster than calling BinarySearchFunc with a lambda. + n := len(x) + // Define x[-1] < target and x[n] >= target. + // Invariant: x[i-1] < target, x[j] >= target. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if x[h] < target { + i = h + 1 // preserves x[i-1] < target + } else { + j = h // preserves x[j] >= target + } + } + // i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i. + return i, i < n && x[i] == target +} + +// BinarySearchFunc works like BinarySearch, but uses a custom comparison +// function. The slice must be sorted in increasing order, where "increasing" +// is defined by cmp. cmp should return 0 if the slice element matches +// the target, a negative number if the slice element precedes the target, +// or a positive number if the slice element follows the target. +// cmp must implement the same ordering as the slice, such that if +// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice. +func BinarySearchFunc[E, T any](x []E, target T, cmp func(E, T) int) (int, bool) { + n := len(x) + // Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 . + // Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if cmp(x[h], target) < 0 { + i = h + 1 // preserves cmp(x[i - 1], target) < 0 + } else { + j = h // preserves cmp(x[j], target) >= 0 + } + } + // i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i. + return i, i < n && cmp(x[i], target) == 0 +} + +type sortedHint int // hint for pdqsort when choosing the pivot + +const ( + unknownHint sortedHint = iota + increasingHint + decreasingHint +) + +// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf +type xorshift uint64 + +func (r *xorshift) Next() uint64 { + *r ^= *r << 13 + *r ^= *r >> 17 + *r ^= *r << 5 + return uint64(*r) +} + +func nextPowerOfTwo(length int) uint { + return 1 << bits.Len(uint(length)) +} diff --git a/vendor/golang.org/x/exp/slices/zsortfunc.go b/vendor/golang.org/x/exp/slices/zsortfunc.go new file mode 100644 index 00000000..2a632476 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortfunc.go @@ -0,0 +1,479 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +// insertionSortLessFunc sorts data[a:b] using insertion sort. +func insertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + for i := a + 1; i < b; i++ { + for j := i; j > a && less(data[j], data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownLessFunc implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && less(data[first+child], data[first+child+1]) { + child++ + } + if !less(data[first+root], data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownLessFunc(data, i, hi, first, less) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownLessFunc(data, lo, i, first, less) + } +} + +// pdqsortLessFunc sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) { + const maxInsertion = 12 + + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSortLessFunc(data, a, b, less) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortLessFunc(data, a, b, less) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsLessFunc(data, a, b, less) + limit-- + } + + pivot, hint := choosePivotLessFunc(data, a, b, less) + if hint == decreasingHint { + reverseRangeLessFunc(data, a, b, less) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSortLessFunc(data, a, b, less) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !less(data[a-1], data[pivot]) { + mid := partitionEqualLessFunc(data, a, b, pivot, less) + a = mid + continue + } + + mid, alreadyPartitioned := partitionLessFunc(data, a, b, pivot, less) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortLessFunc(data, a, mid, limit, less) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortLessFunc(data, mid+1, b, limit, less) + b = mid + } + } +} + +// partitionLessFunc does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int, alreadyPartitioned bool) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && less(data[i], data[a]) { + i++ + } + for i <= j && !less(data[j], data[a]) { + j-- + } + if i > j { + data[j], data[a] = data[a], data[j] + return j, true + } + data[i], data[j] = data[j], data[i] + i++ + j-- + + for { + for i <= j && less(data[i], data[a]) { + i++ + } + for i <= j && !less(data[j], data[a]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + data[j], data[a] = data[a], data[j] + return j, false +} + +// partitionEqualLessFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !less(data[a], data[i]) { + i++ + } + for i <= j && less(data[a], data[j]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortLessFunc partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !less(data[i], data[i-1]) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data[i], data[i-1] = data[i-1], data[i] + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !less(data[j], data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !less(data[j], data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsLessFunc scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data[idx], data[a+other] = data[a+other], data[idx] + } + } +} + +// choosePivotLessFunc chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacentLessFunc(data, i, &swaps, less) + j = medianAdjacentLessFunc(data, j, &swaps, less) + k = medianAdjacentLessFunc(data, k, &swaps, less) + } + // Find the median among i, j, k and stores it into j. + j = medianLessFunc(data, i, j, k, &swaps, less) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2LessFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2LessFunc[E any](data []E, a, b int, swaps *int, less func(a, b E) bool) (int, int) { + if less(data[b], data[a]) { + *swaps++ + return b, a + } + return a, b +} + +// medianLessFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianLessFunc[E any](data []E, a, b, c int, swaps *int, less func(a, b E) bool) int { + a, b = order2LessFunc(data, a, b, swaps, less) + b, c = order2LessFunc(data, b, c, swaps, less) + a, b = order2LessFunc(data, a, b, swaps, less) + return b +} + +// medianAdjacentLessFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentLessFunc[E any](data []E, a int, swaps *int, less func(a, b E) bool) int { + return medianLessFunc(data, a-1, a, a+1, swaps, less) +} + +func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeLessFunc[E any](data []E, a, b, n int, less func(a, b E) bool) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortLessFunc(data, a, b, less) + a = b + b += blockSize + } + insertionSortLessFunc(data, a, n, less) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeLessFunc(data, a, a+blockSize, b, less) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeLessFunc(data, a, m, n, less) + } + blockSize *= 2 + } +} + +// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if less(data[h], data[a]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !less(data[m], data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !less(data[p-c], data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateLessFunc(data, start, m, end, less) + } + if a < start && start < mid { + symMergeLessFunc(data, a, start, mid, less) + } + if mid < end && end < b { + symMergeLessFunc(data, mid, end, b, less) + } +} + +// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeLessFunc(data, m-i, m, j, less) + i -= j + } else { + swapRangeLessFunc(data, m-i, m+j-i, i, less) + j -= i + } + } + // i == j + swapRangeLessFunc(data, m-i, m, i, less) +} diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go new file mode 100644 index 00000000..efaa1c8b --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortordered.go @@ -0,0 +1,481 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import "golang.org/x/exp/constraints" + +// insertionSortOrdered sorts data[a:b] using insertion sort. +func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) { + for i := a + 1; i < b; i++ { + for j := i; j > a && (data[j] < data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownOrdered implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && (data[first+child] < data[first+child+1]) { + child++ + } + if !(data[first+root] < data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortOrdered[E constraints.Ordered](data []E, a, b int) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownOrdered(data, i, hi, first) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownOrdered(data, lo, i, first) + } +} + +// pdqsortOrdered sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) { + const maxInsertion = 12 + + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSortOrdered(data, a, b) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSortOrdered(data, a, b) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatternsOrdered(data, a, b) + limit-- + } + + pivot, hint := choosePivotOrdered(data, a, b) + if hint == decreasingHint { + reverseRangeOrdered(data, a, b) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSortOrdered(data, a, b) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !(data[a-1] < data[pivot]) { + mid := partitionEqualOrdered(data, a, b, pivot) + a = mid + continue + } + + mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsortOrdered(data, a, mid, limit) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsortOrdered(data, mid+1, b, limit) + b = mid + } + } +} + +// partitionOrdered does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && (data[i] < data[a]) { + i++ + } + for i <= j && !(data[j] < data[a]) { + j-- + } + if i > j { + data[j], data[a] = data[a], data[j] + return j, true + } + data[i], data[j] = data[j], data[i] + i++ + j-- + + for { + for i <= j && (data[i] < data[a]) { + i++ + } + for i <= j && !(data[j] < data[a]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + data[j], data[a] = data[a], data[j] + return j, false +} + +// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) { + data[a], data[pivot] = data[pivot], data[a] + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !(data[a] < data[i]) { + i++ + } + for i <= j && (data[a] < data[j]) { + j-- + } + if i > j { + break + } + data[i], data[j] = data[j], data[i] + i++ + j-- + } + return i +} + +// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !(data[i] < data[i-1]) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data[i], data[i-1] = data[i-1], data[i] + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !(data[j] < data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !(data[j] < data[j-1]) { + break + } + data[j], data[j-1] = data[j-1], data[j] + } + } + } + return false +} + +// breakPatternsOrdered scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data[idx], data[a+other] = data[a+other], data[idx] + } + } +} + +// choosePivotOrdered chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacentOrdered(data, i, &swaps) + j = medianAdjacentOrdered(data, j, &swaps) + k = medianAdjacentOrdered(data, k, &swaps) + } + // Find the median among i, j, k and stores it into j. + j = medianOrdered(data, i, j, k, &swaps) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) { + if data[b] < data[a] { + *swaps++ + return b, a + } + return a, b +} + +// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int { + a, b = order2Ordered(data, a, b, swaps) + b, c = order2Ordered(data, b, c, swaps) + a, b = order2Ordered(data, a, b, swaps) + return b +} + +// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int { + return medianOrdered(data, a-1, a, a+1, swaps) +} + +func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) { + i := a + j := b - 1 + for i < j { + data[i], data[j] = data[j], data[i] + i++ + j-- + } +} + +func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func stableOrdered[E constraints.Ordered](data []E, n int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortOrdered(data, a, b) + a = b + b += blockSize + } + insertionSortOrdered(data, a, n) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeOrdered(data, a, a+blockSize, b) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeOrdered(data, a, m, n) + } + blockSize *= 2 + } +} + +// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if data[h] < data[a] { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !(data[m] < data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !(data[p-c] < data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateOrdered(data, start, m, end) + } + if a < start && start < mid { + symMergeOrdered(data, a, start, mid) + } + if mid < end && end < b { + symMergeOrdered(data, mid, end, b) + } +} + +// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeOrdered(data, m-i, m, j) + i -= j + } else { + swapRangeOrdered(data, m-i, m+j-i, i) + j -= i + } + } + // i == j + swapRangeOrdered(data, m-i, m, i) +} diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go index cbee7a4e..b18efb74 100644 --- a/vendor/golang.org/x/sync/errgroup/errgroup.go +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -20,7 +20,7 @@ type token struct{} // A zero Group is valid, has no limit on the number of active goroutines, // and does not cancel on error. type Group struct { - cancel func() + cancel func(error) wg sync.WaitGroup @@ -43,7 +43,7 @@ func (g *Group) done() { // returns a non-nil error or the first time Wait returns, whichever occurs // first. func WithContext(ctx context.Context) (*Group, context.Context) { - ctx, cancel := context.WithCancel(ctx) + ctx, cancel := withCancelCause(ctx) return &Group{cancel: cancel}, ctx } @@ -52,7 +52,7 @@ func WithContext(ctx context.Context) (*Group, context.Context) { func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { - g.cancel() + g.cancel(g.err) } return g.err } @@ -76,7 +76,7 @@ func (g *Group) Go(f func() error) { g.errOnce.Do(func() { g.err = err if g.cancel != nil { - g.cancel() + g.cancel(g.err) } }) } @@ -105,7 +105,7 @@ func (g *Group) TryGo(f func() error) bool { g.errOnce.Do(func() { g.err = err if g.cancel != nil { - g.cancel() + g.cancel(g.err) } }) } diff --git a/vendor/golang.org/x/sync/errgroup/go120.go b/vendor/golang.org/x/sync/errgroup/go120.go new file mode 100644 index 00000000..7d419d37 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/go120.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.20 +// +build go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + return context.WithCancelCause(parent) +} diff --git a/vendor/golang.org/x/sync/errgroup/pre_go120.go b/vendor/golang.org/x/sync/errgroup/pre_go120.go new file mode 100644 index 00000000..1795c18a --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/pre_go120.go @@ -0,0 +1,15 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.20 +// +build !go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + ctx, cancel := context.WithCancel(parent) + return ctx, func(error) { cancel() } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8481f8fa..2f9a135f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -131,7 +131,7 @@ github.com/cenkalti/backoff/v4 github.com/cespare/xxhash/v2 # github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e ## explicit -# github.com/compose-spec/compose-go v1.14.0 +# github.com/compose-spec/compose-go v1.17.0 ## explicit; go 1.19 github.com/compose-spec/compose-go/cli github.com/compose-spec/compose-go/consts @@ -446,7 +446,7 @@ github.com/hashicorp/hcl/v2/hclparse github.com/hashicorp/hcl/v2/hclsyntax github.com/hashicorp/hcl/v2/hclwrite github.com/hashicorp/hcl/v2/json -# github.com/imdario/mergo v0.3.15 +# github.com/imdario/mergo v0.3.16 ## explicit; go 1.13 github.com/imdario/mergo # github.com/in-toto/in-toto-golang v0.5.0 @@ -799,7 +799,11 @@ golang.org/x/crypto/ssh golang.org/x/crypto/ssh/agent golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/terminal -# golang.org/x/mod v0.9.0 +# golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 +## explicit; go 1.20 +golang.org/x/exp/constraints +golang.org/x/exp/slices +# golang.org/x/mod v0.11.0 ## explicit; go 1.17 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/module @@ -819,8 +823,8 @@ golang.org/x/net/trace ## explicit; go 1.17 golang.org/x/oauth2 golang.org/x/oauth2/internal -# golang.org/x/sync v0.2.0 -## explicit +# golang.org/x/sync v0.3.0 +## explicit; go 1.17 golang.org/x/sync/errgroup golang.org/x/sync/semaphore # golang.org/x/sys v0.8.0