-
Notifications
You must be signed in to change notification settings - Fork 441
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
many: automatically detect dependency changes #2165
Conversation
b9b5fc2
to
a137e67
Compare
Currently the only way parts know they're dirty from dependency changes is if the lifecycle tells them so. This is a very error-prone approach, since if the lifecycle forgets to tell the part, then it doesn't know it needs to be cleaned. Dependency changes should be automatically detected instead of being noted in code, which also simplifies the upcoming feature of detecting source changes. Signed-off-by: Kyle Fazzari <kyrofa@ubuntu.com>
a137e67
to
9e66ac0
Compare
Codecov Report
@@ Coverage Diff @@
## master #2165 +/- ##
==========================================
+ Coverage 91.21% 91.26% +0.04%
==========================================
Files 198 200 +2
Lines 12471 12560 +89
Branches 1846 1869 +23
==========================================
+ Hits 11376 11463 +87
- Misses 741 742 +1
- Partials 354 355 +1
Continue to review full report at Codecov.
|
@@ -19,3 +19,4 @@ | |||
from ._init import init # noqa | |||
from ._packer import pack # noqa | |||
from ._runner import execute # noqa | |||
from ._status_cache import StatusCache # noqa |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably doesn't need to be exported anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@@ -299,36 +277,22 @@ def _run_step(self, *, step: steps.Step, part, progress, hint='', | |||
|
|||
notify_part_progress(part, progress, hint) | |||
getattr(part, step.name)() | |||
self._steps_run[part.name].add(step) | |||
self._cache.add_step_run(part, step) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comments here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
self.dirty_project_options = dirty_project_options | ||
self.changed_dependencies = changed_dependencies | ||
|
||
def report(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make these get_*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
""" | ||
self.config = config | ||
self._steps_run = dict() # type: Dict[str, Set[steps.Step]] | ||
self._dirty_reports = collections.defaultdict(dict) # type: Dict[str, Dict[steps.Step, pluginhandler.DirtyReport]] # noqa |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create type for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
self._ensure_steps_run(part) | ||
self._steps_run[part.name].add(step) | ||
|
||
def step_has_run(self, part: pluginhandler.PluginHandler, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
has_step_run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
common.reset_env() | ||
prereqs = self.parts_config.get_prereqs(part.name) | ||
prereq_parts = self.parts_config.get_dependencies(part.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we had a nice conversation about get_dependencies
being renamed, but the local var is still called prereq_parts
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good catch, fixed.
step_prereqs = {p for p in prereqs | ||
if prerequisite_step not in self._steps_run[p]} | ||
prerequisite_step = steps.get_dependency_prerequisite_step(step) | ||
step_prereqs = {p for p in prereq_parts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed as well.
|
||
if step_prereqs: | ||
prereq_names = {p.name for p in step_prereqs} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
2. Is dirty | ||
3. Either (1) or (2) apply to any earlier steps in its lifecycle | ||
""" | ||
if (not self.step_has_run(part, step) or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this use I can see why this may be nice to have in the pluginhandler itself.
self._steps_run = dict() # type: Dict[str, Set[steps.Step]] | ||
self._dirty_reports = collections.defaultdict(dict) # type: Dict[str, Dict[steps.Step, pluginhandler.DirtyReport]] # noqa | ||
|
||
def step_should_run(self, part: pluginhandler.PluginHandler, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iif we follow the pattern, should_step_run
prerequisite_step) | ||
except errors.StepHasNotRunError: | ||
dependency_changed = True | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always have to lookup if else
is for the try
succeeding or no matching except
🤷♂️
|
||
def _ensure_dirty_report(self, part: pluginhandler.PluginHandler, | ||
step: steps.Step) -> None: | ||
if step not in self._dirty_reports[part.name]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about returning early if it is in and bumping everything below one level in?
It would make it obvious that there is no else
case below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw, this is the only scenario where I am mostly ok with multiple return scenarios, i.e.; prechecks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, actually was able to bring it back two stops.
|
||
return None | ||
|
||
def should_step_run(self, step, force=False): | ||
return force or self.is_clean(step) | ||
|
||
def step_timestamp(self, step): | ||
try: | ||
return os.stat( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can tell you like stairs :taunt: (emoji request 😄 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yikes, fixed that a bit.
def summary(self): | ||
reasons = [] | ||
|
||
reason_count = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reasons_count?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, done.
|
||
|
||
class DirtyReport: | ||
def __init__(self, *, dirty_properties=None, dirty_project_options=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
types please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes of course, fixed.
reasons.append('properties') | ||
else: | ||
reasons.append('{!r} property'.format( | ||
next(iter(self.dirty_properties)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This confuses me and I couldn't fine a unit test; so given dirty_properties={'foo', 'bar'}
(as in some test code below, this would reasons.append only foo
? Is that the objective or am I missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, this branch is only hit if there is a single property. This is just getting at the property in a manner that works for both lists and sets. You're absolutely right though, I'm missing tests for these, so I'll add those which should clarify further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
498587a
to
3a5e97e
Compare
LP: #1778081 Signed-off-by: Kyle Fazzari <kyrofa@ubuntu.com>
3a5e97e
to
33cd1db
Compare
It seems rust is failing for every PR. Ignoring and merging. |
./runtests.sh static
?./runtests.sh unit
?Currently the only way parts know they're dirty from dependency changes is if the lifecycle tells them so. This is a very error-prone approach, since if the lifecycle forgets to tell the part, then it doesn't know it needs to be cleaned. This PR fixes LP: #1778081 by automatically detecting changes instead of noting them in code, which also simplifies the upcoming feature of detecting source changes.
Note that this PR breaks backward state compatibility with #2152 (i.e. running on trees made dirty since then will break). This was judged to be acceptable based on the fact that it has not yet been contained in a release.