Add additional validations to the pkg/ package pubspecs.

TEST=these are additional validations that we run on the bots

Redux of https://dart-review.googlesource.com/c/sdk/+/161040

Change-Id: Ia32ced5d48fbfeafacfa9e51dc4774d2e9425091
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174601
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Devon Carew <devoncarew@google.com>
diff --git a/pkg/README.md b/pkg/README.md
new file mode 100644
index 0000000..474a204
--- /dev/null
+++ b/pkg/README.md
@@ -0,0 +1,43 @@
+# Package validation
+
+The packages in `pkg/` are automatically validated on the LUCI CI bots. The
+validation is largely done by the `tools/package_deps` package; it can be tested
+locally via:
+
+```
+dart tools/package_deps/bin/package_deps.dart
+```
+
+## Packages which are published
+
+There are several packages developed in `pkg/` which are published to pub.
+Validation of these packages is particularly important because the pub tools are
+not used for these packages during development; we get our dependency versions
+from the DEPS file. Its very easy for the dependencies specified in a package's
+pubspec file to get out of date wrt the packages and versions actually used.
+
+In order to better ensure we're publishing correct packages, we validate some
+properties of the pubspec files on our CI system. These validations include:
+
+- that the dependencies listed in the pubspec are used in the package
+- that all the packages used by the source are listed in the pubspec
+- that we don't use relative path deps to pkg/ or third_party/ packages
+
+## Packages which are not published
+
+For packages in pkg/ which we do not intend to be published, we put the
+following comment in the pubspec.yaml file:
+
+```
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+```
+
+These pubspecs are still validated by the package validation tool. The contents
+are more informational as the pubspecs for these packages are not consumed by
+the pub tool or ecosystem.
+
+We validate:
+- that the dependencies listed in the pubspec are used in the package
+- that all the packages used by the source are listed in the pubspec
+- that a reference to a pkg/ package is done via a relative path dependency
diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml
index 9a94f7d..ef86eeb 100644
--- a/pkg/analysis_server/pubspec.yaml
+++ b/pkg/analysis_server/pubspec.yaml
@@ -8,8 +8,10 @@
 dependencies:
   _fe_analyzer_shared:
     path: ../_fe_analyzer_shared
-  analyzer: any
-  analyzer_plugin: any
+  analyzer:
+    path: ../analyzer
+  analyzer_plugin:
+    path: ../analyzer_plugin
   args: any
   cli_util: any
   collection: any
@@ -19,7 +21,8 @@
   html: any
   intl: any
   linter: any
-  meta: any
+  meta:
+    path: ../meta
   stream_channel: any
   telemetry:
     path: ../telemetry
@@ -29,7 +32,8 @@
   yaml: any
 
 dev_dependencies:
-  analyzer_utilities: any
+  analyzer_utilities:
+    path: ../analyzer_utilities
   http: any
   logging: any
   matcher: any
diff --git a/pkg/analyzer_cli/pubspec.yaml b/pkg/analyzer_cli/pubspec.yaml
index a1980aa..da7989b 100644
--- a/pkg/analyzer_cli/pubspec.yaml
+++ b/pkg/analyzer_cli/pubspec.yaml
@@ -8,13 +8,15 @@
   sdk: "^2.7.0"
 
 dependencies:
-  analyzer: any
+  analyzer:
+    path: ../analyzer
   args: '>=0.13.0 <2.0.0'
   bazel_worker: ^0.1.0
   collection: ^1.14.1
   convert: any
   linter: ^0.1.16
-  meta: any
+  meta:
+    path: ../meta
   path: any
   pub_semver: ^1.4.2
   yaml: any
@@ -24,7 +26,3 @@
   protobuf: ^0.13.0
   test_reflective_loader: ^0.1.8
   test: ^1.0.0
