Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: migrate to batching toposort for execution of releases #46

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

ThisIsMissEm
Copy link

This is still heavily a WIP, but from the API side, it seems to work well. The tests are currently failing because there's a cyclic dependency chain in the tests/fixtures/yarnWorkspaces packages, which, as far as I can tell could never actually be released fully, as there'd always be something out of date.

I guess it depends on what actually is considered for the DAG, right now we're considering all dependencies, but maybe only dependencies should count, as devDependencies, optionalDependencies and peerDependencies don't really affect versioning as far as I can tell?

Consider this a proof of concept, and we can continue polishing it — e.g., I'm not sure updating the package objects is ideal, and instead perhaps we're better just returning a results object? I'm not sure.

Copy link
Author

@ThisIsMissEm ThisIsMissEm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments about particular aspects that might be curious for reviewers.

@@ -215,20 +201,12 @@ function createInlinePluginCreator(packages, multiContext, synchronizer, flags)
debug("notes generated: %s", pkg.name);

// Return the notes.
return notes.join("\n\n");
return notes.join("\n").replace("\n\n", "\n").trim();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be needed, but I was noticing that our release notes contained a lot of unnecessary new lines

@@ -15,7 +15,7 @@ function hasChangedDeep(packages, ignore = []) {
// 1. Any local dep package itself has changed
if (p._nextType) return true;
// 2. Any local dep package has local deps that have changed.
else if (hasChangedDeep(p._localDeps, [...ignore, ...packages])) return true;
else if (p._localDeps && hasChangedDeep(p._localDeps, [...ignore, ...packages])) return true;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came across this bug originally when doing a spike into why our API usage of multi-semantic-release didn't work, essentially, p._localDeps can be undefined, as far as I can tell, so this check prevents recursion which would result in an error.

Copy link
Collaborator

@antongolub antongolub Nov 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I've moved localDeps resolver one level up. 48bcad7

pkg.localDeps = pkg.deps.map((d) => packages.find((p) => d === p.name)).filter(Boolean);

@@ -180,10 +239,29 @@ async function releasePackage(pkg, createInlinePlugin, multiContext) {

// Call semanticRelease() on the directory and save result to pkg.
// Don't need to log out errors as semantic-release already does that.
pkg.result = await semanticRelease(options, {
pkg.result = semanticRelease(options, {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by not awaiting here, we allow parallel releases of packages within a given batch; this does mean an interleaved log at the moment, but does mean that you should have faster releases.

releases: result.releases,
});

pkg.isPending = false;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isPending was added when I was modifying the original algorithm of the synchronizer, so can safely be ignored now — I was using this to determine the todo()'s instead of the presence or absence of the pkg.result property

@antongolub
Copy link
Collaborator

antongolub commented Nov 21, 2020

Hey, @ThisIsMissEm,

Great improvement. This is really cool, keep going.
FYI, syncronizer is needed to isolate the release commits contents of different packages. For example, if several packages use npm plugin in parallel, the first lucky of them may capture all modified packages/**/package.json files when the git plugin searches for changes.

https://github.com/semantic-release/git/blob/905f113a577c55cd9bb0a37ea3504d9e8ee2dfa2/lib/git.js#L11

async function getModifiedFiles(execaOptions) {
  return (await execa('git', ['ls-files', '-m', '-o'], execaOptions)).stdout
    .split('\n')
    .map(file => file.trim())
    .filter(file => Boolean(file));
}

@ThisIsMissEm
Copy link
Author

Isn't this just executing in a subtree of the repository?

@ThisIsMissEm
Copy link
Author

e.g.,

$ pwd
[...]/repo/packages/example-create-react-app

$ git rev-parse --show-cdup
../../

$ git st
## develop...origin/develop
 M package.json
 M ../react/package.json
 M ../../scripts/createOrUpdateReleasePR.js

$ git ls-files -m -o
build/ [lots of files]
node_modules/ [lots of files]
package.json

$ git ls-files -m
package.json

@antongolub
Copy link
Collaborator

@ThisIsMissEm
Copy link
Author

looks similar: IgorBabkin@6337040

Honestly, not sure there. How shall we move forwards on this?

@antongolub
Copy link
Collaborator

@ThisIsMissEm,

It just seemed like a similar problem was being solved. Anyway, I suppose everything can be made easier. We need just number the vertices of the deps graph, right? And follow the marks during publication. So we could build an incidence matrix/adjacency matrix, find the first kind sources/entry points (they have no outgoing arcs), number them. After that, we may exclude these vertices, remove incoming arcs, and repeat the procedure. With cyclic graphs, I think, we can use any package that needs release as the entry point.

@reuzel
Copy link
Contributor

reuzel commented Aug 13, 2021

Not sure if this pull-request is still alive...

The tests are currently failing because there's a cyclic dependency chain in the tests/fixtures/yarnWorkspaces packages, which, as far as I can tell could never actually be released fully, as there'd always be something out of date.

Yes it could. The test definition is actually fine. Processing wise: the trick is to keep a trace of packages that you analyzed when traversing the graph. Skip the ones you already processed earlier to prevent endless loops. If a depends on b, and b depends on a, they should simply be updated both if any of them has a change.

@ThisIsMissEm
Copy link
Author

Not sure if this pull-request is still alive...

The tests are currently failing because there's a cyclic dependency chain in the tests/fixtures/yarnWorkspaces packages, which, as far as I can tell could never actually be released fully, as there'd always be something out of date.

Yes it could. The test definition is actually fine. Processing wise: the trick is to keep a trace of packages that you analyzed when traversing the graph. Skip the ones you already processed earlier to prevent endless loops. If a depends on b, and b depends on a, they should simply be updated both if any of them has a change.

Right, which would make your release process either go indefinitely or you'd always have one package not published as fully up to date.

@ThisIsMissEm
Copy link
Author

At some point I'll get back to this (I was actually hacking on semantic-release and MSR the other day, but unfortunately the company that was paying for that effort is no longer)

@nfantone
Copy link

nfantone commented Jan 4, 2023

I know this is rather old, but we are very interested in having this.

@ThisIsMissEm Could I help in any way getting this over the line?

Since it's been a while now, has there been any other alternatives to this adopted by the wider community? The only thing I could dig out that comes close to having ordered releases is https://github.com/semrel-extra/zx-bulk-release which uses @semrel-extra/topo for topological sorting.

@ThisIsMissEm
Copy link
Author

I'm unlikely to pick this up again right now, unless there's sponsorship, as I simply don't have the time otherwise.

@antongolub
Copy link
Collaborator

Just another one example, if somebody is willing to continue this work: https://github.com/qiwi/multi-semantic-release/blob/master/lib/multiSemanticRelease.js

@nfantone
Copy link

nfantone commented Jan 4, 2023

@antongolub Thanks for sharing that. Seems like the @qiwi fork already has topological sorting baked-in, which was the original purpose of this PR. Am I reading that correctly?

@antongolub
Copy link
Collaborator

Exactly. But @qiwi fork has many tiny subtle differences, I just can't rebase it and avoid regressive bugs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants