diff --git a/Changelog.md b/Changelog.md index 0bcfd7fb5..84fc6258a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,7 +1,8 @@ # Spectrometer Changelog -# unreleased +## v2.15.0 +- Dart: Adds support for pub package manager. ([#313](https://github.com/fossas/spectrometer/pull/313)) - Modified DiscoveredProject's to include manifest file information (origin paths) ([#316](https://github.com/fossas/spectrometer/pull/316)) ## v2.14.5 diff --git a/docs/quickreference/pub.md b/docs/quickreference/pub.md new file mode 100644 index 000000000..3aa7e66ce --- /dev/null +++ b/docs/quickreference/pub.md @@ -0,0 +1,12 @@ +# Quick reference: pub + +## Requirements + +**Ideal/Minimum** +- `dart` installed locally +- `pubspec.yaml` file present in your project +- `pubspec.lock` file present in your project, with dependencies already retrieved and resolved + +## Project discovery + +Directories containing `pubspec.yaml` files are considered dart projects. \ No newline at end of file diff --git a/docs/strategies.md b/docs/strategies.md index 1efaecba0..bd541d7ca 100644 --- a/docs/strategies.md +++ b/docs/strategies.md @@ -70,6 +70,7 @@ TODO: create a lookup table that categorizes these strategies by language or too The CLI supports the following strategies: - [clojure](strategies/golang.md) (lein) +- [dart](strategies/dart.md) (pub) - [elixir](strategies/elixir.md) (mix) - [erlang](strategies/erlang.md) (rebar3) - [golang](strategies/golang.md) (gomodules, dep, glide) diff --git a/docs/strategies/dart-resolved-graph-with-lock-cmd.svg b/docs/strategies/dart-resolved-graph-with-lock-cmd.svg new file mode 100644 index 000000000..c5e3eb851 --- /dev/null +++ b/docs/strategies/dart-resolved-graph-with-lock-cmd.svg @@ -0,0 +1,327 @@ + + + + + + + + + + 00 + + https://github.com/leocavalcante/encrypt.git(=HEAD) + + + + 01 + + args(=2.2.0) + + + + 00->01 + + + + + + 02 + + asn1lib(=1.0.2) + + + + 00->02 + + + + + + 03 + + clock(=1.1.0) + + + + 00->03 + + + + + + 04 + + collection(=1.15.0) + + + + 00->04 + + + + + + 05 + + crypto(=3.0.1) + + + + 00->05 + + + + + + 07 + + pointycastle(=3.3.0) + + + + 00->07 + + + + + + 05->04 + + + + + + 06 + + typed_data(=1.3.0) + + + + 05->06 + + + + + + 06->04 + + + + + + 07->04 + + + + + + 08 + + convert(=3.0.1) + + + + 07->08 + + + + + + 09 + + js(=0.6.3) + + + + 07->09 + + + + + + 08->06 + + + + + + 010 + + characters(=1.1.0) + + + + 011 + + meta(=1.7.0) + + + + 012 + + path(=1.8.0) + + + + 013 + + provider(=5.0.0) + + + + 013->04 + + + + + + 013->06 + + + + + + 013->010 + + + + + + 013->011 + + + + + + 014 + + nested(=1.0.0) + + + + 013->014 + + + + + + 015 + + vector_math(=2.1.0) + + + + 013->015 + + + + + + 014->04 + + + + + + 014->06 + + + + + + 014->010 + + + + + + 014->011 + + + + + + 014->015 + + + + + + 016 + + quiver(=3.0.1) + + + + 017 + + matcher(=0.12.11) + + + + 016->017 + + + + + + 018 + + stack_trace(=1.10.0) + + + + 017->018 + + + + + + 018->012 + + + + + \ No newline at end of file diff --git a/docs/strategies/dart-resolved-graph-without-cmd.svg b/docs/strategies/dart-resolved-graph-without-cmd.svg new file mode 100644 index 000000000..32ed7c202 --- /dev/null +++ b/docs/strategies/dart-resolved-graph-without-cmd.svg @@ -0,0 +1,39 @@ + + + + + + + + + + 00 + + https://github.com/leocavalcante/encrypt.git(=HEAD) + + + + 01 + + path(=1.8.0) + + + + 02 + + provider(=5.0.0) + + + + 03 + + quiver(=3.0.1) + + + \ No newline at end of file diff --git a/docs/strategies/dart.md b/docs/strategies/dart.md new file mode 100644 index 000000000..60f704d31 --- /dev/null +++ b/docs/strategies/dart.md @@ -0,0 +1,278 @@ +# Dart + +Dart ecosystem uses pub package manager to manage shared [packages and libraries](https://dart.dev/guides/packages). Packages can be sourced from [registry](https://pub.dev/), [git repository](https://dart.dev/tools/pub/dependencies#git-packages), or from [local file system](https://dart.dev/tools/pub/dependencies#path-packages). + +## Project Discovery + +Find file named `pubspec.yaml`. + +## Analysis + +We attempt to perform all of the strategies below, we select the result of succeeded strategies which has the highest preference. + +| Preference | Strategy | Direct Deps | Deep Deps | Edges | +| ---------- | ------------------------------------------------------------------------------------------------------ | ------------------ | ------------------ | ------------------ | +| Highest | 1. `pubspec.yaml` and `pubspec.lock` are discovered, and `flutter pub deps -s compact` can be executed | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| | 2. `pubspec.yaml` and `pubspec.lock` are discovered, and `dart pub deps -s compact` can be executed | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| | 3. `pubspec.yaml` and `pubspec.lock` are discovered, and `pub deps -s compact` can be executed | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| | 4. `pubspec.yaml` and `pubspec.lock` are discovered | :heavy_check_mark: | :x: | :x: | +| Lowest | 5. Only `pubspec.yaml` is discovered | :heavy_check_mark: | :x: | :x: | + +Where, + +* :heavy_check_mark: - Supported in all projects +* :x: - Not Supported + +It is recommended that, `pub deps get` is executed prior to analyzing dart project. This ensures dependencies are retrieved, so `pub deps -s compact` command can produce edges between direct, and deep dependencies. + +### Limitations + +* [Path dependencies](https://dart.dev/tools/pub/dependencies#path-packages) are not reported, and will be ignored in analyses. All descendant dependencies of the path dependency will be promoted to the ancestor of the path dependency. +* [Sdk dependencies](https://dart.dev/tools/pub/dependencies#sdk) are not reported, and will be ignored in analyses. All descendant dependencies of the sdk dependency will be promoted to the ancestor of the sdk dependency. + +# Example + +Create new dart project by creating `pubspec.yaml` file. + +```yaml +name: some_example +description: some example description +version: 1.0.0+1 + +environment: + sdk: ">=2.0.0 <3.0.0" + +dependencies: + path: ">= 1.2.0 <3.0.0" + encrypt: + git: https://github.com/leocavalcante/encrypt.git + flutter: + sdk: flutter + provider: ^5.0.0 + quiver: any + +flutter: + uses-material-design: true +``` + +Execute `dart pub get` to retrieve packages from the spec file. When performed, it will create `pubspec.lock` file. + +```text +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + encrypt: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: bc2a3f44339574edb5c374b991b6386c495a1bbb + url: "https://github.com/leocavalcante/encrypt.git" + source: git + version: "5.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path: + dependency: "direct main" + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + quiver: + dependency: "direct main" + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" +sdks: + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.16.0" +``` + +Dependencies can be inspected using `dart pub deps -s compact`: + +```text +Dart SDK 2.14.0-301.0.dev +Flutter SDK 2.4.0-4.0.pre +some_example 1.0.0+1 + +dependencies: +- encrypt 5.0.1 [args asn1lib clock collection crypto pointycastle] +- flutter 0.0.0 [characters collection meta typed_data vector_math sky_engine] +- path 1.8.0 +- provider 5.0.0 [collection flutter nested] +- quiver 3.0.1 [matcher] + +transitive dependencies: +- args 2.2.0 +- asn1lib 1.0.2 +- characters 1.1.0 +- clock 1.1.0 +- collection 1.15.0 +- convert 3.0.1 [typed_data] +- crypto 3.0.1 [collection typed_data] +- js 0.6.3 +- matcher 0.12.11 [stack_trace] +- meta 1.7.0 +- nested 1.0.0 [flutter] +- pointycastle 3.3.0 [collection convert js] +- sky_engine 0.0.99 +- stack_trace 1.10.0 [path] +- typed_data 1.3.0 [collection] +- vector_math 2.1.0 +``` + +When pub deps command is successfully executed, and lockfile id discovered (strategy 1, 2, or 3) analyses would yield following dependency graph: + +![With lock file and deps command](dart-resolved-graph-with-lock-cmd.svg) + +Note: Dependencies in yellow boxes are direct dependencies, rest are deep dependencies. All descendent dependencies of sdk dependencies are promoted to their ancestor - e.g. characters, collection, meta, typed_data, and vector_math. + +If pub deps command is not successfully executed: + +![Without deps command](dart-resolved-graph-without-cmd.svg) + +## FAQ + +### How do I *only* analyze dart projects? + +You can explicitly specify analyses target in `.fossa.yml` file. + +Example below, will exclude all analyses targets except pub. + +```yaml +# .fossa.yml + +version: 3 +targets: + only: + - type: pub +``` + +## References + +* [Pub](https://dart.dev/tools/pub/cmd) +* [Dart](https://dart.dev/tools/pub/cmd) +* [Flutter](https://flutter.dev/) diff --git a/docs/userguide.md b/docs/userguide.md index 3e05bea46..52be1434e 100644 --- a/docs/userguide.md +++ b/docs/userguide.md @@ -9,6 +9,7 @@ - [Check for FOSSA scan results](#check-for-fossa-scan-results) - [Supported Languages](#supported-languages) - [clojure](#clojure) + - [dart](#dart) - [erlang](#erlang) - [golang](#golang) - [haskell](#haskell) @@ -85,6 +86,10 @@ fossa analyze --help - [leiningen](quickreference/leiningen.md) +### dart + +- [pub](quickreference/pub.md) + ### erlang - [rebar3](quickreference/rebar3.md) @@ -249,6 +254,7 @@ Supported dependency types: - `maven` - Maven dependencies that can be found at many different sources. Specified as `name: javax.xml.bind:jaxb-api` where the convention is `groupId:artifactId`. - `npm` - Javascript dependencies found at [npmjs.com](https://www.npmjs.com/). - `nuget` - .NET dependencies found at [NuGet.org](https://www.nuget.org/). +- `pub` - Dart dependencies found at [pub.dev](https://www.pub.dev/). - `pypi` - Python dependencies that are typically found at [Pypi.org](https://pypi.org/). - `cocoapods` - Swift and Objective-C dependencies found at [Cocoapods.org](https://cocoapods.org/). - `url` - The URL type allows you to specify only the download location of an archive (e.g.: `.zip`, .`tar.gz`, etc.) in the `name` field and the FOSSA backend will attempt to download and scan it. Example for a github source dependency `https://github.com/fossas/spectrometer/archive/refs/tags/v2.7.2.tar.gz`. The `version` field will be silently ignored for `url` type dependencies. diff --git a/spectrometer.cabal b/spectrometer.cabal index ac4f89ab5..ee166e524 100644 --- a/spectrometer.cabal +++ b/spectrometer.cabal @@ -227,6 +227,9 @@ library Strategy.Conda Strategy.Conda.CondaList Strategy.Conda.EnvironmentYml + Strategy.Dart.PubDeps + Strategy.Dart.PubSpec + Strategy.Dart.PubSpecLock Strategy.Elixir.MixTree Strategy.Erlang.ConfigParser Strategy.Erlang.Rebar3Tree @@ -264,6 +267,7 @@ library Strategy.NuGet.Paket Strategy.NuGet.ProjectAssetsJson Strategy.NuGet.ProjectJson + Strategy.Pub Strategy.Python.Pipenv Strategy.Python.Poetry Strategy.Python.Poetry.Common @@ -327,6 +331,9 @@ test-suite unit-tests Conda.CondaListSpec Conda.EnvironmentYmlSpec Control.Carrier.DiagnosticsSpec + Dart.PubDepsSpec + Dart.PubSpecSpec + Dart.PubSpecLockSpec Discovery.FiltersSpec Effect.ExecSpec Elixir.MixTreeSpec diff --git a/src/App/Fossa/Analyze.hs b/src/App/Fossa/Analyze.hs index 952e84ede..ac2534877 100644 --- a/src/App/Fossa/Analyze.hs +++ b/src/App/Fossa/Analyze.hs @@ -84,6 +84,7 @@ import Strategy.NuGet.PackagesConfig qualified as PackagesConfig import Strategy.NuGet.Paket qualified as Paket import Strategy.NuGet.ProjectAssetsJson qualified as ProjectAssetsJson import Strategy.NuGet.ProjectJson qualified as ProjectJson +import Strategy.Pub qualified as Pub import Strategy.Python.Pipenv qualified as Pipenv import Strategy.Python.Poetry qualified as Poetry import Strategy.Python.Setuptools qualified as Setuptools @@ -192,6 +193,7 @@ discoverFuncs = , Poetry.discover , ProjectAssetsJson.discover , ProjectJson.discover + , Pub.discover , RPM.discover , Rebar3.discover , RepoManifest.discover diff --git a/src/DepTypes.hs b/src/DepTypes.hs index e17c19a76..e28eab561 100644 --- a/src/DepTypes.hs +++ b/src/DepTypes.hs @@ -76,6 +76,8 @@ data DepType PipType | -- | Cocoapods registry PodType + | -- | Pub dependency for dart + PubType | -- | Go dependency GoType | -- | Rust Cargo Dependency diff --git a/src/Srclib/Converter.hs b/src/Srclib/Converter.hs index 46acb6cd1..a2e788471 100644 --- a/src/Srclib/Converter.hs +++ b/src/Srclib/Converter.hs @@ -118,3 +118,4 @@ depTypeToFetcher = \case SubprojectType -> "mvn" -- FIXME. I knew SubprojectType would come back to bite us. URLType -> "url" UserType -> "user" + PubType -> "pub" diff --git a/src/Strategy/Dart/PubDeps.hs b/src/Strategy/Dart/PubDeps.hs new file mode 100644 index 000000000..065cc820e --- /dev/null +++ b/src/Strategy/Dart/PubDeps.hs @@ -0,0 +1,203 @@ +module Strategy.Dart.PubDeps ( + analyzeDepsCmd, + + -- * for testing + dartPubDepCmd, + depsCmdOutputParser, + flutterPubDepCmd, + buildGraph, + PubDepPackage (..), +) where + +import Control.Effect.Diagnostics (Diagnostics, context, (<||>)) +import Data.List (find) +import Data.Map.Strict qualified as Map +import Data.Maybe (fromMaybe) +import Data.Set (Set, toList) +import Data.Set qualified as Set +import Data.String.Conversion (ToText (toText)) +import Data.Text (Text) +import Data.Void (Void) +import DepTypes (Dependency (..)) +import Effect.Exec (AllowErr (Never), Command (..), Exec, execParser) +import Effect.Logger (Logger (..)) +import Effect.ReadFS (Has, ReadFS, readContentsYaml) +import GHC.Generics (Generic) +import Graphing (Graphing, deeps, directs, edges, gmap, shrink) +import Path +import Strategy.Dart.PubSpecLock ( + PackageName (..), + PubDepSource (..), + PubLockContent (..), + PubLockPackageHostedSource (..), + PubLockPackageMetadata (..), + isSupported, + logIgnoredPackages, + toDependency, + ) +import Text.Megaparsec ( + MonadParsec (eof), + Parsec, + anySingle, + between, + many, + optional, + sepBy, + skipManyTill, + (<|>), + ) +import Text.Megaparsec qualified as Megaparsec +import Text.Megaparsec.Char (alphaNumChar, char, space1) +import Text.Megaparsec.Char.Lexer qualified as L +import Types (GraphBreadth (..)) + +type Parser = Parsec Void Text + +symbol :: Text -> Parser Text +symbol = L.symbol scn + +scn :: Parser () +scn = L.space space1 Megaparsec.empty Megaparsec.empty + +lexeme :: Parser a -> Parser a +lexeme = L.lexeme scn + +-- | Represents `dart pub deps -s compact`. +dartPubDepCmd :: Command +dartPubDepCmd = + Command + { cmdName = "dart" + , cmdArgs = ["pub", "deps", "-s", "compact"] + , cmdAllowErr = Never + } + +-- | Represents `flutter pub deps -s compact`. +-- This is when, dart project has flutter app or other Flutter-specific code. +flutterPubDepCmd :: Command +flutterPubDepCmd = + Command + { cmdName = "flutter" + , cmdArgs = ["pub", "deps", "-s", "compact"] + , cmdAllowErr = Never + } + +-- | Represents `pub deps -s compact`. +-- Standalone pub command is required for dart lang versions below 2.10 +-- https://github.com/dart-lang/sdk/blob/master/CHANGELOG.md#pub-1 +pubDepJsonCmd :: Command +pubDepJsonCmd = + Command + { cmdName = "pub" + , cmdArgs = ["deps", "-s", "compact"] + , cmdAllowErr = Never + } + +data PubDepPackage = PubDepPackage + { pubDepPackageName :: PackageName + , pubDepPackageVersion :: Maybe Text + , pubDepPackageDeps :: Maybe (Set PackageName) + , pubDepPackageIsDirect :: Bool + } + deriving (Generic, Show, Eq, Ord) + +-- | Parse package name. +-- Ref: https://dart.dev/tools/pub/pubspec#name +parsePackageName :: Parser PackageName +parsePackageName = PackageName . toText <$> many (alphaNumChar <|> char '_') + +-- | Parse package version. +-- Ref: https://dart.dev/tools/pub/pubspec#version +parsePackageVersion :: Parser Text +parsePackageVersion = toText <$> many (alphaNumChar <|> char '.' <|> char '-' <|> char '+') + +-- | Parses Pub Package Entry. +-- - pkg_name 1.0.0 [pkg_dep_one] +-- - pkg_name 1.0.0 +-- - pkg_name 1.0.0 [pkg_dep_one, pkg_dep_two] +parsePubDepPackage :: Bool -> Parser PubDepPackage +parsePubDepPackage isDirectDep = do + packageName <- lexeme $ symbol "-" *> lexeme parsePackageName + packageVersion <- lexeme parsePackageVersion + + -- package may not have any dependencies + packageSubDeps <- optional (between (symbol "[") (symbol "]") (Set.fromList <$> sepBy parsePackageName (symbol " "))) + pure $ PubDepPackage packageName (Just packageVersion) packageSubDeps isDirectDep + +-- | Parses command output. +-- Reference: https://github.com/dart-lang/pub/blob/291705ca0b9632cb945dd39493dd5b9db41b897a/lib/src/command/deps.dart#L176 +-- Note, it prints dependencies, dev dependencies, overrides, and transitive dependencies in order. +depsCmdOutputParser :: Parser [PubDepPackage] +depsCmdOutputParser = parseDeps <* eof + where + parseDeps = do + directDeps <- skipManyTill anySingle (symbol "dependencies:") *> many (parsePubDepPackage True) + devDeps <- optional $ symbol "dev dependencies:" *> many (parsePubDepPackage True) + + -- Since, this strategy requires lockfile - dependency override information is redundant, + -- as lock file already produces resolved dependency source. + _ <- optional $ symbol "dependency overrides:" *> many (parsePubDepPackage False) + transitiveDeps <- optional $ symbol "transitive dependencies:" *> many (parsePubDepPackage False) + + pure $ + directDeps + ++ fromMaybe [] devDeps + ++ fromMaybe [] transitiveDeps + +isPackageSupported :: PubLockContent -> PackageName -> Bool +isPackageSupported lockContent pkg = maybe False isSupported $ Map.lookup pkg (packages lockContent) + +buildGraph :: PubLockContent -> [PubDepPackage] -> Graphing Dependency +buildGraph lockContent pkgs = gmap pkgToDependency filteredGraphOfPackageNames + where + graphOfPackageNames :: Graphing PackageName + graphOfPackageNames = + directs (pubDepPackageName <$> filter pubDepPackageIsDirect pkgs) + -- packages without any edges + <> deeps (pubDepPackageName <$> filter (\x -> Set.empty == fromMaybe Set.empty (pubDepPackageDeps x)) pkgs) + <> edges (concatMap edgesOf pkgs) + + edgesOf :: PubDepPackage -> [(PackageName, PackageName)] + edgesOf pkg = (pubDepPackageName pkg,) <$> toList (fromMaybe Set.empty $ pubDepPackageDeps pkg) + + filteredGraphOfPackageNames :: Graphing PackageName + filteredGraphOfPackageNames = shrink (isReportable) graphOfPackageNames + + isReportable :: PackageName -> Bool + isReportable = isPackageSupported lockContent + + pkgToDependency :: PackageName -> Dependency + pkgToDependency pkg = toDependency pkg $ metadataOf pkg + + metadataOf :: PackageName -> PubLockPackageMetadata + metadataOf pkg = + Map.findWithDefault + ( PubLockPackageMetadata + { pubLockPackageIsDirect = any (\x -> pubDepPackageName x == pkg) pkgs + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource Nothing Nothing + , pubLockPackageVersion = pubDepPackageVersion =<< find (\x -> pubDepPackageName x == pkg) pkgs + , pubLockPackageEnvironment = [] + } + ) + pkg + (packages lockContent) + +-- | Analyze using pub deps command and lockfile. +-- The pub package manager has a command-line interface that works with either the flutter tool or the dart tool. +-- We attempt with flutter command, than dart command, and lastly with pub (for versions below 2.10). +-- Ref: https://dart.dev/tools/pub/cmd. +analyzeDepsCmd :: + (Has Exec sig m, Has ReadFS sig m, Has Diagnostics sig m, Has Logger sig m) => + Path Abs File -> + Path Abs Dir -> + m (Graphing Dependency, GraphBreadth) +analyzeDepsCmd lockFile dir = do + lockContents <- context "Reading pubspec.lock" $ readContentsYaml lockFile + depsCmdPackages <- + execParser depsCmdOutputParser dir flutterPubDepCmd + <||> execParser depsCmdOutputParser dir dartPubDepCmd + <||> execParser depsCmdOutputParser dir pubDepJsonCmd + + _ <- logIgnoredPackages lockContents + + context "building graphing from pub deps command and pubspec.lock" $ + pure (buildGraph lockContents depsCmdPackages, Complete) diff --git a/src/Strategy/Dart/PubSpec.hs b/src/Strategy/Dart/PubSpec.hs new file mode 100644 index 000000000..cb24d5683 --- /dev/null +++ b/src/Strategy/Dart/PubSpec.hs @@ -0,0 +1,194 @@ +module Strategy.Dart.PubSpec ( + analyzePubSpecFile, + + -- * for testing + buildGraph, + PubSpecContent (..), + PubSpecDepSource (..), + PubSpecDepHostedSource (..), + PubSpecDepGitSource (..), + PubSpecDepSdkSource (..), + PubSpecDepPathSource (..), +) where + +import Control.Applicative ((<|>)) +import Control.Effect.Diagnostics (Diagnostics, context) +import Data.Foldable (asum, for_) +import Data.Map (Map, toList) +import Data.Map.Strict qualified as Map +import Data.Maybe (fromMaybe, maybeToList) +import Data.Text (Text) +import Data.Yaml (FromJSON (parseJSON), (.:), (.:?)) +import Data.Yaml qualified as Yaml +import DepTypes ( + DepEnvironment (EnvDevelopment, EnvProduction), + DepType (GitType, PubType), + Dependency (..), + VerConstraint (CEq), + ) +import Effect.Logger (Logger (..), Pretty (pretty), logDebug) +import Effect.ReadFS (Has, ReadFS, readContentsYaml) +import Graphing (Graphing, directs, induceJust) +import Path +import Strategy.Dart.PubSpecLock (PackageName (..)) +import Types (GraphBreadth (..)) + +data PubSpecContent = PubSpecContent + { pubSpecDependencies :: Maybe (Map PackageName PubSpecDepSource) + , pubSpecDevDependencies :: Maybe (Map PackageName PubSpecDepSource) + , pubSpecDependenciesOverrides :: Maybe (Map PackageName PubSpecDepSource) + } + deriving (Show, Eq, Ord) + +newtype PubSpecDepSdkSource = PubSpecDepSdkSource {sdkName :: Text} deriving (Show, Eq, Ord) +newtype PubSpecDepPathSource = PubSpecDepPathSource {hostPath :: Text} deriving (Show, Eq, Ord) +data PubSpecDepHostedSource = PubSpecDepHostedSource + { version :: Maybe Text + , hostedName :: Maybe Text + , hostedUrl :: Maybe Text + } + deriving (Show, Eq, Ord) +data PubSpecDepGitSource = PubSpecDepGitSource + { gitRef :: Maybe Text + , gitUrl :: Text + } + deriving (Show, Eq, Ord) + +data PubSpecDepSource + = HostedSource PubSpecDepHostedSource + | GitSource PubSpecDepGitSource + | SdkSource PubSpecDepSdkSource + | PathSource PubSpecDepPathSource + deriving (Show, Eq, Ord) + +instance FromJSON PubSpecContent where + parseJSON = Yaml.withObject "pubspec.yaml content" $ \o -> do + dependencies <- o .:? "dependencies" + devDependencies <- o .:? "dev_dependencies" + depOverrides <- o .:? "dependency_overrides" + pure $ PubSpecContent dependencies devDependencies depOverrides + +instance FromJSON PubSpecDepSource where + parseJSON (Yaml.String s) = pure $ HostedSource $ PubSpecDepHostedSource (Just s) Nothing Nothing + parseJSON (Yaml.Object o) = + asum + [ parseHostedSource o + , parseGitSource o + , parseSdkSource o + , parsePathSource o + ] + where + parseHostedSource :: Yaml.Object -> Yaml.Parser PubSpecDepSource + parseHostedSource ho = + HostedSource + <$> ( PubSpecDepHostedSource + <$> ho .: "version" + <*> ho .: "hosted" |> "name" + <*> ho .: "hosted" |> "url" + ) + + parseGitSource :: Yaml.Object -> Yaml.Parser PubSpecDepSource + parseGitSource go = + GitSource + <$> ( PubSpecDepGitSource + <$> go .: "git" |> "ref" + <*> go .: "git" |> "url" + <|> PubSpecDepGitSource Nothing + <$> go .: "git" + ) + parseSdkSource :: Yaml.Object -> Yaml.Parser PubSpecDepSource + parseSdkSource so = SdkSource . PubSpecDepSdkSource <$> so .: "sdk" + + parsePathSource :: Yaml.Object -> Yaml.Parser PubSpecDepSource + parsePathSource po = PathSource . PubSpecDepPathSource <$> po .: "path" + + (|>) :: FromJSON a => Yaml.Parser Yaml.Object -> Text -> Yaml.Parser a + (|>) parser key = do + obj <- parser + obj .: key + parseJSON _ = fail "failed parsing pub package's source!" + +toDependency :: DepEnvironment -> PackageName -> PubSpecDepSource -> Maybe Dependency +toDependency environment name (HostedSource (PubSpecDepHostedSource version _ url)) = + Just + Dependency + { dependencyType = PubType + , dependencyName = unPackageName name + , dependencyVersion = CEq <$> version + , dependencyLocations = maybeToList url + , dependencyEnvironments = [environment] + , dependencyTags = Map.empty + } +toDependency environment _ (GitSource (PubSpecDepGitSource gitRef gitUrl)) = + Just + Dependency + { dependencyType = GitType + , dependencyName = gitUrl + , dependencyVersion = CEq <$> gitRef + , dependencyLocations = [] + , dependencyEnvironments = [environment] + , dependencyTags = Map.empty + } +toDependency _ _ (SdkSource _) = Nothing +toDependency _ _ (PathSource _) = Nothing + +-- | Updates all of 'A''s value, with 'B' value, when key of 'A' matches with key of 'B'. +update :: Map PackageName PubSpecDepSource -> Map PackageName PubSpecDepSource -> Map PackageName PubSpecDepSource +update a b = Map.union (Map.intersection b a) a + +buildGraph :: PubSpecContent -> Graphing.Graphing Dependency +buildGraph specContent = induceJust allDependencies + where + -- In pub manifest, dependency can be overriden. + -- Ref: https://dart.dev/tools/pub/dependencies#dependency-overrides + supersededDependencies :: Map PackageName PubSpecDepSource + supersededDependencies = fromMaybe Map.empty $ pubSpecDependenciesOverrides specContent + + dependencies :: Map PackageName PubSpecDepSource + dependencies = update (fromMaybe Map.empty $ pubSpecDependencies specContent) supersededDependencies + + devDependencies :: Map PackageName PubSpecDepSource + devDependencies = update (fromMaybe Map.empty $ pubSpecDevDependencies specContent) supersededDependencies + + allDependencies :: Graphing (Maybe Dependency) + allDependencies = + directs (map (uncurry $ toDependency EnvProduction) $ toList dependencies) + <> directs (map (uncurry $ toDependency EnvDevelopment) $ toList devDependencies) + +logIgnoredPackages :: Has Logger sig m => PubSpecContent -> m () +logIgnoredPackages specContent = for_ notSupportedPackagesMsgs (logDebug . pretty) + where + notSupportedPackagesMsgs :: [Text] + notSupportedPackagesMsgs = map (<> " : ignored in analyses. Dependency's source is not supported!") notSupportedPackages + + notSupportedPackages :: [Text] + notSupportedPackages = + notSupportedOf (pubSpecDevDependencies specContent) + ++ notSupportedOf (pubSpecDependencies specContent) + + notSupportedOf :: Maybe (Map PackageName PubSpecDepSource) -> [Text] + notSupportedOf dependencies = + unPackageName . fst + <$> (toList . notSupported) + (update (fromMaybe Map.empty dependencies) supersededDependencies) + + supersededDependencies :: Map PackageName PubSpecDepSource + supersededDependencies = fromMaybe Map.empty $ pubSpecDependenciesOverrides specContent + + notSupported :: Map k PubSpecDepSource -> Map k PubSpecDepSource + notSupported = Map.filter $ not . isSupported + + isSupported :: PubSpecDepSource -> Bool + isSupported (GitSource _) = True + isSupported (HostedSource _) = True + isSupported (SdkSource _) = False + isSupported (PathSource _) = False + +analyzePubSpecFile :: + (Has ReadFS sig m, Has Diagnostics sig m, Has Logger sig m) => + Path Abs File -> + m (Graphing Dependency, GraphBreadth) +analyzePubSpecFile specFile = do + specContent <- context "Reading pubspec.yaml" $ readContentsYaml specFile + _ <- logIgnoredPackages specContent + context "building graphing from pubspec.yaml" $ pure (buildGraph specContent, Partial) diff --git a/src/Strategy/Dart/PubSpecLock.hs b/src/Strategy/Dart/PubSpecLock.hs new file mode 100644 index 000000000..55e4a48d1 --- /dev/null +++ b/src/Strategy/Dart/PubSpecLock.hs @@ -0,0 +1,188 @@ +module Strategy.Dart.PubSpecLock ( + analyzePubLockFile, + PackageName (..), + PubLockContent (..), + PubLockPackageMetadata (..), + logIgnoredPackages, + + -- * for testing + buildGraph, + PubDepSource (..), + PubLockPackageHostedSource (..), + PubLockPackageGitSource (..), + PubLockPackageSdkSource (..), + PubLockPackagePathSource (..), + toDependency, + isSupported, +) where + +import Control.Effect.Diagnostics (Diagnostics, context) +import Data.Aeson.Types ( + FromJSONKey, + ) +import Data.Aeson.Types qualified as AesonTypes +import Data.Foldable (asum, for_) +import Data.Map (Map) +import Data.Map.Strict qualified as Map +import Data.Text (Text) +import Data.Text qualified as Text +import Data.Yaml (FromJSON (parseJSON), (.:), (.:?)) +import Data.Yaml qualified as Yaml +import DepTypes ( + DepEnvironment (EnvDevelopment, EnvProduction), + DepType (GitType, PubType), + Dependency (..), + VerConstraint (CEq), + ) +import Effect.Exec (Exec, Has) +import Effect.Logger (Logger (..), Pretty (pretty), logDebug) +import Effect.ReadFS (ReadFS, readContentsYaml) +import GHC.Generics (Generic) +import Graphing (Graphing, deeps, directs) +import Path +import Types (GraphBreadth (..)) + +newtype PackageName = PackageName {unPackageName :: Text} deriving (Show, Eq, Ord, FromJSONKey) +newtype PubLockContent = PubLockContent {packages :: Map PackageName PubLockPackageMetadata} deriving (Show, Eq, Ord) + +-- | Represents Pub Dependency's metadata from lock file. +data PubLockPackageMetadata = PubLockPackageMetadata + { pubLockPackageIsDirect :: Bool + , pubLockPackageSource :: PubDepSource + , pubLockPackageVersion :: Maybe Text + , pubLockPackageEnvironment :: [DepEnvironment] + } + deriving (Generic, Show, Eq, Ord) + +newtype PubLockPackageSdkSource = PubLockPackageSdkSource {sdkName :: Text} deriving (Show, Eq, Ord) +newtype PubLockPackagePathSource = PubLockPackagePathSource {hostPath :: Text} deriving (Show, Eq, Ord) +data PubLockPackageGitSource = PubLockPackageGitSource {gitUrl :: Text, ref :: Text} deriving (Show, Eq, Ord) +data PubLockPackageHostedSource = PubLockPackageHostedSource {hostPackageName :: Maybe Text, hostUrl :: Maybe Text} deriving (Show, Eq, Ord) + +-- | Represents Pub Dependency's source. +data PubDepSource + = SdkSource PubLockPackageSdkSource + | GitSource PubLockPackageGitSource + | HostedSource PubLockPackageHostedSource + | PathSource PubLockPackagePathSource + deriving (Show, Eq, Ord) + +instance FromJSON PackageName where + parseJSON (AesonTypes.String packageName) = pure $ PackageName packageName + parseJSON _ = fail "failed to parse package's name" + +instance FromJSON PubLockContent where + parseJSON = Yaml.withObject "pubspec.lock content" $ \o -> do + packages <- o .: "packages" + pure $ PubLockContent packages + +instance FromJSON PubLockPackageMetadata where + parseJSON = Yaml.withObject "pubspec.lock content package" $ \o -> do + pubLockPackageIsDirect <- isDirect <$> o .: "dependency" + pubLockPackageSource <- o .: "description" + pubLockPackageVersion <- o .:? "version" + pubLockPackageEnvironment <- getEnvironment <$> o .: "dependency" + pure $ PubLockPackageMetadata pubLockPackageIsDirect pubLockPackageSource pubLockPackageVersion pubLockPackageEnvironment + where + isDirect :: Text -> Bool + isDirect candidate = Text.isInfixOf "direct" candidate + + getEnvironment :: Text -> [DepEnvironment] + getEnvironment candidate + | Text.isInfixOf "main" candidate = [EnvProduction] + | Text.isInfixOf "dev" candidate = [EnvDevelopment] + | otherwise = [] + +instance FromJSON PubDepSource where + parseJSON (Yaml.String v) = pure $ SdkSource $ PubLockPackageSdkSource v + parseJSON (Yaml.Object o) = + asum + [ parseGitSource o + , parsePathSource o + , parseHostedSource o + ] + where + parseHostedSource :: Yaml.Object -> Yaml.Parser PubDepSource + parseHostedSource ho = HostedSource <$> (PubLockPackageHostedSource <$> ho .:? "name" <*> ho .:? "url") + + parseGitSource :: Yaml.Object -> Yaml.Parser PubDepSource + parseGitSource go = GitSource <$> (PubLockPackageGitSource <$> go .: "url" <*> go .: "ref") + + parsePathSource :: Yaml.Object -> Yaml.Parser PubDepSource + parsePathSource po = PathSource . PubLockPackagePathSource <$> po .: "path" + parseJSON _ = fail "could not parse pub dependency's metadata" + +isSupported :: PubLockPackageMetadata -> Bool +isSupported meta = + case pubLockPackageSource meta of + SdkSource _ -> False + PathSource _ -> False + GitSource _ -> True + HostedSource _ -> True + +-- Transforms Package into Dependency. +toDependency :: PackageName -> PubLockPackageMetadata -> Dependency +toDependency pkg meta = + Dependency + { dependencyType = depType + , dependencyName = depName + , dependencyVersion = depVersion + , dependencyLocations = depLocation + , dependencyEnvironments = pubLockPackageEnvironment meta + , dependencyTags = Map.empty + } + where + depType :: DepType + depType = case pubLockPackageSource meta of + GitSource{} -> GitType + _ -> PubType + + depName :: Text + depName = case pubLockPackageSource meta of + GitSource (PubLockPackageGitSource gitUrl _) -> gitUrl + _ -> unPackageName pkg + + depVersion :: Maybe VerConstraint + depVersion = case pubLockPackageSource meta of + GitSource (PubLockPackageGitSource _ ref) -> Just $ CEq ref + _ -> CEq <$> pubLockPackageVersion meta + + depLocation :: [Text] + depLocation = case pubLockPackageSource meta of + HostedSource (PubLockPackageHostedSource _ (Just hostUrl)) -> [hostUrl] + _ -> [] + +-- | Builds the dependency graphing from pubspec.lock's content. +-- Edges are not reported. +buildGraph :: PubLockContent -> Graphing Dependency +buildGraph lockContent = graphOfDirects <> graphOfTransitives + where + supportedPackages :: Map PackageName PubLockPackageMetadata + supportedPackages = Map.filter isSupported $ packages lockContent + + getDependencies :: (PubLockPackageMetadata -> Bool) -> [Dependency] + getDependencies f = Map.elems $ Map.mapWithKey toDependency $ Map.filter f supportedPackages + + graphOfTransitives :: Graphing Dependency + graphOfTransitives = deeps $ getDependencies $ not . pubLockPackageIsDirect + + graphOfDirects :: Graphing Dependency + graphOfDirects = directs $ getDependencies pubLockPackageIsDirect + +logIgnoredPackages :: Has Logger sig m => PubLockContent -> m () +logIgnoredPackages lockContent = for_ notSupportedDependenciesMsgs (logDebug . pretty) + where + notSupportedDependenciesMsgs :: [Text] + notSupportedDependenciesMsgs = map (<> " : ignored in analyses. Dependency's source is not supported!") notSupportedPackages + + notSupportedPackages :: [Text] + notSupportedPackages = map (unPackageName . fst) (Map.toList $ Map.filter isSupported $ packages lockContent) + +analyzePubLockFile :: + (Has Exec sig m, Has ReadFS sig m, Has Diagnostics sig m, Has Logger sig m) => + Path Abs File -> + m (Graphing Dependency, GraphBreadth) +analyzePubLockFile lockFile = do + lockContents <- context "Reading pubspec.lock" $ readContentsYaml lockFile + _ <- logIgnoredPackages lockContents + context "building graphing from pubspec.lock only" $ pure (buildGraph lockContents, Partial) diff --git a/src/Strategy/Pub.hs b/src/Strategy/Pub.hs new file mode 100644 index 000000000..95ab39f9e --- /dev/null +++ b/src/Strategy/Pub.hs @@ -0,0 +1,60 @@ +module Strategy.Pub (discover) where + +import Control.Effect.Diagnostics (Diagnostics, context, (<||>)) +import Discovery.Walk (WalkStep (WalkContinue), findFileNamed, walk') +import Effect.Exec (Exec, Has) +import Effect.Logger (Logger (..)) +import Effect.ReadFS (ReadFS) +import Path +import Strategy.Dart.PubDeps (analyzeDepsCmd) +import Strategy.Dart.PubSpec (analyzePubSpecFile) +import Strategy.Dart.PubSpecLock (analyzePubLockFile) +import Types (DependencyResults (..), DiscoveredProject (..)) + +discover :: (Has ReadFS sig m, Has Diagnostics sig m, Has ReadFS rsig run, Has Exec rsig run, Has Diagnostics rsig run, Has Logger rsig run) => Path Abs Dir -> m [DiscoveredProject run] +discover dir = context "Pub" $ do + projects <- context "Finding projects" $ findProjects dir + pure (map mkProject projects) + +findProjects :: (Has ReadFS sig m, Has Diagnostics sig m) => Path Abs Dir -> m [PubProject] +findProjects = walk' $ \dir _ files -> do + -- Note: pub does not support pubspec.yml naming - it must be pubspec.yaml. + let pubSpecFile = findFileNamed "pubspec.yaml" files + let pubSpecLockFile = findFileNamed "pubspec.lock" files + + case (pubSpecFile, pubSpecLockFile) of + (Just specFile, Just lockFile) -> pure ([PubProject specFile (Just lockFile) dir], WalkContinue) + (Just specFile, Nothing) -> pure ([PubProject specFile Nothing dir], WalkContinue) + -- lockfile without manifest (pubspec.yaml) is not a dart project + -- ref: https://dart.dev/guides/packages + (Nothing, Just _) -> pure ([], WalkContinue) + (Nothing, Nothing) -> pure ([], WalkContinue) + +data PubProject = PubProject + { pubSpec :: Path Abs File + , pubLock :: Maybe (Path Abs File) + , pubSpecDir :: Path Abs Dir + } + deriving (Eq, Ord, Show) + +mkProject :: (Has Exec sig n, Has ReadFS sig n, Has Diagnostics sig n, Has Logger sig n) => PubProject -> DiscoveredProject n +mkProject project = + DiscoveredProject + { projectType = "pub" + , projectBuildTargets = mempty + , projectDependencyResults = const $ getDeps project + , projectPath = pubSpecDir project + , projectLicenses = pure [] + } + +getDeps :: (Has Exec sig m, Has ReadFS sig m, Has Diagnostics sig m, Has Logger sig m) => PubProject -> m DependencyResults +getDeps project = do + (graph, graphBreadth) <- case pubLock project of + Just lockFile -> analyzeDepsCmd lockFile (pubSpecDir project) <||> analyzePubLockFile lockFile + Nothing -> analyzePubSpecFile $ pubSpec project + pure $ + DependencyResults + { dependencyGraph = graph + , dependencyGraphBreadth = graphBreadth + , dependencyManifestFiles = [pubSpec project] + } diff --git a/test/Dart/PubDepsSpec.hs b/test/Dart/PubDepsSpec.hs new file mode 100644 index 000000000..351cd057f --- /dev/null +++ b/test/Dart/PubDepsSpec.hs @@ -0,0 +1,354 @@ +module Dart.PubDepsSpec ( + spec, +) where + +import Data.Map.Strict qualified as Map +import Data.Set qualified as Set +import Data.Text.IO qualified as TIO +import DepTypes +import GraphUtil (expectDeps, expectDirect, expectEdges) +import Strategy.Dart.PubDeps (PubDepPackage (..), buildGraph, depsCmdOutputParser) +import Strategy.Dart.PubSpecLock ( + PackageName (..), + PubDepSource (..), + PubLockContent (..), + PubLockPackageHostedSource (..), + PubLockPackageMetadata (..), + PubLockPackagePathSource (..), + PubLockPackageSdkSource (..), + ) +import Test.Hspec +import Text.Megaparsec + +expectedDepsCmdOutput :: [PubDepPackage] +expectedDepsCmdOutput = + [ PubDepPackage (PackageName "pkg_a") (Just "5.0.0") (Just $ Set.fromList [PackageName "pkg_aa"]) True + , PubDepPackage (PackageName "pkg_b") (Just "4.0.0") Nothing True + , PubDepPackage (PackageName "pkg_c") (Just "3.0.0") (Just $ Set.fromList [PackageName "pkg_ca", PackageName "pkg_cb"]) True + , PubDepPackage (PackageName "pkg_d") (Just "3.0.1") Nothing True + , PubDepPackage (PackageName "pkg_e") (Just "2.0.0") (Just $ Set.fromList [PackageName "pkg_ea"]) True + , PubDepPackage (PackageName "pkg_aa") (Just "1.0.0") (Just $ Set.fromList [PackageName "pkg_ca"]) False + , PubDepPackage (PackageName "pkg_ca") (Just "1.2.0") Nothing False + , PubDepPackage (PackageName "pkg_cb") (Just "1.3.0") Nothing False + , PubDepPackage (PackageName "pkg_ea") (Just "1.4.0") Nothing False + ] + +spec :: Spec +spec = do + describe "pub deps -s compact" $ do + contents <- runIO (TIO.readFile "test/Dart/testdata/pubdeps.compact") + + it "should parse content correctly" $ do + case runParser depsCmdOutputParser "" contents of + Left failCode -> expectationFailure $ show failCode + Right result -> result `shouldBe` expectedDepsCmdOutput + + describe "buildGraph" $ do + it "should build graph with edges" $ do + let pubDepsContent = + [ PubDepPackage (PackageName "pkg_a") (Just "1.8.0") (Just $ Set.fromList [PackageName "pkg_deep"]) True + , PubDepPackage (PackageName "pkg_deep") (Just "1.10.0") (Just $ Set.fromList [PackageName "pkg_deeper"]) False + , PubDepPackage (PackageName "pkg_deeper") (Just "1.20.0") Nothing False + ] + + let lockContent = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_a" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_a") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.8.0" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_deep" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_deep") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.10.0" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_deeper" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_deep") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.20.0" + , pubLockPackageEnvironment = [] + } + ) + ] + } + let graph = buildGraph lockContent pubDepsContent + + expectDirect + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + + expectDeps + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deep" + , dependencyVersion = Just $ CEq "1.10.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deeper" + , dependencyVersion = Just $ CEq "1.20.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + + expectEdges + [ + ( Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deep" + , dependencyVersion = Just $ CEq "1.10.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ) + , + ( Dependency + { dependencyType = PubType + , dependencyName = "pkg_deep" + , dependencyVersion = Just $ CEq "1.10.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deeper" + , dependencyVersion = Just $ CEq "1.20.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ) + ] + graph + + it "should ignore path dependency" $ do + let pubDepsContent = [PubDepPackage (PackageName "pkg_path") (Just "1.10.0") Nothing True] + let lockContent = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_path" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = PathSource $ PubLockPackagePathSource "./../dir" + , pubLockPackageVersion = Just "1.8.0" + , pubLockPackageEnvironment = [] + } + ) + ] + } + + let graph = buildGraph lockContent pubDepsContent + expectDirect [] graph + expectDeps [] graph + expectEdges [] graph + + it "should ignore sdk dependency" $ do + let pubDepsContent = [PubDepPackage (PackageName "pkg_sdk") (Just "1.10.0") Nothing True] + + let lockContent = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_sdk" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = SdkSource $ PubLockPackageSdkSource "pkg_sdk_source" + , pubLockPackageVersion = Just "1.8.0" + , pubLockPackageEnvironment = [] + } + ) + ] + } + + let graph = buildGraph lockContent pubDepsContent + expectDirect [] graph + expectDeps [] graph + expectEdges [] graph + + it "should build graph when dependencies have no edges" $ do + let pubDepsContent = [PubDepPackage (PackageName "pkg_a") (Just "1.10.0") Nothing True] + + let lockContent = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_a" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource Nothing Nothing + , pubLockPackageVersion = Just "1.8.0" + , pubLockPackageEnvironment = [] + } + ) + ] + } + + let graph = buildGraph lockContent pubDepsContent + expectDirect + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = [] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + expectDeps + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = [] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + expectEdges [] graph + + it "should retain supported transitive dependencies, even if predecessor is not supported" $ do + let pubDepsContent = + [ PubDepPackage (PackageName "pkg_a") (Just "1.8.0") (Just $ Set.fromList [PackageName "pkg_deep"]) True + , PubDepPackage (PackageName "pkg_deep") (Just "1.10.0") (Just $ Set.fromList [PackageName "pkg_deeper"]) False + , PubDepPackage (PackageName "pkg_deeper") (Just "1.20.0") Nothing False + ] + + let lockContent = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_a" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_a") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.8.0" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_deep" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = PathSource $ PubLockPackagePathSource "some/path/" + , pubLockPackageVersion = Just "1.10.0" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_deeper" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_deep") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.20.0" + , pubLockPackageEnvironment = [] + } + ) + ] + } + let graph = buildGraph lockContent pubDepsContent + + expectDirect + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + + expectDeps + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deeper" + , dependencyVersion = Just $ CEq "1.20.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + + expectEdges + [ + ( Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.8.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deeper" + , dependencyVersion = Just $ CEq "1.20.0" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ) + ] + graph diff --git a/test/Dart/PubSpecLockSpec.hs b/test/Dart/PubSpecLockSpec.hs new file mode 100644 index 000000000..43e0e3d5b --- /dev/null +++ b/test/Dart/PubSpecLockSpec.hs @@ -0,0 +1,210 @@ +module Dart.PubSpecLockSpec ( + spec, +) where + +import Data.ByteString qualified as BS +import Data.Map qualified as Map +import Data.Yaml (decodeEither') +import DepTypes +import GraphUtil (expectDeps, expectDirect) +import Graphing (empty) +import Strategy.Dart.PubSpecLock ( + PackageName (..), + PubDepSource (..), + PubLockContent (..), + PubLockPackageGitSource (..), + PubLockPackageHostedSource (..), + PubLockPackageMetadata (..), + PubLockPackagePathSource (..), + PubLockPackageSdkSource (..), + buildGraph, + toDependency, + ) +import Test.Hspec + +expectedLockFile :: PubLockContent +expectedLockFile = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_hosted" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_hosted") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.1" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_git" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = GitSource $ PubLockPackageGitSource "https://github.com/user/pkg" "release-0.9" + , pubLockPackageVersion = Just "1.2" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_sdk" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = SdkSource $ PubLockPackageSdkSource "flutter" + , pubLockPackageVersion = Just "1.3" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_file" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = PathSource $ PubLockPackagePathSource "/Users/dir/pkg_dir" + , pubLockPackageVersion = Just "1.4" + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_hosted_direct" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_hosted_direct") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.5" + , pubLockPackageEnvironment = [EnvProduction] + } + ) + , + ( PackageName "pkg_hosted_direct_dev" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_hosted_direct_dev") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.6" + , pubLockPackageEnvironment = [EnvDevelopment] + } + ) + ] + } + +spec :: Spec +spec = do + lockFile <- runIO (BS.readFile "test/Dart/testdata/pubspec.lock") + + describe "pubspec.lock parsing" $ + it "should parse file correctly" $ + case decodeEither' lockFile of + Right res -> res `shouldBe` expectedLockFile + Left err -> expectationFailure $ "failed to parse: " <> show err + + describe "pubLockPackage to dependency conversion" $ do + it "should create dependency for hosted sources" $ do + let pkg = + PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_a") (Just "https://pub.dartlang.org") + , pubLockPackageVersion = Just "1.1" + , pubLockPackageEnvironment = [EnvDevelopment] + } + let expectedDependency = + Dependency + { dependencyType = PubType + , dependencyName = "pkg_a" + , dependencyVersion = Just $ CEq "1.1" + , dependencyLocations = ["https://pub.dartlang.org"] + , dependencyEnvironments = [EnvDevelopment] + , dependencyTags = Map.empty + } + toDependency (PackageName "pkg_a") pkg `shouldBe` expectedDependency + + it "should create dependency for git sources" $ do + let pkg = + PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = GitSource $ PubLockPackageGitSource "https://github.com/user/pkg" "release-0.9" + , pubLockPackageVersion = Just "1.1" + , pubLockPackageEnvironment = [] + } + let expectedDependency = + Dependency + { dependencyType = GitType + , dependencyName = "https://github.com/user/pkg" + , dependencyVersion = Just $ CEq "release-0.9" + , dependencyLocations = [] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + toDependency (PackageName "pkg_b") pkg `shouldBe` expectedDependency + + describe "graphing from pubspec.lock" $ do + it "should not create graphing for packages sourced from sdk" $ do + let sdkSources = + PubLockContent $ + Map.filterWithKey (\x _ -> (unPackageName x) == "pkg_sdk") (packages expectedLockFile) + + Map.null (packages sdkSources) `shouldBe` False + buildGraph sdkSources `shouldBe` Graphing.empty + + it "should not create graphing for packages sourced from file" $ do + let fileSources = + PubLockContent $ + Map.filterWithKey (\x _ -> (unPackageName x) == "pkg_file") (packages expectedLockFile) + + Map.null (packages fileSources) `shouldBe` False + buildGraph fileSources `shouldBe` Graphing.empty + + it "should graph direct and deep dependencies" $ do + let lockContent = + PubLockContent + { packages = + Map.fromList + [ + ( PackageName "pkg_direct" + , PubLockPackageMetadata + { pubLockPackageIsDirect = True + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_direct") (Just "some-url-1") + , pubLockPackageVersion = Nothing + , pubLockPackageEnvironment = [] + } + ) + , + ( PackageName "pkg_deep" + , PubLockPackageMetadata + { pubLockPackageIsDirect = False + , pubLockPackageSource = HostedSource $ PubLockPackageHostedSource (Just "pkg_deep") (Just "some-url-2") + , pubLockPackageVersion = Nothing + , pubLockPackageEnvironment = [] + } + ) + ] + } + let graph = buildGraph lockContent + + expectDirect + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_direct" + , dependencyVersion = Nothing + , dependencyLocations = ["some-url-1"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph + + expectDeps + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_direct" + , dependencyVersion = Nothing + , dependencyLocations = ["some-url-1"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_deep" + , dependencyVersion = Nothing + , dependencyLocations = ["some-url-2"] + , dependencyEnvironments = [] + , dependencyTags = Map.empty + } + ] + graph diff --git a/test/Dart/PubSpecSpec.hs b/test/Dart/PubSpecSpec.hs new file mode 100644 index 000000000..de882edf6 --- /dev/null +++ b/test/Dart/PubSpecSpec.hs @@ -0,0 +1,169 @@ +module Dart.PubSpecSpec ( + spec, +) where + +import Data.ByteString qualified as BS +import Data.Map.Strict qualified as Map +import Data.Yaml (decodeEither') +import DepTypes +import GraphUtil (expectDeps, expectDirect, expectEdges) +import Strategy.Dart.PubSpec ( + PubSpecContent (..), + PubSpecDepGitSource (..), + PubSpecDepHostedSource (..), + PubSpecDepPathSource (..), + PubSpecDepSdkSource (..), + PubSpecDepSource (..), + buildGraph, + ) +import Strategy.Dart.PubSpecLock (PackageName (..)) +import Test.Hspec + +-- hostedSource :: (Maybe VerConstraint) -> Maybe Text -> Maybe Text + +spec :: Spec +spec = do + specFile <- runIO (BS.readFile "test/Dart/testdata/pubspec.yaml") + describe "parse pubspec.yml" $ + it "should parse dependencies" $ do + let expectedPubSpecContent = + PubSpecContent + { pubSpecDependencies = + Just $ + Map.fromList + [ (PackageName "pkg_default", HostedSource $ PubSpecDepHostedSource (Just "1.3.0") Nothing Nothing) + , (PackageName "pkg_hosted", HostedSource $ PubSpecDepHostedSource (Just "^1.0.0") (Just "pkg_hosted") (Just "http://pub.dev")) + , (PackageName "pkg_a", GitSource $ PubSpecDepGitSource Nothing "https://github.com/user/pkg_a.git") + , (PackageName "pkg_b", GitSource $ PubSpecDepGitSource (Just "release-0.9") "https://github.com/user/pkg_b") + , (PackageName "pkg_sdk", SdkSource $ PubSpecDepSdkSource "flutter") + ] + , pubSpecDevDependencies = + Just $ + Map.fromList + [ (PackageName "pkg_dev_default", HostedSource $ PubSpecDepHostedSource (Just "1.0.0") Nothing Nothing) + ] + , pubSpecDependenciesOverrides = + Just $ + Map.fromList + [ (PackageName "pkg_b", PathSource $ PubSpecDepPathSource "./some/dir") + ] + } + + case decodeEither' specFile of + Right res -> res `shouldBe` expectedPubSpecContent + Left err -> expectationFailure $ "failed to parse: " <> show err + + describe "build graph from pubspec.yml" $ do + it "should create expected graph" $ do + let pubSpecContent = + PubSpecContent + { pubSpecDependencies = + Just $ + Map.fromList + [ (PackageName "pkg_default", HostedSource $ PubSpecDepHostedSource (Just "1.3.0") Nothing Nothing) + , (PackageName "pkg_hosted", HostedSource $ PubSpecDepHostedSource (Just "^1.0.0") (Just "pkg_hosted") (Just "http://pub.dev")) + , (PackageName "pkg_a", GitSource $ PubSpecDepGitSource Nothing "https://github.com/user/pkg_a.git") + ] + , pubSpecDevDependencies = Just $ Map.fromList [(PackageName "pkg_dev_default", HostedSource $ PubSpecDepHostedSource (Just "^1.0.0") Nothing Nothing)] + , pubSpecDependenciesOverrides = Nothing + } + + let graph = buildGraph pubSpecContent + let expectedGraphDeps = + [ Dependency + { dependencyType = PubType + , dependencyName = "pkg_default" + , dependencyVersion = Just $ CEq "1.3.0" + , dependencyLocations = [] + , dependencyEnvironments = [EnvProduction] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_hosted" + , dependencyVersion = Just $ CEq "^1.0.0" + , dependencyLocations = ["http://pub.dev"] + , dependencyEnvironments = [EnvProduction] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = GitType + , dependencyName = "https://github.com/user/pkg_a.git" + , dependencyVersion = Nothing + , dependencyLocations = [] + , dependencyEnvironments = [EnvProduction] + , dependencyTags = Map.empty + } + , Dependency + { dependencyType = PubType + , dependencyName = "pkg_dev_default" + , dependencyVersion = Just $ CEq "^1.0.0" + , dependencyLocations = [] + , dependencyEnvironments = [EnvDevelopment] + , dependencyTags = Map.empty + } + ] + expectEdges [] graph + expectDeps expectedGraphDeps graph + expectDirect expectedGraphDeps graph + + it "should not graph, if dependency is overriden, and the new source is not supported" $ do + let pubSpecContent = + PubSpecContent + { pubSpecDependencies = Just $ Map.fromList [(PackageName "pkg_b", GitSource $ PubSpecDepGitSource (Just "release-0.9") "https://github.com/user/pkg_b")] + , pubSpecDependenciesOverrides = Just $ Map.fromList [(PackageName "pkg_b", PathSource $ PubSpecDepPathSource "./some/dir")] + , pubSpecDevDependencies = Nothing + } + + let graph = buildGraph pubSpecContent + expectEdges [] graph + expectDeps [] graph + expectDirect [] graph + + it "should graph, if dependency is overriden, and the new source is supported" $ do + let pubSpecContent = + PubSpecContent + { pubSpecDependencies = + Just $ Map.fromList [(PackageName "pkg_b", GitSource $ PubSpecDepGitSource (Just "release-0.9") "https://github.com/user/pkg_b")] + , pubSpecDependenciesOverrides = + Just $ Map.fromList [(PackageName "pkg_b", GitSource $ PubSpecDepGitSource (Just "develop") "https://github.com/user/pkg_b")] + , pubSpecDevDependencies = Nothing + } + let graph = buildGraph pubSpecContent + let expectedGraphDeps = + [ Dependency + { dependencyType = GitType + , dependencyName = "https://github.com/user/pkg_b" + , dependencyVersion = Just $ CEq "develop" + , dependencyLocations = [] + , dependencyEnvironments = [EnvProduction] + , dependencyTags = Map.empty + } + ] + expectEdges [] graph + expectDeps expectedGraphDeps graph + expectDirect expectedGraphDeps graph + + it "should not graph dependency of path sources" $ do + let pubSpecContent = + PubSpecContent + { pubSpecDependencies = Just $ Map.fromList [(PackageName "pkg_sdk", PathSource $ PubSpecDepPathSource "./../some-dir/")] + , pubSpecDevDependencies = Nothing + , pubSpecDependenciesOverrides = Nothing + } + let graph = buildGraph pubSpecContent + expectEdges [] graph + expectDeps [] graph + expectDirect [] graph + + it "should not graph dependency of sdk sources" $ do + let pubSpecContent = + PubSpecContent + { pubSpecDependencies = Just $ Map.fromList [(PackageName "pkg_sdk", SdkSource $ PubSpecDepSdkSource "flutter")] + , pubSpecDevDependencies = Nothing + , pubSpecDependenciesOverrides = Nothing + } + let graph = buildGraph pubSpecContent + expectEdges [] graph + expectDeps [] graph + expectDirect [] graph diff --git a/test/Dart/testdata/pubdeps.compact b/test/Dart/testdata/pubdeps.compact new file mode 100644 index 000000000..752e39255 --- /dev/null +++ b/test/Dart/testdata/pubdeps.compact @@ -0,0 +1,21 @@ +Dart SDK 2.14.0-301.0.dev +Flutter SDK 2.4.0-4.0.pre +some_example 1.0.0+1 + +dependencies: +- pkg_a 5.0.0 [pkg_aa] +- pkg_b 4.0.0 +- pkg_c 3.0.0 [pkg_ca pkg_cb] +- pkg_d 3.0.1 + +dev dependencies: +- pkg_e 2.0.0 [pkg_ea] + +dependency overrides: +- pkg_d 3.0.1 + +transitive dependencies: +- pkg_aa 1.0.0 [pkg_ca] +- pkg_ca 1.2.0 +- pkg_cb 1.3.0 +- pkg_ea 1.4.0 diff --git a/test/Dart/testdata/pubdeps.json b/test/Dart/testdata/pubdeps.json new file mode 100644 index 000000000..439eb8993 --- /dev/null +++ b/test/Dart/testdata/pubdeps.json @@ -0,0 +1,67 @@ +{ + "root": "main_app", + "packages": [ + { + "name": "main_app", + "version": "2.0.0", + "kind": "root", + "source": "root", + "dependencies": [ + "pkg_a", + "pkg_dev_a", + "pkg_git", + "pkg_flutter" + ] + }, + { + "name": "pkg_a", + "version": "1.8.0", + "kind": "direct", + "source": "hosted", + "dependencies": ["pkg_deep"] + }, + { + "name": "pkg_dev_a", + "version": "3.2.0", + "kind": "dev", + "source": "hosted", + "dependencies": [] + }, + { + "name": "pkg_git", + "version": "1.8.0", + "kind": "direct", + "source": "git", + "dependencies": [] + }, + { + "name": "pkg_flutter", + "version": "1.8.0", + "kind": "direct", + "source": "sdk", + "dependencies": [] + }, + { + "name": "pkg_deep", + "version": "1.10.0", + "kind": "transitive", + "source": "hosted", + "dependencies": [] + } + ], + "sdks": [ + { + "name": "Dart", + "version": "2.14.0-301.0.dev" + }, + { + "name": "Flutter", + "version": "2.4.0-4.0.pre" + } + ], + "executables": [ + "test", + "build_runner" + ] + } + \ No newline at end of file diff --git a/test/Dart/testdata/pubspec.lock b/test/Dart/testdata/pubspec.lock new file mode 100644 index 000000000..d7f7003a0 --- /dev/null +++ b/test/Dart/testdata/pubspec.lock @@ -0,0 +1,61 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + + # hosted source + pkg_hosted: + dependency: transitive + description: + name: pkg_hosted + url: "https://pub.dartlang.org" + source: hosted + version: "1.1" + + # git source + pkg_git: + dependency: transitive + description: + path: "." + ref: "release-0.9" + resolved-ref: "6af9eab77cf8d777b35c349b75a9c345de967ed4" + url: "https://github.com/user/pkg" + source: hosted + version: "1.2" + + # sdk source + pkg_sdk: + dependency: transitive + description: flutter + source: sdk + version: "1.3" + + # file source + pkg_file: + dependency: transitive + description: + path: "/Users/dir/pkg_dir" + relative: false + source: path + version: "1.4" + + # direct dependency + pkg_hosted_direct: + dependency: "direct main" + description: + name: pkg_hosted_direct + url: "https://pub.dartlang.org" + source: hosted + version: "1.5" + + # direct development dependency + pkg_hosted_direct_dev: + dependency: "direct dev" + description: + name: pkg_hosted_direct_dev + url: "https://pub.dartlang.org" + source: hosted + version: "1.6" + +sdks: + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.16.0" \ No newline at end of file diff --git a/test/Dart/testdata/pubspec.yaml b/test/Dart/testdata/pubspec.yaml new file mode 100644 index 000000000..2d36b1bdb --- /dev/null +++ b/test/Dart/testdata/pubspec.yaml @@ -0,0 +1,32 @@ +name: some_example +description: A Flutter sample to demonstrate isolates +version: 1.0.0+1 + +environment: + sdk: ">=2.0.0 <3.0.0" + +dependencies: + pkg_default: 1.3.0 + pkg_hosted: + hosted: + name: pkg_hosted + url: http://pub.dev + version: ^1.0.0 + pkg_a: + git: https://github.com/user/pkg_a.git + pkg_b: + git: + url: https://github.com/user/pkg_b + ref: release-0.9 + pkg_sdk: + sdk: flutter + +dev_dependencies: + pkg_dev_default: 1.0.0 + +dependency_overrides: + pkg_b: + path: ./some/dir + +flutter: + uses-material-design: true \ No newline at end of file