-
-dependency_overrides:
-  analyzer:
-    path: ../analyzer
diff --git a/pkg/analyzer_utilities/pubspec.yaml b/pkg/analyzer_utilities/pubspec.yaml
index ed67a42..7142157 100644
--- a/pkg/analyzer_utilities/pubspec.yaml
+++ b/pkg/analyzer_utilities/pubspec.yaml
@@ -6,7 +6,8 @@
   sdk: '>=2.1.0 <3.0.0'
 
 dependencies:
-  analyzer: any
+  analyzer:
+    path: ../analyzer
   html: any
   path: any
   test: any
diff --git a/pkg/build_integration/pubspec.yaml b/pkg/build_integration/pubspec.yaml
index b741a4b..a7b79b1 100644
--- a/pkg/build_integration/pubspec.yaml
+++ b/pkg/build_integration/pubspec.yaml
@@ -9,11 +9,8 @@
   sdk: '>=2.1.0 <3.0.0'
 
 dependencies:
-  front_end: any
+  front_end:
+    path: ../front_end
 
 dev_dependencies:
   test: any
-
-dependency_overrides:
-  front_end:
-    path: ../front_end
diff --git a/pkg/compiler/pubspec.yaml b/pkg/compiler/pubspec.yaml
index 6a7ec65..8a61bf9 100644
--- a/pkg/compiler/pubspec.yaml
+++ b/pkg/compiler/pubspec.yaml
@@ -13,8 +13,10 @@
   collection: any
   crypto: any
   dart2js_info: any
-  front_end: any
-  kernel: any
+  front_end:
+    path: ../front_end
+  kernel:
+    path: ../kernel
 
   # Unpublished packages that can be used via path dependency
   _fe_analyzer_shared:
@@ -30,7 +32,8 @@
   # Published packages - repo version ensured via dependency_overrides
   args: any
   http: any
-  js: any
+  js:
+    path: ../js
   package_config: any
   path: any
   source_maps: any
@@ -54,7 +57,7 @@
   front_end:
     path: ../front_end
   kernel:
-    path: ../../pkg/kernel
+    path: ../kernel
   meta:
     path: ../meta
 
diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml
index ecf8aed..d6347db 100644
--- a/pkg/dartdev/pubspec.yaml
+++ b/pkg/dartdev/pubspec.yaml
@@ -16,7 +16,8 @@
     path: ../dart2native
   dart_style: any
   intl: any
-  meta: any
+  meta:
+    path: ../meta
   nnbd_migration:
     path: ../nnbd_migration
   path: ^1.0.0
diff --git a/pkg/dev_compiler/pubspec.yaml b/pkg/dev_compiler/pubspec.yaml
index 73cdcc9..155491d 100644
--- a/pkg/dev_compiler/pubspec.yaml
+++ b/pkg/dev_compiler/pubspec.yaml
@@ -19,7 +19,8 @@
     path: ../front_end
   kernel:
     path: ../kernel
-  meta: any
+  meta:
+    path: ../meta
   path: any
   source_maps: any
   source_span: any
@@ -27,10 +28,12 @@
     path: ../vm
 
 dev_dependencies:
-  analyzer: any
+  analyzer:
+    path: ../analyzer
   expect:
     path: ../expect
-  js: any
+  js:
+    path: ../js
   modular_test:
     path: ../modular_test
   package_config: any
diff --git a/pkg/expect/pubspec.yaml b/pkg/expect/pubspec.yaml
index b1b5bd1..6a8f0bc 100644
--- a/pkg/expect/pubspec.yaml
+++ b/pkg/expect/pubspec.yaml
@@ -12,4 +12,5 @@
   sdk: '>=2.0.0'
 
 dependencies:
-  meta: any
+  meta:
+    path: ../meta
diff --git a/pkg/front_end/pubspec.yaml b/pkg/front_end/pubspec.yaml
index 77ae47b..20a36d8 100644
--- a/pkg/front_end/pubspec.yaml
+++ b/pkg/front_end/pubspec.yaml
@@ -9,12 +9,16 @@
   sdk: '>=2.6.0 <3.0.0'
 
 dependencies:
