コード例 #1
0
        /// <summary>
        /// Gets the set of search paths that were last saved for a database.
        /// </summary>
        /// <param name="databasePath">Path containing the database.</param>
        /// <returns>The cached list of search paths.</returns>
        /// <remarks>Added in 2.2</remarks>
        public static List <PythonLibraryPath> GetCachedDatabaseSearchPaths(string databasePath)
        {
            var cacheFile = Path.Combine(databasePath, "database.path");

            if (!File.Exists(cacheFile))
            {
                return(null);
            }
            if (!IsDatabaseVersionCurrent(databasePath))
            {
                // Cache file with no database is only valid for one hour
                try {
                    var time = File.GetLastWriteTimeUtc(cacheFile);
                    if (time.AddHours(1) < DateTime.UtcNow)
                    {
                        File.Delete(cacheFile);
                        return(null);
                    }
                } catch (IOException) {
                    return(null);
                }
            }


            try {
                var result = new List <PythonLibraryPath>();
                using (var file = File.OpenText(cacheFile)) {
                    string line;
                    while ((line = file.ReadLine()) != null)
                    {
                        try {
                            result.Add(PythonLibraryPath.Parse(line));
                        } catch (FormatException) {
                            Debug.Fail("Invalid format: " + line);
                        }
                    }
                }

                return(result);
            } catch (IOException) {
                return(null);
            }
        }
コード例 #2
0
ファイル: AnalyzerTests.cs プロジェクト: vyktor90/PTVS
        public async Task GetVirtualEnvDatabasePaths()
        {
            var version = PythonPaths.Python27 ?? PythonPaths.Python27_x64;

            version.AssertInstalled();

            var env = Path.Combine(TestData.GetTempPath(), "env");

            using (var p = ProcessOutput.RunHiddenAndCapture(version.InterpreterPath, "-m", "virtualenv", env)) {
                if ((await p) != 0)
                {
                    Assert.Fail("Could not create virtualenv{0}{1}{0}{2}",
                                Environment.NewLine,
                                string.Join(Environment.NewLine, p.StandardOutputLines),
                                string.Join(Environment.NewLine, p.StandardErrorLines)
                                );
                    return;
                }
            }

            var interpreter = Path.Combine(env, "Scripts", "python.exe");
            var paths       = await PythonLibraryPath.GetUncachedDatabaseSearchPathsAsync(interpreter);

            AssertUtil.ContainsAtLeast(
                paths.Where(p => p.IsStandardLibrary).Select(p => p.Path),
                new[] {
                Path.Combine(env, "Scripts").ToLowerInvariant(),
                PathUtils.TrimEndSeparator(env.ToLowerInvariant()),
                PathUtils.TrimEndSeparator(version.PrefixPath.ToLowerInvariant()),
                Path.Combine(version.PrefixPath, "Lib").ToLowerInvariant()
            }
                );
            AssertUtil.ContainsAtLeast(
                paths.Where(p => !p.IsStandardLibrary).Select(p => p.Path),
                new[] {
                Path.Combine(env, "Lib", "site-packages").ToLowerInvariant()
            }
                );
            AssertUtil.DoesntContain(
                paths.Select(p => p.Path),
                Path.Combine(version.PrefixPath, "Lib", "site-packages").ToLowerInvariant()
                );
        }
コード例 #3
0
        public void NormalizeUser()
        {
            var appPath = TestData.GetTestSpecificPath("app.py");
            var root    = Path.GetDirectoryName(appPath);

            var src = Path.Combine(root, "src");

            var fromUser = new[] {
                "./src/",
            };

            var(interpreterPaths, userPaths) = PythonLibraryPath.ClassifyPaths(root, _fs, Array.Empty <PythonLibraryPath>(), fromUser);

            interpreterPaths.Should().BeEmpty();

            userPaths.Should().BeEquivalentToWithStrictOrdering(new[] {
                new PythonLibraryPath(src, PythonLibraryPathType.Unspecified),
            });
        }
