Skip to content

Commit

Permalink
iDelay resolving patterns to existing versions until all final versio…
Browse files Browse the repository at this point in the history
…ns are known. Fixes yarnpkg#3466
  • Loading branch information
blexrob committed May 22, 2017
1 parent 67fe99d commit b17d5f6
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 5 deletions.
11 changes: 11 additions & 0 deletions __tests__/commands/install/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,3 +757,14 @@ test.concurrent('bailout should work with --production flag too', (): Promise<vo
expect(await fs.exists(path.join(config.cwd, 'node_modules', 'left-pad', 'index.js'))).toBe(false);
});
});

test.concurrent('package version resolve should be deterministic', (): Promise<void> => {
// Scenario:
// graceful-fs will install two versions, from @4.1.10 and @^4.1.11. The pattern @^4.1.2 would sometimes resolve
// to 4.1.10, if @^4.1.11 hadn't been processed before. Otherwise it would resolve to the result of @^4.1.11.
// Run an independent install and check, and see they have different results for @^4.1.2 - won't always see
// the bug, but its the best we can do without creating mock registry with controlled timing of responses.
return runInstall({}, 'install-deterministic-versions', async (config, reporter) => {
await check(config, reporter, {integrity: true}, []);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": {
"graceful-fs": "4.1.10",
"write-file-atomic": "1.3.1",
"path-type": "1.1.0"
}
}
3 changes: 3 additions & 0 deletions src/cli/commands/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ class ImportPackageResolver extends PackageResolver {
deps = this.next;
this.next = [];
if (!deps.length) {
// all required package versions have been discovered, so now packages that
// resolved to existing versions can be resolved to their best available version
this.resolvePackagesWithExistingVersions();
return;
}
await this.findAll(deps);
Expand Down
26 changes: 21 additions & 5 deletions src/package-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default class PackageRequest {
this.optional = req.optional;
this.pattern = req.pattern;
this.config = resolver.config;
this.foundInfo = null;

resolver.usedRegistries.add(req.registry);
}
Expand All @@ -54,6 +55,7 @@ export default class PackageRequest {
config: Config;
registry: ResolverRegistryNames;
optional: boolean;
foundInfo: ?Manifest;

getParentNames(): Array<string> {
const chain = [];
Expand Down Expand Up @@ -226,6 +228,24 @@ export default class PackageRequest {

reportResolvedRangeMatch(info: Manifest, resolved: Manifest) {}

/**
* Do the final resolve of a package that had a match with an existing version.
* After all unique versions have been discovered, so the best available version
* is found.
*/
resolveToExistingVersion(info: Manifest) {
// get final resolved version
const {range, name} = PackageRequest.normalizePattern(this.pattern);
const resolved: ?Manifest = this.resolver.getHighestRangeVersionMatch(name, range);
invariant(resolved, 'should have a resolved reference');

this.reportResolvedRangeMatch(info, resolved);
const ref = resolved._reference;
invariant(ref, 'Resolved package info has no package reference');
ref.addRequest(this);
ref.addPattern(this.pattern, resolved);
}

/**
* TODO description
*/
Expand All @@ -246,11 +266,7 @@ export default class PackageRequest {
const {range, name} = PackageRequest.normalizePattern(this.pattern);
const resolved: ?Manifest = this.resolver.getHighestRangeVersionMatch(name, range);
if (resolved) {
this.reportResolvedRangeMatch(info, resolved);
const ref = resolved._reference;
invariant(ref, 'Resolved package info has no package reference');
ref.addRequest(this);
ref.addPattern(this.pattern, resolved);
this.resolver.reportPackageWithExistingVersion(this, info);
return;
}

Expand Down
30 changes: 30 additions & 0 deletions src/package-resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class PackageResolver {
this.reporter = config.reporter;
this.lockfile = lockfile;
this.config = config;
this.delayedResolveQueue = [];
}

// whether the dependency graph will be flattened
Expand Down Expand Up @@ -78,6 +79,10 @@ export default class PackageResolver {
// environment specific config methods and options
config: Config;

// list of packages need to be resolved later (they found a matching version in the
// resolver, but better matches can still arrive later in the resolve process)
delayedResolveQueue: Array<{ req: PackageRequest, info: Manifest }>;

/**
* TODO description
*/
Expand Down Expand Up @@ -474,7 +479,32 @@ export default class PackageResolver {
//
await Promise.all(deps.map((req): Promise<void> => this.find(req)));

// all required package versions have been discovered, so now packages that
// resolved to existing versions can be resolved to their best available version
this.resolvePackagesWithExistingVersions();

activity.end();
this.activity = null;
}

/**
* Called by the package requester for packages that this resolver already had
* a matching version for. Delay the resolve, because better matches can still be
* discovered.
*/

reportPackageWithExistingVersion(req: PackageRequest, info: Manifest) {
this.delayedResolveQueue.push({req, info});
}

/**
* Executes the resolve to existing versions for packages after the find process,
* when all versions that are going to be used have been discovered.
*/

resolvePackagesWithExistingVersions() {
for (const {req, info} of this.delayedResolveQueue) {
req.resolveToExistingVersion(info);
}
}
}

0 comments on commit b17d5f6

Please sign in to comment.