diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index dea568fd5300fea86b3537cf7e78c96b48cb7680..3e491a93a3954bc935116af4c4ded23c436ebb43 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -9,6 +9,7 @@ import 'log.dart' as log;
 import 'io.dart';
 import 'system_cache.dart';
 import 'utils.dart';
+import 'validator/lib.dart';
 import 'validator/license.dart';
 import 'validator/name.dart';
 import 'validator/pubspec_field.dart';
@@ -40,6 +41,7 @@ abstract class Validator {
   static Future<Pair<List<String>, List<String>>> runAll(
       Entrypoint entrypoint) {
     var validators = [
+      new LibValidator(entrypoint),
       new LicenseValidator(entrypoint),
       new NameValidator(entrypoint),
       new PubspecFieldValidator(entrypoint)
diff --git a/lib/src/validator/lib.dart b/lib/src/validator/lib.dart
new file mode 100644
index 0000000000000000000000000000000000000000..64f44c6365f5694c8468c8b73d513a0a171a7416
--- /dev/null
+++ b/lib/src/validator/lib.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library lib_validator;
+
+import 'dart:io';
+
+import '../entrypoint.dart';
+import '../io.dart';
+import '../system_cache.dart';
+import '../utils.dart';
+import '../validator.dart';
+
+// TODO(nweiz): When issue 7196 is fixed, complain about non-Dart files in lib.
+/// A validator that checks that libraries in "lib/" (and not "lib/src/") exist
+/// and are well-formed.
+class LibValidator extends Validator {
+  LibValidator(Entrypoint entrypoint)
+    : super(entrypoint);
+
+  Future validate() {
+    var libDir = join(entrypoint.root.dir, "lib");
+    return dirExists(libDir).chain((libDirExists) {
+      if (!libDirExists) {
+        errors.add('Your package must have a "lib/" directory so users have '
+            'something to import.');
+        return new Future.immediate(null);
+      }
+
+      return listDir(libDir).transform((files) {
+        files = files.map((file) => relativeTo(file, libDir));
+        if (files.isEmpty) {
+          errors.add('The "lib/" directory may not be empty so users have '
+              'something to import');
+        } else if (files.length == 1 && files.first == "src") {
+          errors.add('The "lib/" directory must contain something other than '
+              '"src/" so users have something to import');
+        }
+      });
+    });
+  }
+}
diff --git a/test/oauth2_test.dart b/test/oauth2_test.dart
index fdbf92bd341d2378e11dd2be7be2065890d718a0..32e4a75a729b647d628df36057dccc226a2699a5 100644
--- a/test/oauth2_test.dart
+++ b/test/oauth2_test.dart
@@ -15,12 +15,7 @@ import '../../pub/io.dart';
 import '../../pub/utils.dart';
 
 main() {
-  setUp(() {
-    dir(appPath, [
-      libPubspec("test_pkg", "1.0.0"),
-      file("LICENSE", "Eh, do what you want.")
-    ]).scheduleCreate();
-  });
+  setUp(() => normalPackage.scheduleCreate());
 
   test('with no credentials.json, authenticates and saves credentials.json',
       () {
diff --git a/test/pub_lish_test.dart b/test/pub_lish_test.dart
index 58f6a1983ed7f4008f9c3206de1d791237a669b4..a947601d772229b92df61feea78b226ddf001fcb 100644
--- a/test/pub_lish_test.dart
+++ b/test/pub_lish_test.dart
@@ -47,12 +47,7 @@ void handleUpload(ScheduledServer server) {
 }
 
 main() {
-  setUp(() {
-    dir(appPath, [
-      libPubspec("test_pkg", "1.0.0"),
-      file("LICENSE", "Eh, do what you want.")
-    ]).scheduleCreate();
-  });
+  setUp(() => normalPackage.scheduleCreate());
 
   test('archives and uploads a package', () {
     var server = new ScheduledServer();
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 806ce3ee34d0a5a6cb5f46083b7e467d105ef267..aa8e8797bdfb22776be4e989e8ee260b518b0744 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -222,6 +222,15 @@ void servePackages(List<Map> pubspecs) {
 /** Converts [value] into a YAML string. */
 String yaml(value) => JSON.stringify(value);
 
+/// Describes a package that passes all validation.
+Descriptor get normalPackage => dir(appPath, [
+  libPubspec("test_pkg", "1.0.0"),
+  file("LICENSE", "Eh, do what you want."),
+  dir("lib", [
+    file("test_pkg.dart", "int i = 1;")
+  ])
+]);
+
 /**
  * Describes a file named `pubspec.yaml` with the given YAML-serialized
  * [contents], which should be a serializable object.
diff --git a/test/validator_test.dart b/test/validator_test.dart
index e5005bfb9627f7dccfef0c127c0cec3cad8b905b..d9877eb45de9c678b220df0939f5f386b3590d78 100644
--- a/test/validator_test.dart
+++ b/test/validator_test.dart
@@ -12,6 +12,7 @@ import '../../../pkg/unittest/lib/unittest.dart';
 import '../../pub/entrypoint.dart';
 import '../../pub/io.dart';
 import '../../pub/validator.dart';
+import '../../pub/validator/lib.dart';
 import '../../pub/validator/license.dart';
 import '../../pub/validator/name.dart';
 import '../../pub/validator/pubspec_field.dart';
@@ -28,19 +29,16 @@ void expectValidationWarning(ValidatorCreator fn) {
   expectLater(schedulePackageValidation(fn), pairOf(isEmpty, isNot(isEmpty)));
 }
 
-Validator pubspecField(Entrypoint entrypoint) =>
-  new PubspecFieldValidator(entrypoint);
+Validator lib(Entrypoint entrypoint) => new LibValidator(entrypoint);
 
 Validator license(Entrypoint entrypoint) => new LicenseValidator(entrypoint);
 
 Validator name(Entrypoint entrypoint) => new NameValidator(entrypoint);
 
-void scheduleNormalPackage() {
-  dir(appPath, [
-    libPubspec("test_pkg", "1.0.0"),
-    file("LICENSE", "Eh, do what you want.")
-  ]).scheduleCreate();
-}
+Validator pubspecField(Entrypoint entrypoint) =>
+  new PubspecFieldValidator(entrypoint);
+
+void scheduleNormalPackage() => normalPackage.scheduleCreate();
 
 main() {
   group('should consider a package valid if it', () {
@@ -93,6 +91,17 @@ main() {
       expectNoValidationError(name);
       run();
     });
+
+    test('has a non-Dart file in lib', () {
+      dir(appPath, [
+        libPubspec("test_pkg", "1.0.0"),
+        dir("lib", [
+          file("thing.txt", "woo hoo")
+        ])
+      ]).scheduleCreate();
+      expectNoValidationError(lib);
+      run();
+    });
   });
 
   group('should consider a package invalid if it', () {
@@ -242,5 +251,28 @@ main() {
       expectValidationError(name);
       run();
     });
+
+    test('has no lib directory', () {
+      dir(join(appPath, "lib")).scheduleDelete();
+      expectValidationError(lib);
+      run();
+    });
+
+    test('has an empty lib directory', () {
+      file(join(appPath, "lib", "test_pkg.dart"), '').scheduleDelete();
+      expectValidationError(lib);
+      run();
+    });
+
+    test('has a lib directory containing only src', () {
+      file(join(appPath, "lib", "test_pkg.dart"), '').scheduleDelete();
+      dir(appPath, [
+        dir("lib", [
+          dir("src", [file("test_pkg.dart", "int i = 0;")])
+        ])
+      ]).scheduleCreate();
+      expectValidationError(lib);
+      run();
+    });
   });
 }