diff --git a/pkg/julia/manifest/naive_pkg_parser.go b/pkg/julia/manifest/naive_pkg_parser.go new file mode 100644 index 00000000..b1c24087 --- /dev/null +++ b/pkg/julia/manifest/naive_pkg_parser.go @@ -0,0 +1,68 @@ +package julia + +import ( + "bufio" + "io" + "strings" +) + +type pkgPosition struct { + start int + end int +} +type minPkg struct { + uuid string + version string + position pkgPosition +} + +func (pkg *minPkg) setEndPositionIfEmpty(n int) { + if pkg.position.end == 0 { + pkg.position.end = n + } +} + +type naivePkgParser struct { + r io.Reader +} + +func (parser *naivePkgParser) parse() map[string]pkgPosition { + var currentPkg minPkg = minPkg{} + var idx = make(map[string]pkgPosition, 0) + + scanner := bufio.NewScanner(parser.r) + lineNum := 1 + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(strings.TrimSpace(line), "[") { + if currentPkg.uuid != "" { + currentPkg.setEndPositionIfEmpty(lineNum - 1) + idx[currentPkg.uuid] = currentPkg.position + } + currentPkg = minPkg{} + currentPkg.position.start = lineNum + + } else if strings.HasPrefix(strings.TrimSpace(line), "uuid =") { + currentPkg.uuid = propertyValue(line) + } else if strings.HasPrefix(strings.TrimSpace(line), "version =") { + currentPkg.version = propertyValue(line) + } else if strings.TrimSpace(line) == "" { + currentPkg.setEndPositionIfEmpty(lineNum - 1) + } + + lineNum++ + } + // add last item + if currentPkg.uuid != "" { + currentPkg.setEndPositionIfEmpty(lineNum - 1) + idx[currentPkg.uuid] = currentPkg.position + } + return idx +} +func propertyValue(line string) string { + parts := strings.Split(line, "=") + if len(parts) == 2 { + return strings.Trim(parts[1], ` "`) + } + return "" +} diff --git a/pkg/julia/manifest/parse.go b/pkg/julia/manifest/parse.go new file mode 100644 index 00000000..06cbaafc --- /dev/null +++ b/pkg/julia/manifest/parse.go @@ -0,0 +1,161 @@ +package julia + +import ( + "io" + "sort" + + "github.com/BurntSushi/toml" + dio "github.com/aquasecurity/go-dep-parser/pkg/io" + "github.com/aquasecurity/go-dep-parser/pkg/types" + + "golang.org/x/exp/maps" + "golang.org/x/xerrors" +) + +type primitiveManifest struct { + JuliaVersion string `toml:"julia_version"` + ManifestFormat string `toml:"manifest_format"` + Dependencies map[string][]primitiveDependency `toml:"deps"` // e.g. [[deps.Foo]] +} + +type primitiveDependency struct { + Dependencies toml.Primitive `toml:"deps"` // by name. e.g. deps = ["Foo"] or [deps.Foo.deps] + UUID string `toml:"uuid"` + Version string `toml:"version"` // not specified for stdlib packages, which are of the Julia version + DependsOn []string `toml:"-"` // list of dependent UUID's. +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r dio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var oldDeps map[string][]primitiveDependency + var primMan primitiveManifest + var manMetadata toml.MetaData + decoder := toml.NewDecoder(r) + // Try to read the old Manifest format. If that fails, try the new format. + if _, err := decoder.Decode(&oldDeps); err != nil { + if _, err = r.Seek(0, io.SeekStart); err != nil { + return nil, nil, xerrors.Errorf("seek error: %w", err) + } + if manMetadata, err = decoder.Decode(&primMan); err != nil { + return nil, nil, xerrors.Errorf("decode error: %w", err) + } + } + + // We can't know the Julia version on an old manifest. + // All newer manifests include a manifest version and a julia version. + if primMan.ManifestFormat == "" { + primMan = primitiveManifest{ + JuliaVersion: "unknown", + Dependencies: oldDeps, + } + } + + man, err := decodeManifest(&primMan, &manMetadata) + if err != nil { + return nil, nil, xerrors.Errorf("unable to decode manifest dependencies: %w", err) + } + + if _, err := r.Seek(0, io.SeekStart); err != nil { + return nil, nil, xerrors.Errorf("seek error: %w", err) + } + + // naive parser to get line numbers + pkgParser := naivePkgParser{r: r} + lineNumIdx := pkgParser.parse() + + var libs []types.Library + var deps []types.Dependency + for name, manifestDeps := range man.Dependencies { + for _, manifestDep := range manifestDeps { + version := depVersion(&manifestDep, man.JuliaVersion) + pkgID := manifestDep.UUID + lib := types.Library{ + ID: pkgID, + Name: name, + Version: version, + } + if pos, ok := lineNumIdx[manifestDep.UUID]; ok { + lib.Locations = []types.Location{{StartLine: pos.start, EndLine: pos.end}} + } + + libs = append(libs, lib) + + if len(manifestDep.DependsOn) > 0 { + deps = append(deps, types.Dependency{ + ID: pkgID, + DependsOn: manifestDep.DependsOn, + }) + } + } + } + sort.Sort(types.Libraries(libs)) + sort.Sort(types.Dependencies(deps)) + return libs, deps, nil +} + +// Returns the effective version of the `dep`. +// stdlib packages do not have a version in the manifest because they are packaged with julia itself +func depVersion(dep *primitiveDependency, juliaVersion string) string { + if len(dep.Version) == 0 { + return juliaVersion + } + return dep.Version +} + +// Decodes a primitive manifest using the metadata from parse time. +func decodeManifest(man *primitiveManifest, metadata *toml.MetaData) (*primitiveManifest, error) { + // Decode each dependency into the new manifest + for depName, primDeps := range man.Dependencies { + var newPrimDeps []primitiveDependency + for _, primDep := range primDeps { + newPrimDep, err := decodeDependency(man, primDep, metadata) + if err != nil { + return nil, err + } + newPrimDeps = append(newPrimDeps, newPrimDep) + } + man.Dependencies[depName] = newPrimDeps + } + + return man, nil +} + +// Decodes a primitive dependency using the metadata from parse time. +func decodeDependency(man *primitiveManifest, dep primitiveDependency, metadata *toml.MetaData) (primitiveDependency, error) { + // Try to decode as []string first where the manifest looks like deps = ["A", "B"] + var possibleDeps []string + err := metadata.PrimitiveDecode(dep.Dependencies, &possibleDeps) + if err == nil { + var possibleUuids []string + for _, depName := range possibleDeps { + primDep := man.Dependencies[depName] + if len(primDep) > 1 { + return primitiveDependency{}, xerrors.Errorf("Dependency %q has invalid format (parsed multiple deps): %s", depName, primDep) + } + possibleUuids = append(possibleUuids, primDep[0].UUID) + } + sort.Strings(possibleUuids) + dep.DependsOn = possibleUuids + return dep, nil + } + + // The other possibility is a map where the manifest looks like + // [deps.A.deps] + // B = "..." + var possibleDepsMap map[string]string + err = metadata.PrimitiveDecode(dep.Dependencies, &possibleDepsMap) + if err == nil { + possibleUuids := maps.Values(possibleDepsMap) + sort.Strings(possibleUuids) + dep.DependsOn = possibleUuids + return dep, nil + } + + // We don't know what the shape of the data is -- i.e. an invalid manifest + return primitiveDependency{}, err +} diff --git a/pkg/julia/manifest/parse_test.go b/pkg/julia/manifest/parse_test.go new file mode 100644 index 00000000..60785d91 --- /dev/null +++ b/pkg/julia/manifest/parse_test.go @@ -0,0 +1,75 @@ +package julia + +import ( + "os" + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/go-dep-parser/pkg/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + file string // Test input file + want []types.Library + wantDeps []types.Dependency + }{ + { + name: "Manifest v1.6", + file: "testdata/primary/Manifest_v1.6.toml", + want: juliaV1_6Libs, + wantDeps: juliaV1_6Deps, + }, + { + name: "Manifest v1.8", + file: "testdata/primary/Manifest_v1.8.toml", + want: juliaV1_8Libs, + wantDeps: juliaV1_8Deps, + }, + { + name: "no deps v1.6", + file: "testdata/no_deps_v1.6/Manifest.toml", + want: nil, + wantDeps: nil, + }, + { + name: "no deps v1.9", + file: "testdata/no_deps_v1.9/Manifest.toml", + want: nil, + wantDeps: nil, + }, + { + name: "dep extensions v1.9", + file: "testdata/dep_ext_v1.9/Manifest.toml", + want: juliaV1_9DepExtLibs, + wantDeps: nil, + }, + { + name: "shadowed dep v1.9", + file: "testdata/shadowed_dep_v1.9/Manifest.toml", + want: juliaV1_9ShadowedDepLibs, + wantDeps: juliaV1_9ShadowedDepDeps, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.file) + require.NoError(t, err) + + gotLibs, gotDeps, err := NewParser().Parse(f) + require.NoError(t, err) + + sort.Sort(types.Libraries(tt.want)) + assert.Equal(t, tt.want, gotLibs) + if tt.wantDeps != nil { + sort.Sort(types.Dependencies(tt.wantDeps)) + assert.Equal(t, tt.wantDeps, gotDeps) + } + }) + } +} diff --git a/pkg/julia/manifest/parse_testcase.go b/pkg/julia/manifest/parse_testcase.go new file mode 100644 index 00000000..8740abfe --- /dev/null +++ b/pkg/julia/manifest/parse_testcase.go @@ -0,0 +1,77 @@ +package julia + +import "github.com/aquasecurity/go-dep-parser/pkg/types" + +var ( + juliaV1_6Libs = []types.Library{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "unknown", Locations: []types.Location{{StartLine: 3, EndLine: 5}}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 7, EndLine: 11}}}, + {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "unknown", Locations: []types.Location{{StartLine: 13, EndLine: 14}}}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.4.2", Locations: []types.Location{{StartLine: 16, EndLine: 20}}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "unknown", Locations: []types.Location{{StartLine: 22, EndLine: 24}}}, + {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "unknown", Locations: []types.Location{{StartLine: 26, EndLine: 27}}}, + } + + juliaV1_6Deps = []types.Dependency{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}}, + } + + juliaV1_8Libs = []types.Library{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", Name: "Dates", Version: "1.8.5", Locations: []types.Location{{StartLine: 7, EndLine: 9}}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", Name: "JSON", Version: "0.21.4", Locations: []types.Location{{StartLine: 11, EndLine: 15}}}, + {ID: "a63ad114-7e13-5084-954f-fe012c677804", Name: "Mmap", Version: "1.8.5", Locations: []types.Location{{StartLine: 17, EndLine: 18}}}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", Name: "Parsers", Version: "2.5.10", Locations: []types.Location{{StartLine: 20, EndLine: 24}}}, + {ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", Name: "PrecompileTools", Version: "1.1.1", Locations: []types.Location{{StartLine: 26, EndLine: 30}}}, + {ID: "21216c6a-2e73-6563-6e65-726566657250", Name: "Preferences", Version: "1.4.0", Locations: []types.Location{{StartLine: 32, EndLine: 36}}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", Name: "Printf", Version: "1.8.5", Locations: []types.Location{{StartLine: 38, EndLine: 40}}}, + {ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", Name: "Random", Version: "1.8.5", Locations: []types.Location{{StartLine: 42, EndLine: 44}}}, + {ID: "ea8e919c-243c-51af-8825-aaa63cd721ce", Name: "SHA", Version: "0.7.0", Locations: []types.Location{{StartLine: 46, EndLine: 48}}}, + {ID: "9e88b42a-f829-5b0c-bbe9-9e923198166b", Name: "Serialization", Version: "1.8.5", Locations: []types.Location{{StartLine: 50, EndLine: 51}}}, + {ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", Name: "TOML", Version: "1.0.0", Locations: []types.Location{{StartLine: 53, EndLine: 56}}}, + {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", Name: "UUIDs", Version: "1.8.5", Locations: []types.Location{{StartLine: 58, EndLine: 60}}}, + {ID: "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", Name: "Unicode", Version: "1.8.5", Locations: []types.Location{{StartLine: 62, EndLine: 63}}}, + } + + juliaV1_8Deps = []types.Dependency{ + {ID: "ade2ca70-3891-5945-98fb-dc099432e06a", DependsOn: []string{"de0858da-6303-5e67-8744-51eddeeeb8d7"}}, + {ID: "682c06a0-de6a-54ab-a142-c8b1cf79cde6", DependsOn: []string{ + "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5", + "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", + "a63ad114-7e13-5084-954f-fe012c677804", + "ade2ca70-3891-5945-98fb-dc099432e06a", + }}, + {ID: "69de0a69-1ddd-5017-9359-2bf0b02dc9f0", DependsOn: []string{ + "ade2ca70-3891-5945-98fb-dc099432e06a", + "aea7be01-6a6a-4083-8856-8a6e6704d82a", + "cf7118a7-6976-5b1a-9a39-7adc72f591a4", + }}, + {ID: "aea7be01-6a6a-4083-8856-8a6e6704d82a", DependsOn: []string{"21216c6a-2e73-6563-6e65-726566657250"}}, + {ID: "21216c6a-2e73-6563-6e65-726566657250", DependsOn: []string{"fa267f1f-6049-4f14-aa54-33bafae1ed76"}}, + {ID: "de0858da-6303-5e67-8744-51eddeeeb8d7", DependsOn: []string{"4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"}}, + {ID: "9a3f8284-a2c9-5f02-9a11-845980a1fd5c", DependsOn: []string{"9e88b42a-f829-5b0c-bbe9-9e923198166b", "ea8e919c-243c-51af-8825-aaa63cd721ce"}}, + {ID: "fa267f1f-6049-4f14-aa54-33bafae1ed76", DependsOn: []string{"ade2ca70-3891-5945-98fb-dc099432e06a"}}, + {ID: "cf7118a7-6976-5b1a-9a39-7adc72f591a4", DependsOn: []string{"9a3f8284-a2c9-5f02-9a11-845980a1fd5c", "ea8e919c-243c-51af-8825-aaa63cd721ce"}}, + } + + juliaV1_9DepExtLibs = []types.Library{ + {ID: "621f4979-c628-5d54-868e-fcf4e3e8185c", Name: "AbstractFFTs", Version: "1.3.1", Locations: []types.Location{{StartLine: 7, EndLine: 10}}}, + } + + juliaV1_9ShadowedDepLibs = []types.Library{ + {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", Name: "A", Version: "1.9.0", Locations: []types.Location{{StartLine: 7, EndLine: 8}}}, + {ID: "f41f7b98-334e-11e9-1257-49272045fb24", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 13, EndLine: 14}}}, + {ID: "edca9bc6-334e-11e9-3554-9595dbb4349c", Name: "B", Version: "1.9.0", Locations: []types.Location{{StartLine: 15, EndLine: 16}}}, + } + + juliaV1_9ShadowedDepDeps = []types.Dependency{ + {ID: "ead4f63c-334e-11e9-00e6-e7f0a5f21b60", DependsOn: []string{"f41f7b98-334e-11e9-1257-49272045fb24"}}, + } +) diff --git a/pkg/julia/manifest/testdata/dep_ext_v1.9/Manifest.toml b/pkg/julia/manifest/testdata/dep_ext_v1.9/Manifest.toml new file mode 100644 index 00000000..c3b22a72 --- /dev/null +++ b/pkg/julia/manifest/testdata/dep_ext_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.AbstractFFTs]] +git-tree-sha1 = "16b6dbc4cf7caee4e1e75c49485ec67b667098a0" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "1.3.1" + + [deps.AbstractFFTs.extensions] + AbstractFFTsChainRulesCoreExt = "ChainRulesCore" + + [deps.AbstractFFTs.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" diff --git a/pkg/julia/manifest/testdata/no_deps_v1.6/Manifest.toml b/pkg/julia/manifest/testdata/no_deps_v1.6/Manifest.toml new file mode 100644 index 00000000..f45eecff --- /dev/null +++ b/pkg/julia/manifest/testdata/no_deps_v1.6/Manifest.toml @@ -0,0 +1,2 @@ +# This file is machine-generated - editing it directly is not advised + diff --git a/pkg/julia/manifest/testdata/no_deps_v1.9/Manifest.toml b/pkg/julia/manifest/testdata/no_deps_v1.9/Manifest.toml new file mode 100644 index 00000000..9ed5ab94 --- /dev/null +++ b/pkg/julia/manifest/testdata/no_deps_v1.9/Manifest.toml @@ -0,0 +1,7 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709" + +[deps] diff --git a/pkg/julia/manifest/testdata/primary/Manifest_v1.6.toml b/pkg/julia/manifest/testdata/primary/Manifest_v1.6.toml new file mode 100644 index 00000000..6883d004 --- /dev/null +++ b/pkg/julia/manifest/testdata/primary/Manifest_v1.6.toml @@ -0,0 +1,27 @@ +# This file is machine-generated - editing it directly is not advised + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "6c01a9b494f6d2a9fc180a08b182fcb06f0958a0" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.4.2" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/pkg/julia/manifest/testdata/primary/Manifest_v1.8.toml b/pkg/julia/manifest/testdata/primary/Manifest_v1.8.toml new file mode 100644 index 00000000..6b7c988c --- /dev/null +++ b/pkg/julia/manifest/testdata/primary/Manifest_v1.8.toml @@ -0,0 +1,63 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.5" +manifest_format = "2.0" +project_hash = "f65b9de676a27ce78ee011db6d477b3d44d1a7c5" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "a5aef8d4a6e8d81f171b2bd4be5265b01384c74c" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.5.10" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "259e206946c293698122f63e2b513a7c99a244e8" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.1.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.0" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/pkg/julia/manifest/testdata/shadowed_dep_v1.9/Manifest.toml b/pkg/julia/manifest/testdata/shadowed_dep_v1.9/Manifest.toml new file mode 100644 index 00000000..dd4ea00b --- /dev/null +++ b/pkg/julia/manifest/testdata/shadowed_dep_v1.9/Manifest.toml @@ -0,0 +1,16 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.0" +manifest_format = "2.0" +project_hash = "f0a796fb78285c02ad123fec6e14c8bac09a2ccc" + +[[deps.A]] +uuid = "ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + + [deps.A.deps] + B = "f41f7b98-334e-11e9-1257-49272045fb24" + +[[deps.B]] +uuid = "f41f7b98-334e-11e9-1257-49272045fb24" +[[deps.B]] +uuid = "edca9bc6-334e-11e9-3554-9595dbb4349c"