Skip to content

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

Draft
ThisIsMissEm wants to merge 2 commits into
dhoulb:masterfrom
ThisIsMissEm:refactor/migrate-to-toposort
Draft

Refactor: migrate to batching toposort for execution of releases#46
ThisIsMissEm wants to merge 2 commits into
dhoulb:masterfrom
ThisIsMissEm:refactor/migrate-to-toposort

Conversation

@ThisIsMissEm

Copy link
Copy Markdown

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.

@ThisIsMissEm
ThisIsMissEm force-pushed the refactor/migrate-to-toposort branch from 6c41356 to b3ca7bb Compare November 21, 2020 11:26

@ThisIsMissEm ThisIsMissEm left a comment

Copy link
Copy Markdown
Author

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.


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

Copy link
Copy Markdown
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

Comment thread lib/hasChangedDeep.js
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
Copy Markdown
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.

@antongolub antongolub Nov 21, 2020

Copy link
Copy Markdown
Collaborator

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);

// 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
Copy Markdown
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
Copy Markdown
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

antongolub commented Nov 21, 2020

Copy link
Copy Markdown
Collaborator

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
Copy Markdown
Author

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

@ThisIsMissEm

Copy link
Copy Markdown
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
Copy Markdown
Collaborator

@ThisIsMissEm

Copy link
Copy Markdown
Author

looks similar: IgorBabkin@6337040

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

@antongolub

Copy link
Copy Markdown
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

reuzel commented Aug 13, 2021

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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

nfantone commented Jan 4, 2023

Copy link
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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

nfantone commented Jan 4, 2023

Copy link
Copy Markdown

@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
Copy Markdown
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