コード例 #4
0
        private async Task <ImmutableArray <PythonLibraryPath> > GetInterpreterSearchPathsAsync(CancellationToken cancellationToken = default)
        {
            if (!FileSystem.FileExists(Configuration.InterpreterPath))
            {
                Log?.Log(TraceEventType.Warning, "Interpreter does not exist:", Configuration.InterpreterPath);
                _ui?.ShowMessageAsync(Resources.InterpreterNotFound, TraceEventType.Error);
                return(ImmutableArray <PythonLibraryPath> .Empty);
            }

            Log?.Log(TraceEventType.Information, "GetCurrentSearchPaths", Configuration.InterpreterPath);
            try {
                var fs = Services.GetService <IFileSystem>();
                var ps = Services.GetService <IProcessServices>();
                return(await PythonLibraryPath.GetSearchPathsAsync(Configuration, fs, ps, cancellationToken));
            } catch (InvalidOperationException ex) {
                Log?.Log(TraceEventType.Warning, "Exception getting search paths", ex);
                _ui?.ShowMessageAsync(Resources.ExceptionGettingSearchPaths, TraceEventType.Error);
                return(ImmutableArray <PythonLibraryPath> .Empty);
            }
        }
コード例 #5
0
        protected virtual async Task <IReadOnlyList <string> > GetCurrentSearchPathsAsync()
        {
            if (Configuration.SearchPaths.Any())
            {
                return(Configuration.SearchPaths);
            }

            if (!File.Exists(Configuration.InterpreterPath))
            {
                return(Array.Empty <string>());
            }

            try {
                var paths = await PythonLibraryPath.GetDatabaseSearchPathsAsync(Configuration, _searchPathCachePath).ConfigureAwait(false);

                return(paths.MaybeEnumerate().Select(p => p.Path).ToArray());
            } catch (InvalidOperationException) {
                return(Array.Empty <string>());
            }
        }
コード例 #6
0
ファイル: DatabaseTest.cs プロジェクト: xNUTs/PTVS
        public async Task GetSearchPaths() {
            Python.AssertInstalled();

            var paths = await PythonTypeDatabase.GetUncachedDatabaseSearchPathsAsync(Python.InterpreterPath);
            Console.WriteLine("Paths for {0}", Python.InterpreterPath);
            foreach (var path in paths) {
                Console.WriteLine("{0} {1}", path.Path, path.IsStandardLibrary ? "(stdlib)" : "");
            }

            // Python.PrefixPath and LibraryPath should be included.
            // We can't assume anything else
            AssertUtil.ContainsAtLeast(paths.Select(p => p.Path.ToLowerInvariant().TrimEnd('\\')),
                Python.PrefixPath.ToLowerInvariant().TrimEnd('\\'),
                Python.LibPath.ToLowerInvariant().TrimEnd('\\')
            );
            
            // All paths should exist
            AssertUtil.ArrayEquals(paths.Where(p => !Directory.Exists(p.Path)).ToList(), new PythonLibraryPath[0]);

            // Ensure we can round-trip the entries via ToString/Parse
            var asStrings = paths.Select(p => p.ToString()).ToList();
            var asPaths = asStrings.Select(PythonLibraryPath.Parse).ToList();
            var asStrings2 = asPaths.Select(p => p.ToString()).ToList();
            AssertUtil.ArrayEquals(asStrings, asStrings2);
            AssertUtil.ArrayEquals(paths, asPaths, (o1, o2) => {
                PythonLibraryPath p1 = (PythonLibraryPath)o1, p2 = (PythonLibraryPath)o2;
                return p1.Path == p2.Path && p1.IsStandardLibrary == p2.IsStandardLibrary;
            });

            var dbPath = TestData.GetTempPath(randomSubPath: true);
            Assert.IsNull(PythonTypeDatabase.GetCachedDatabaseSearchPaths(dbPath),
                "Should not have found cached paths in an empty directory");

            PythonTypeDatabase.WriteDatabaseSearchPaths(dbPath, paths);
            Assert.IsTrue(File.Exists(Path.Combine(dbPath, "database.path")));
            var paths2 = PythonTypeDatabase.GetCachedDatabaseSearchPaths(dbPath);
            AssertUtil.ArrayEquals(paths, paths2, (o1, o2) => {
                PythonLibraryPath p1 = (PythonLibraryPath)o1, p2 = (PythonLibraryPath)o2;
                return p1.Path == p2.Path && p1.IsStandardLibrary == p2.IsStandardLibrary;
            });
        }