-  _fe_analyzer_shared: any
-  kernel: any
-  package_config: any
+  _fe_analyzer_shared:
+    path: ../_fe_analyzer_shared
+  kernel:
+    path: ../kernel
+  package_config:
+    path: ../../third_party/pkg_tested/package_config
 
 dev_dependencies:
-  analyzer: any
+  analyzer:
+    path: ../analyzer
   args: '>=0.13.0 <2.0.0'
   async_helper:
     path: ../async_helper
@@ -39,13 +43,3 @@
     path: ../vm_service
   web_socket_channel: ^1.0.4
   yaml: any
-
-dependency_overrides:
-  _fe_analyzer_shared:
-    path: ../_fe_analyzer_shared/
-  analyzer:
-    path: ../analyzer
-  kernel:
-    path: ../kernel/
-  package_config:
-    path: ../../third_party/pkg_tested/package_config/
diff --git a/pkg/kernel/pubspec.yaml b/pkg/kernel/pubspec.yaml
index dc4c5f73..3609233 100644
--- a/pkg/kernel/pubspec.yaml
+++ b/pkg/kernel/pubspec.yaml
@@ -8,7 +8,8 @@
 environment:
   sdk: '>=2.6.0 <3.0.0'
 dependencies:
-  meta: ^1.0.0
+  meta:
+    path: ../meta
 dev_dependencies:
   args: '>=0.13.4 <2.0.0'
   expect:
diff --git a/pkg/nnbd_migration/pubspec.yaml b/pkg/nnbd_migration/pubspec.yaml
index ed3de13..0a382de 100644
--- a/pkg/nnbd_migration/pubspec.yaml
+++ b/pkg/nnbd_migration/pubspec.yaml
@@ -7,14 +7,18 @@
   sdk: '>=2.6.0 <3.0.0'
 
 dependencies:
-  _fe_analyzer_shared: ^4.0.0
-  analyzer: ^0.40.4
-  analyzer_plugin: ^0.2.4
+  _fe_analyzer_shared:
+    path: ../_fe_analyzer_shared
+  analyzer:
+    path: ../analyzer
+  analyzer_plugin:
+    path: ../analyzer_plugin
   args: ^1.4.4
   cli_util: ^0.2.0
   collection: ^1.14.11
   crypto: ^2.0.6
-  meta: ^1.1.6
+  meta:
+    path: ../meta
   path: ^1.6.2
   pub_semver: ^1.4.2
   source_span: ^1.4.1
diff --git a/pkg/scrape/pubspec.yaml b/pkg/scrape/pubspec.yaml
index a011644..ffdaff1 100644
--- a/pkg/scrape/pubspec.yaml
+++ b/pkg/scrape/pubspec.yaml
@@ -2,11 +2,15 @@
 description: Helper package for analyzing the syntax of Dart programs.
 # This package is not intended for consumption on pub.dev. DO NOT publish.
 publish_to: none
+
 environment:
   sdk: ^2.10.0
+
 dependencies:
   args: ^1.6.0
-  analyzer: ^0.40.4
+  analyzer:
+    path: ../analyzer
   path: ^1.7.0
+
 dev_dependencies:
   pedantic: ^1.9.2
diff --git a/pkg/telemetry/pubspec.yaml b/pkg/telemetry/pubspec.yaml
index c3ba85f..ca646aa 100644
--- a/pkg/telemetry/pubspec.yaml
+++ b/pkg/telemetry/pubspec.yaml
@@ -9,7 +9,8 @@
 
 dependencies:
   http: ^0.12.0
-  meta: ^1.0.2
+  meta:
+    path: ../meta
   path: ^1.4.0
   stack_trace: ^1.7.0
   usage: ^3.2.0+1
