diff --git a/bin/pub.dart b/bin/pub.dart index ab95fc6ecf0ccf45d3a42eb5ff68f67d6f32f997..35b8c90ef63e1d6a326d49c14ea34cd61e71c93b 100644 --- a/bin/pub.dart +++ b/bin/pub.dart @@ -173,7 +173,7 @@ Future invokeCommand(String cacheDir, ArgResults mainOptions) { } return syncFuture(() { - return command.run(cacheDir, options); + return command.run(cacheDir, mainOptions, options); }).whenComplete(() { command.cache.deleteTempDir(); }); diff --git a/lib/src/command.dart b/lib/src/command.dart index 0b3e985bb2ba83de6dedacc646630903088e1367..d70bf1733ef3ed52ef1c563ae8af384e948be04a 100644 --- a/lib/src/command.dart +++ b/lib/src/command.dart @@ -105,6 +105,10 @@ abstract class PubCommand { GlobalPackages get globals => _globals; GlobalPackages _globals; + /// The parsed options for the pub executable. + ArgResults get globalOptions => _globalOptions; + ArgResults _globalOptions; + /// The parsed options for this command. ArgResults get commandOptions => _commandOptions; ArgResults _commandOptions; @@ -115,7 +119,10 @@ abstract class PubCommand { /// is not a package. Entrypoint get entrypoint { // Lazy load it. - if (_entrypoint == null) _entrypoint = new Entrypoint(path.current, _cache); + if (_entrypoint == null) { + _entrypoint = new Entrypoint(path.current, _cache, + packageSymlinks: globalOptions['package-symlinks']); + } return _entrypoint; } @@ -181,8 +188,10 @@ abstract class PubCommand { help: 'Print usage information for this command.'); } - /// Runs this command using a system cache at [cacheDir] with [options]. - Future run(String cacheDir, ArgResults options) { + /// Runs this command using a system cache at [cacheDir] with [globalOptions] + /// and [options]. + Future run(String cacheDir, ArgResults globalOptions, ArgResults options) { + _globalOptions = globalOptions; _commandOptions = options; _cache = new SystemCache.withSources(cacheDir, isOffline: isOffline); @@ -305,6 +314,8 @@ ArgParser _initArgParser() { help: 'Shortcut for "--verbosity=all".'); argParser.addFlag('with-prejudice', hide: !isAprilFools, negatable: false, help: 'Execute commands with prejudice.'); + argParser.addFlag('package-symlinks', hide: true, negatable: true, + defaultsTo: true); // Register the commands. PubCommand.mainCommands.forEach((name, command) { diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 6a9f6076bdb37ed5f7c1348b9fde2b850715ea08..6ce137d954bd6d38636fca3e9fc6ee178e174c8e 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -43,18 +43,28 @@ class Entrypoint { /// the network. final SystemCache cache; + /// Whether to create and symlink a "packages" directory containing links to + /// the installed packages. + final bool _packageSymlinks; + /// The lockfile for the entrypoint. /// /// If not provided to the entrypoint, it will be laoded lazily from disc. LockFile _lockFile; /// Loads the entrypoint from a package at [rootDir]. - Entrypoint(String rootDir, SystemCache cache) + /// + /// If [packageSymlinks] is `true`, this will create a "packages" directory + /// with symlinks to the installed packages. This directory will be symlinked + /// into any directory that might contain an entrypoint. + Entrypoint(String rootDir, SystemCache cache, {bool packageSymlinks: true}) : root = new Package.load(null, rootDir, cache.sources), - cache = cache; + cache = cache, + _packageSymlinks = packageSymlinks; /// Creates an entrypoint given package and lockfile objects. - Entrypoint.inMemory(this.root, this._lockFile, this.cache); + Entrypoint.inMemory(this.root, this._lockFile, this.cache) + : _packageSymlinks = false; /// The path to the entrypoint's "packages" directory. String get packagesDir => path.join(root.dir, 'packages'); @@ -109,12 +119,19 @@ class Entrypoint { return null; } - // Install the packages. - cleanDir(packagesDir); + // Install the packages and maybe link them into the entrypoint. + if (_packageSymlinks) { + cleanDir(packagesDir); + } else { + deleteEntry(packagesDir); + } + return Future.wait(result.packages.map(_get)).then((ids) { _saveLockFile(ids); - _linkSelf(); - _linkSecondaryPackageDirs(); + + if (_packageSymlinks) _linkSelf(); + _linkOrDeleteSecondaryPackageDirs(); + result.summarizeChanges(type, dryRun: dryRun); }); }); @@ -128,11 +145,17 @@ class Entrypoint { Future<PackageId> _get(PackageId id) { if (id.isRoot) return new Future.value(id); - var packageDir = path.join(packagesDir, id.name); - if (entryExists(packageDir)) deleteEntry(packageDir); - var source = cache.sources[id.source]; - return source.get(id, packageDir).then((_) => source.resolveId(id)); + return syncFuture(() { + if (!_packageSymlinks) { + if (source is! CachedSource) return null; + return source.downloadToSystemCache(id); + } + + var packageDir = path.join(packagesDir, id.name); + if (entryExists(packageDir)) deleteEntry(packageDir); + return source.get(id, packageDir); + }).then((_) => source.resolveId(id)); } /// Determines whether or not the lockfile is out of date with respect to the @@ -257,28 +280,34 @@ class Entrypoint { isSelfLink: true, relative: true); } - /// Add "packages" directories to the whitelist of directories that may - /// contain Dart entrypoints. - void _linkSecondaryPackageDirs() { + /// If [packageSymlinks] is true, add "packages" directories to the whitelist + /// of directories that may contain Dart entrypoints. + /// + /// Otherwise, delete any "packages" directories in the whitelist of + /// directories that may contain Dart entrypoints. + void _linkOrDeleteSecondaryPackageDirs() { // Only the main "bin" directory gets a "packages" directory, not its // subdirectories. var binDir = path.join(root.dir, 'bin'); - if (dirExists(binDir)) _linkSecondaryPackageDir(binDir); + if (dirExists(binDir)) _linkOrDeleteSecondaryPackageDir(binDir); // The others get "packages" directories in subdirectories too. for (var dir in ['benchmark', 'example', 'test', 'tool', 'web']) { - _linkSecondaryPackageDirsRecursively(path.join(root.dir, dir)); + _linkOrDeleteSecondaryPackageDirsRecursively(path.join(root.dir, dir)); } } - /// Creates a symlink to the `packages` directory in [dir] and all its + /// If [packageSymlinks] is true, creates a symlink to the "packages" + /// directory in [dir] and all its subdirectories. + /// + /// Otherwise, deletes any "packages" directories in [dir] and all its /// subdirectories. - void _linkSecondaryPackageDirsRecursively(String dir) { + void _linkOrDeleteSecondaryPackageDirsRecursively(String dir) { if (!dirExists(dir)) return; - _linkSecondaryPackageDir(dir); + _linkOrDeleteSecondaryPackageDir(dir); _listDirWithoutPackages(dir) .where(dirExists) - .forEach(_linkSecondaryPackageDir); + .forEach(_linkOrDeleteSecondaryPackageDir); } // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed. @@ -294,11 +323,13 @@ class Entrypoint { })); } - /// Creates a symlink to the `packages` directory in [dir]. Will replace one - /// if already there. - void _linkSecondaryPackageDir(String dir) { + /// If [packageSymlinks] is true, creates a symlink to the "packages" + /// directory in [dir]. + /// + /// Otherwise, deletes a "packages" directories in [dir] if one exists. + void _linkOrDeleteSecondaryPackageDir(String dir) { var symlink = path.join(dir, 'packages'); if (entryExists(symlink)) deleteEntry(symlink); - createSymlink(packagesDir, symlink, relative: true); + if (_packageSymlinks) createSymlink(packagesDir, symlink, relative: true); } } diff --git a/test/no_package_symlinks_test.dart b/test/no_package_symlinks_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..12d51514569244f30441556ba3b903dea8f8419e --- /dev/null +++ b/test/no_package_symlinks_test.dart @@ -0,0 +1,115 @@ +// Copyright (c) 2014, 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. + +import 'package:scheduled_test/scheduled_test.dart'; + +import 'descriptor.dart' as d; +import 'test_pub.dart'; + +main() { + initConfig(); + + forBothPubGetAndUpgrade((command) { + group("with --no-package-symlinks", () { + integration("installs hosted dependencies to the cache", () { + servePackages([ + packageMap("foo", "1.0.0"), + packageMap("bar", "1.0.0") + ]); + + d.appDir({"foo": "any", "bar": "any"}).create(); + + pubCommand(command, args: ["--no-package-symlinks"]); + + d.nothing("$appPath/packages").validate(); + + d.hostedCache([ + d.dir("foo-1.0.0", [ + d.dir("lib", [d.file("foo.dart", 'main() => "foo 1.0.0";')]) + ]), + d.dir("bar-1.0.0", [ + d.dir("lib", [d.file("bar.dart", 'main() => "bar 1.0.0";')]) + ]) + ]).validate(); + }); + + integration("installs git dependencies to the cache", () { + ensureGit(); + + d.git('foo.git', [ + d.libDir('foo'), + d.libPubspec('foo', '1.0.0') + ]).create(); + + d.appDir({"foo": {"git": "../foo.git"}}).create(); + + pubCommand(command, args: ["--no-package-symlinks"]); + + d.nothing("$appPath/packages").validate(); + + d.dir(cachePath, [ + d.dir('git', [ + d.dir('cache', [d.gitPackageRepoCacheDir('foo')]), + d.gitPackageRevisionCacheDir('foo') + ]) + ]).validate(); + }); + + integration("locks path dependencies", () { + d.dir("foo", [ + d.libDir("foo"), + d.libPubspec("foo", "0.0.1") + ]).create(); + + d.dir(appPath, [ + d.appPubspec({ + "foo": {"path": "../foo"} + }) + ]).create(); + + pubCommand(command, args: ["--no-package-symlinks"]); + + d.nothing("$appPath/packages").validate(); + d.matcherFile("$appPath/pubspec.lock", contains("foo")); + }); + + integration("removes package directories near entrypoints", () { + d.dir(appPath, [ + d.appPubspec(), + d.dir("packages"), + d.dir("bin/packages"), + d.dir("web/packages"), + d.dir("web/subdir/packages") + ]).create(); + + pubCommand(command, args: ["--no-package-symlinks"]); + + d.dir(appPath, [ + d.nothing("packages"), + d.nothing("bin/packages"), + d.nothing("web/packages"), + d.nothing("web/subdir/packages") + ]).validate(); + }); + + integration("doesn't remove package directories that pub wouldn't " + "generate", () { + d.dir(appPath, [ + d.appPubspec(), + d.dir("packages"), + d.dir("bin/subdir/packages"), + d.dir("lib/packages") + ]).create(); + + pubCommand(command, args: ["--no-package-symlinks"]); + + d.dir(appPath, [ + d.nothing("packages"), + d.dir("bin/subdir/packages"), + d.dir("lib/packages") + ]).validate(); + }); + }); + }); +}