コード例 #7
0
ファイル: AnalyzerTests.cs プロジェクト: xyongy/PTVS
        public async Task GetDatabasePaths()
        {
            foreach (var version in PythonPaths.Versions)
            {
                var paths = await PythonLibraryPath.GetUncachedDatabaseSearchPathsAsync(version.InterpreterPath);

                AssertUtil.ContainsAtLeast(
                    paths.Where(p => p.IsStandardLibrary).Select(p => p.Path),
                    new[] {
                    PathUtils.TrimEndSeparator(version.PrefixPath.ToLowerInvariant()),
                    Path.Combine(version.PrefixPath, "Lib").ToLowerInvariant()
                }
                    );
                AssertUtil.ContainsAtLeast(
                    paths.Where(p => !p.IsStandardLibrary).Select(p => p.Path),
                    new[] {
                    Path.Combine(version.PrefixPath, "Lib", "site-packages").ToLowerInvariant()
                }
                    );
            }
        }
コード例 #8
0
        private async Task <IReadOnlyList <string> > GetCurrentSearchPathsAsync(CancellationToken cancellationToken)
        {
            if (_configuration.SearchPaths.Any())
            {
                return(_configuration.SearchPaths);
            }
            if (!File.Exists(_configuration.InterpreterPath))
            {
                return(Array.Empty <string>());
            }

            _log?.Log(TraceLevel.Info, "GetCurrentSearchPaths", _configuration.InterpreterPath, _moduleCache.SearchPathCachePath);
            try {
                var paths = await PythonLibraryPath.GetDatabaseSearchPathsAsync(_configuration, _moduleCache.SearchPathCachePath).ConfigureAwait(false);

                cancellationToken.ThrowIfCancellationRequested();
                return(paths.MaybeEnumerate().Select(p => p.Path).ToArray());
            } catch (InvalidOperationException) {
                return(Array.Empty <string>());
            }
        }
コード例 #9
0
        private string[] GetMissingModules(HashSet<string> existingDatabase) {
            Debug.Assert(!string.IsNullOrEmpty(DatabasePath));

            var searchPaths = PythonLibraryPath.GetCachedDatabaseSearchPaths(Path.Combine(DatabasePath, "database.path"));

            if (searchPaths == null) {
                // No cached search paths means our database is out of date.
                return existingDatabase
                    .Except(RequiredBuiltinModules)
                    .OrderBy(name => name, StringComparer.InvariantCultureIgnoreCase)
                    .ToArray();
            }
            
            return PythonTypeDatabase.GetDatabaseExpectedModules(Configuration.Version, searchPaths)
                .SelectMany()
                .Select(mp => mp.ModuleName)
                .Concat(RequiredBuiltinModules)
                .Where(m => !existingDatabase.Contains(m))
                .OrderBy(name => name, StringComparer.InvariantCultureIgnoreCase)
                .ToArray();
        }
コード例 #10
0
        /// <summary>
        /// Gets the set of search paths that were last saved for a database.
        /// </summary>
        /// <param name="databasePath">Path containing the database.</param>
        /// <returns>The cached list of search paths.</returns>
        /// <remarks>Added in 2.2</remarks>
        public static List <PythonLibraryPath> GetCachedDatabaseSearchPaths(string databasePath)
        {
            try {
                var result = new List <PythonLibraryPath>();

                using (var file = File.OpenText(Path.Combine(databasePath, "database.path"))) {
                    string line;
                    while ((line = file.ReadLine()) != null)
                    {
                        try {
                            result.Add(PythonLibraryPath.Parse(line));
                        } catch (FormatException) {
                            Debug.Fail("Invalid format: " + line);
                        }
                    }
                }

                return(result);
            } catch (IOException) {
                return(null);
            }
        }