diff --git a/pkg/vm/pubspec.yaml b/pkg/vm/pubspec.yaml
index 9a2d487..95341eb 100644
--- a/pkg/vm/pubspec.yaml
+++ b/pkg/vm/pubspec.yaml
@@ -11,9 +11,12 @@
   build_integration:
     path: ../build_integration
   crypto: any
-  front_end: any
-  kernel: ^0.3.6
-  meta: any
+  front_end:
+    path: ../front_end
+  kernel:
+    path: ../kernel
+  meta:
+    path: ../meta
   package_config: any
 
 dev_dependencies:
diff --git a/tools/package_deps/bin/package_deps.dart b/tools/package_deps/bin/package_deps.dart
index e177485..ad73069 100644
--- a/tools/package_deps/bin/package_deps.dart
+++ b/tools/package_deps/bin/package_deps.dart
@@ -4,10 +4,6 @@
 import 'package:path/path.dart' as path;
 import 'package:yaml/yaml.dart' as yaml;
 
-// TODO(devoncarew): Validate that publishable packages don't use relative sdk
-// paths in their pubspecs.
-
-// TODO(devoncarew): Find unused entries in the DEPS file.
 const validateDEPS = false;
 
 void main(List<String> arguments) {
@@ -20,6 +16,15 @@
     exit(1);
   }
 
+  print('To run this script, execute:');
+  print('');
+  print('  dart tools/package_deps/bin/package_deps.dart');
+  print('');
+  print('See pkg/README.md for more information.');
+  print('');
+  print('----');
+  print('');
+
   // locate all pkg/ packages
   final packages = <Package>[];
   for (var entity in Directory('pkg').listSync()) {
@@ -31,6 +36,8 @@
     }
   }
 