コード例 #11
0
        public void InsideStdLib()
        {
            var appPath = TestData.GetTestSpecificPath("app.py");
            var root    = Path.GetDirectoryName(appPath);

            var venv             = Path.Combine(root, "venv");
            var venvLib          = Path.Combine(venv, "Lib");
            var venvSitePackages = Path.Combine(venvLib, "site-packages");
            var inside           = Path.Combine(venvSitePackages, "inside");
            var what             = Path.Combine(venvSitePackages, "what");

            var src = Path.Combine(root, "src");

            var fromInterpreter = new[] {
                new PythonLibraryPath(venvLib, PythonLibraryPathType.StdLib),
                new PythonLibraryPath(venv, PythonLibraryPathType.StdLib),
                new PythonLibraryPath(venvSitePackages, PythonLibraryPathType.Site),
                new PythonLibraryPath(inside, PythonLibraryPathType.Pth),
            };

            var fromUser = new[] {
                "./src",
                "./venv/Lib/site-packages/what",
            };

            var(interpreterPaths, userPaths) = PythonLibraryPath.ClassifyPaths(root, _fs, fromInterpreter, fromUser);

            interpreterPaths.Should().BeEquivalentToWithStrictOrdering(new[] {
                new PythonLibraryPath(venvLib, PythonLibraryPathType.StdLib),
                new PythonLibraryPath(venv, PythonLibraryPathType.StdLib),
                new PythonLibraryPath(what, PythonLibraryPathType.Unspecified),
                new PythonLibraryPath(venvSitePackages, PythonLibraryPathType.Site),
                new PythonLibraryPath(inside, PythonLibraryPathType.Pth),
            });

            userPaths.Should().BeEquivalentToWithStrictOrdering(new[] {
                new PythonLibraryPath(src, PythonLibraryPathType.Unspecified),
            });
        }
コード例 #12
0
        internal async Task ReloadSearchPaths(CancellationToken cancellationToken = default)
        {
            var ps = _services.GetService <IProcessServices>();

            var paths = await GetInterpreterSearchPathsAsync(cancellationToken);

            var(interpreterPaths, userPaths) = PythonLibraryPath.ClassifyPaths(Root, _fs, paths, Configuration.SearchPaths);

            InterpreterPaths = interpreterPaths.Select(p => p.Path);
            _userPaths       = userPaths.Select(p => p.Path);

            _log?.Log(TraceEventType.Information, "Interpreter search paths:");
            foreach (var s in InterpreterPaths)
            {
                _log?.Log(TraceEventType.Information, $"    {s}");
            }

            _log?.Log(TraceEventType.Information, "User search paths:");
            foreach (var s in _userPaths)
            {
                _log?.Log(TraceEventType.Information, $"    {s}");
            }
        }
コード例 #13
0
        private async Task <IReadOnlyList <PythonLibraryPath> > GetInterpreterSearchPathsAsync(CancellationToken cancellationToken = default)
        {
            if (!_fs.FileExists(Configuration.InterpreterPath))
            {
                _log?.Log(TraceEventType.Warning, "Interpreter does not exist:", Configuration.InterpreterPath);
                _ui?.ShowMessageAsync(Resources.InterpreterNotFound, TraceEventType.Error);
                return(Array.Empty <PythonLibraryPath>());
            }

            _log?.Log(TraceEventType.Information, "GetCurrentSearchPaths", Configuration.InterpreterPath);
            try {
                var fs    = _services.GetService <IFileSystem>();
                var ps    = _services.GetService <IProcessServices>();
                var paths = await PythonLibraryPath.GetSearchPathsAsync(Configuration, fs, ps, cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();
                return(paths.ToArray());
            } catch (InvalidOperationException ex) {
                _log?.Log(TraceEventType.Warning, "Exception getting search paths", ex);
                _ui?.ShowMessageAsync(Resources.ExceptionGettingSearchPaths, TraceEventType.Error);
                return(Array.Empty <PythonLibraryPath>());
            }
        }
コード例 #14
0
        private async Task ReloadSearchPaths(CancellationToken cancellationToken = default)
        {
            LibraryPaths = await GetInterpreterSearchPathsAsync(cancellationToken);

            var(interpreterPaths, userPaths) = PythonLibraryPath.ClassifyPaths(Root, FileSystem, LibraryPaths, _userConfiguredPaths);

            InterpreterPaths = interpreterPaths.Select(p => p.Path);
            UserPaths        = userPaths.Select(p => p.Path);

            if (Log != null)
            {
                Log.Log(TraceEventType.Information, "Interpreter search paths:");
                foreach (var s in InterpreterPaths)
                {
                    Log.Log(TraceEventType.Information, $"    {s}");
                }

                Log.Log(TraceEventType.Information, "User search paths:");
                foreach (var s in UserPaths)
                {
                    Log.Log(TraceEventType.Information, $"    {s}");
                }
            }
        }
コード例 #15
0
        /// <summary>
        /// Gets the set of search paths by running the interpreter.
        /// </summary>
        /// <param name="interpreter">Path to the interpreter.</param>
        /// <returns>A list of search paths for the interpreter.</returns>
        /// <remarks>Added in 2.2</remarks>
        public static async Task <List <PythonLibraryPath> > GetUncachedDatabaseSearchPathsAsync(string interpreter)
        {
            List <string> lines;

            // sys.path will include the working directory, so we make an empty
            // path that we can filter out later
            var tempWorkingDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

            Directory.CreateDirectory(tempWorkingDir);
            var srcGetSearchPaths = PythonToolsInstallPath.GetFile("get_search_paths.py", typeof(PythonTypeDatabase).Assembly);
            var getSearchPaths    = PathUtils.GetAbsoluteFilePath(tempWorkingDir, PathUtils.GetFileOrDirectoryName(srcGetSearchPaths));

            File.Copy(srcGetSearchPaths, getSearchPaths);

            try {
                using (var proc = ProcessOutput.Run(
                           interpreter,
                           new[] {
                    "-S",       // don't import site - we do that in code
                    "-E",       // ignore environment
                    getSearchPaths
                },
                           tempWorkingDir,
                           null,
                           false,
                           null
                           )) {
                    int exitCode = -1;
                    try {
                        exitCode = await proc;
                    } catch (OperationCanceledException) {
                    }
                    if (exitCode != 0)
                    {
                        throw new InvalidOperationException(string.Format(
                                                                "Cannot obtain list of paths{0}{1}",
                                                                Environment.NewLine,
                                                                string.Join(Environment.NewLine, proc.StandardErrorLines))
                                                            );
                    }

                    lines = proc.StandardOutputLines.ToList();
                }
            } finally {
                try {
                    Directory.Delete(tempWorkingDir, true);
                } catch (Exception ex) {
                    if (ex.IsCriticalException())
                    {
                        throw;
                    }
                }
            }

            return(lines.Select(s => {
                if (s.StartsWith(tempWorkingDir, StringComparison.OrdinalIgnoreCase))
                {
                    return null;
                }
                try {
                    return PythonLibraryPath.Parse(s);
                } catch (FormatException) {
                    Debug.Fail("Invalid format for search path: " + s);
                    return null;
                }
            }).Where(p => p != null).ToList());
        }
コード例 #16
0
ファイル: PipPackageManager.cs プロジェクト: int19h/PTVS
        private async Task CreateLibraryWatchers()
        {
            Debug.Assert(_libWatchers != null, "Should not create watchers when suppressed");

            IReadOnlyList <string> paths = null;

            if (_factory.Configuration.SearchPaths.Any())
            {
                paths = _factory.Configuration.SearchPaths;
            }

            if (paths == null)
            {
                try {
                    paths = (await PythonLibraryPath.GetSearchPathsAsync(_factory.Configuration, new FileSystem(new OSPlatform()), new ProcessServices()))
                            .Select(p => p.Path)
                            .ToArray();
                } catch (InvalidOperationException) {
                    return;
                }
            }

            paths = paths
                    .Where(Directory.Exists)
                    .OrderBy(p => p.Length)
                    .ToList();

            var watching = new List <string>();
            var watchers = new List <FileSystemWatcher>();

            foreach (var path in paths)
            {
                if (watching.Any(p => PathUtils.IsSubpathOf(p, path)))
                {
                    continue;
                }

                FileSystemWatcher watcher = null;
                try {
                    watcher = new FileSystemWatcher {
                        IncludeSubdirectories = true,
                        Path         = path,
                        NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite
                    };
                    watcher.Created            += OnChanged;
                    watcher.Deleted            += OnChanged;
                    watcher.Changed            += OnChanged;
                    watcher.Renamed            += OnRenamed;
                    watcher.EnableRaisingEvents = true;

                    watching.Add(path);
                    watchers.Add(watcher);
                } catch (IOException) {
                    // Raced with directory deletion. We normally handle the
                    // library being deleted by disposing the watcher, but this
                    // occurs in response to an event from the watcher. Because
                    // we never got to start watching, we will just dispose
                    // immediately.
                    watcher?.Dispose();
                } catch (ArgumentException ex) {
                    watcher?.Dispose();
                    Debug.WriteLine("Error starting FileSystemWatcher:\r\n{0}", ex);
                }
            }

            List <FileSystemWatcher> oldWatchers;

            lock (_libWatchers) {
                oldWatchers = _libWatchers.ToList();
                _libWatchers.Clear();
                _libWatchers.AddRange(watchers);
            }

            foreach (var oldWatcher in oldWatchers)
            {
                oldWatcher.EnableRaisingEvents = false;
                oldWatcher.Dispose();
            }
        }
コード例 #17
0
ファイル: PipPackageManager.cs プロジェクト: int19h/PTVS
        private async Task CacheInstalledPackagesAsync(
            bool alreadyHasLock,
            bool alreadyHasConcurrencyLock,
            CancellationToken cancellationToken
            )
        {
            if (!IsReady)
            {
                await UpdateIsReadyAsync(alreadyHasLock, cancellationToken);

                if (!IsReady)
                {
                    return;
                }
            }

            List <PackageSpec> packages = null;

            var workingLock = alreadyHasLock ? null : await _working.LockAsync(cancellationToken);

            try {
                var args = _pipListHasFormatOption ? _commands.ListJson() : _commands.List();

                var concurrencyLock = alreadyHasConcurrencyLock ? null : await _concurrencyLock.LockAsync(cancellationToken);

                try {
                    var envVars = await GetEnvironmentVariables();

                    using (var proc = ProcessOutput.Run(
                               _factory.Configuration.InterpreterPath,
                               args,
                               _factory.Configuration.GetPrefixPath(),
                               envVars,
                               false,
                               null
                               )) {
                        try {
                            if ((await proc) == 0)
                            {
                                if (_pipListHasFormatOption)
                                {
                                    try {
                                        var data = JToken.ReadFrom(new JsonTextReader(new StringListReader(proc.StandardOutputLines)));
                                        packages = data
                                                   .Select(j => new PackageSpec(j.Value <string>("name"), j.Value <string>("version")))
                                                   .Where(p => p.IsValid)
                                                   .OrderBy(p => p.Name)
                                                   .ToList();
                                    } catch (JsonException ex) {
                                        Debug.WriteLine("Failed to parse: {0}".FormatInvariant(ex.Message));
                                        foreach (var l in proc.StandardOutputLines)
                                        {
                                            Debug.WriteLine(l);
                                        }
                                    }
                                }
                                else
                                {
                                    packages = proc.StandardOutputLines
                                               .Select(i => PackageSpec.FromPipList(i))
                                               .Where(p => p.IsValid)
                                               .OrderBy(p => p.Name)
                                               .ToList();
                                }
                            }
                            else if (_pipListHasFormatOption)
                            {
                                // Actually, pip probably doesn't have the --format option
                                Debug.WriteLine("{0} does not support --format".FormatInvariant(_factory.Configuration.InterpreterPath));
                                _pipListHasFormatOption = false;
                                await CacheInstalledPackagesAsync(true, true, cancellationToken);

                                return;
                            }
                            else
                            {
                            }
                        } catch (OperationCanceledException) {
                            // Process failed to run
                            Debug.WriteLine("Failed to run pip to collect packages");
                            foreach (var line in proc.StandardOutputLines)
                            {
                                Debug.WriteLine(line);
                            }
                        }
                    }

                    if (packages == null)
                    {
                        // Pip failed, so return a directory listing
                        var paths = await PythonLibraryPath.GetSearchPathsAsync(
                            _factory.Configuration,
                            new FileSystem(new OSPlatform()),
                            new ProcessServices()
                            );

                        packages = await Task.Run(() => paths.Where(p => p.Type != PythonLibraryPathType.StdLib && Directory.Exists(p.Path))
                                                  .SelectMany(p => PathUtils.EnumerateDirectories(p.Path, recurse: false))
                                                  .Select(path => Path.GetFileName(path))
                                                  .Select(name => PackageNameRegex.Match(name))
                                                  .Where(match => match.Success)
                                                  .Select(match => new PackageSpec(match.Groups["name"].Value))
                                                  .Where(p => p.IsValid)
                                                  .OrderBy(p => p.Name)
                                                  .ToList());
                    }
                } finally {
                    concurrencyLock?.Dispose();
                }

                // Outside of concurrency lock, still in working lock

                _packages.Clear();
                _packages.AddRange(packages);
                _everCached = true;
            } finally {
                workingLock?.Dispose();
            }

            InstalledPackagesChanged?.Invoke(this, EventArgs.Empty);
            _factory.NotifyImportNamesChanged();
        }