+  List<String> pkgPackages = packages.map((p) => p.packageName).toList();
+
   // Manually added directories (outside of pkg/).
   List<String> alsoValidate = [
     'tools/package_deps',
@@ -49,7 +56,7 @@
     print('validating ${package.dir}'
         '${package.publishable ? ' [publishable]' : ''}');
 
-    if (!package.validate(logger)) {
+    if (!package.validate(logger, pkgPackages)) {
       validateFailure = true;
     }
 
@@ -59,19 +66,22 @@
   // Read and display info about the sdk DEPS file.
   if (validateDEPS) {
     print('SDK DEPS');
+    print('');
+
     var sdkDeps = SdkDeps(File('DEPS'));
     sdkDeps.parse();
-    print('');
-    print('packages:');
-    for (var pkg in sdkDeps.pkgs) {
-      print('  package:$pkg');
+
+    List<String> deps = [...sdkDeps.pkgs, ...sdkDeps.testedPkgs]..sort();
+    for (var pkg in deps) {
+      final tested = sdkDeps.testedPkgs.contains(pkg);
+      print('package:$pkg${tested ? ' [tested]' : ''}');
     }
 
-    print('');
-    print('tested packages:');
-    for (var pkg in sdkDeps.testedPkgs) {
-      print('  package:$pkg');
-    }
+    // TODO(devoncarew): Validate that published packages solve against the
+    // versions brought in from the DEPS file.
+
+    // TODO(devoncarew): Find unused entries in the DEPS file.
+
   }
 
   if (validateFailure) {
@@ -93,13 +103,16 @@
 
   String get packageName => _packageName;
   Set<String> _declaredDependencies;
+  List<PubDep> _declaredPubDeps;
   Set<String> _declaredDevDependencies;
+  List<PubDep> _declaredDevPubDeps;
 
   List<String> get regularDependencies => _regularDependencies.toList()..sort();
 
   List<String> get devDependencies => _devDependencies.toList()..sort();
 
   bool _publishToNone;
+
   bool get publishable => !_publishToNone;
 
   @override
@@ -111,9 +124,9 @@
   @override
   int compareTo(Package other) => dir.compareTo(other.dir);
 
-  bool validate(Logger logger) {
+  bool validate(Logger logger, List<String> pkgPackages) {
     _parseImports();
-    return _validatePubspecDeps(logger);
+    return _validatePubspecDeps(logger, pkgPackages);
   }
 
   void _parseImports() {
@@ -155,21 +168,34 @@
     _packageName = docContents['name'];
     _publishToNone = docContents['publish_to'] == 'none';
 
+    _declaredPubDeps = [];
     if (docContents['dependencies'] != null) {
       _declaredDependencies =
           Set<String>.from(docContents['dependencies'].keys);
+
+      var deps = docContents['dependencies'];
+      for (var package in deps.keys) {
+        _declaredPubDeps.add(PubDep.parse(package, deps[package]));
+      }
     } else {
       _declaredDependencies = {};
     }
+
+    _declaredDevPubDeps = [];
     if (docContents['dev_dependencies'] != null) {
       _declaredDevDependencies =
           Set<String>.from(docContents['dev_dependencies'].keys);
+
+      var deps = docContents['dev_dependencies'];
+      for (var package in deps.keys) {
+        _declaredDevPubDeps.add(PubDep.parse(package, deps[package]));
+      }
     } else {
       _declaredDevDependencies = {};
     }
   }
 
-  bool _validatePubspecDeps(Logger logger) {
+  bool _validatePubspecDeps(Logger logger, List<String> pkgPackages) {
     var fail = false;
 
     if (dirName != packageName) {
@@ -240,6 +266,47 @@
       fail = true;
     }
 
+    // Validate that we don't have relative deps into third_party.
+    // TODO(devoncarew): This is currently just enforced for publishable
+    // packages.
+    if (publishable) {
+      for (PubDep dep in [..._declaredPubDeps, ..._declaredDevPubDeps]) {
+        if (dep is PathPubDep) {
+          var path = dep.path;
+
+          if (path.contains('third_party/pkg_tested/') ||
+              path.contains('third_party/pkg/')) {
+            out('  Prefer a semver dependency for packages brought in via DEPS:');
+            out('    $dep');
+            fail = true;
+          }
+        }
+      }
+    }
+
+    // Validate that published packages don't use path deps.
+    if (publishable) {
+      for (PubDep dep in _declaredPubDeps) {
+        if (dep is PathPubDep) {
+          out('  Published packages should use semver deps:');
+          out('    $dep');
+          fail = true;
+        }
+      }
+    }
+
+    // Validate that non-published packages use relative a (relative) path dep
+    // for pkg/ packages.
+    if (!publishable) {
+      for (PubDep dep in [..._declaredPubDeps, ..._declaredDevPubDeps]) {
+        if (pkgPackages.contains(dep.name) && dep is! PathPubDep) {
+          out('  Prefer a relative path dep for pkg/ packages:');
+          out('    $dep');
+          fail = true;
+        }
+      }
+    }
+
     if (!fail) {
       print('  No issues.');
     }
@@ -361,3 +428,45 @@
     testedPkgs.sort();
   }
 }
+
+abstract class PubDep {
+  final String name;
+
+  PubDep(this.name);
+
+  String toString() => name;
+
+  static PubDep parse(String name, Object dep) {
+    if (dep is String) {
+      return SemverPubDep(name, dep);
+    } else if (dep is Map) {
+      if (dep.containsKey('path')) {
+        return PathPubDep(name, dep['path']);
+      } else {
+        return UnhandledPubDep(name);
+      }
+    } else {
+      return UnhandledPubDep(name);
+    }
+  }
+}
+
+class SemverPubDep extends PubDep {
+  final String value;
+
+  SemverPubDep(String name, this.value) : super(name);
+
+  String toString() => '$name: $value';
+}
+
+class PathPubDep extends PubDep {
+  final String path;
+
+  PathPubDep(String name, this.path) : super(name);
+
+  String toString() => '$name: $path';
+}
+
+class UnhandledPubDep extends PubDep {
+  UnhandledPubDep(String name) : super(name);
+}