diff options
Diffstat (limited to 'buildbot/buildbot/interfaces.py')
-rw-r--r-- | buildbot/buildbot/interfaces.py | 1123 |
1 files changed, 1123 insertions, 0 deletions
diff --git a/buildbot/buildbot/interfaces.py b/buildbot/buildbot/interfaces.py new file mode 100644 index 0000000..e510d05 --- /dev/null +++ b/buildbot/buildbot/interfaces.py @@ -0,0 +1,1123 @@ + +"""Interface documentation. + +Define the interfaces that are implemented by various buildbot classes. +""" + +from zope.interface import Interface, Attribute + +# exceptions that can be raised while trying to start a build +class NoSlaveError(Exception): + pass +class BuilderInUseError(Exception): + pass +class BuildSlaveTooOldError(Exception): + pass +class LatentBuildSlaveFailedToSubstantiate(Exception): + pass + +# other exceptions +class BuildbotNotRunningError(Exception): + pass + +class IChangeSource(Interface): + """Object which feeds Change objects to the changemaster. When files or + directories are changed and the version control system provides some + kind of notification, this object should turn it into a Change object + and pass it through:: + + self.changemaster.addChange(change) + """ + + def start(): + """Called when the buildmaster starts. Can be used to establish + connections to VC daemons or begin polling.""" + + def stop(): + """Called when the buildmaster shuts down. Connections should be + terminated, polling timers should be canceled.""" + + def describe(): + """Should return a string which briefly describes this source. This + string will be displayed in an HTML status page.""" + +class IScheduler(Interface): + """I watch for Changes in the source tree and decide when to trigger + Builds. I create BuildSet objects and submit them to the BuildMaster. I + am a service, and the BuildMaster is always my parent. + + @ivar properties: properties to be applied to all builds started by this + scheduler + @type properties: L<buildbot.process.properties.Properties> + """ + + def addChange(change): + """A Change has just been dispatched by one of the ChangeSources. + Each Scheduler will receive this Change. I may decide to start a + build as a result, or I might choose to ignore it.""" + + def listBuilderNames(): + """Return a list of strings indicating the Builders that this + Scheduler might feed.""" + + def getPendingBuildTimes(): + """Return a list of timestamps for any builds that are waiting in the + tree-stable-timer queue. This is only relevant for Change-based + schedulers, all others can just return an empty list.""" + # TODO: it might be nice to make this into getPendingBuildSets, which + # would let someone subscribe to the buildset being finished. + # However, the Scheduler doesn't actually create the buildset until + # it gets submitted, so doing this would require some major rework. + +class IUpstreamScheduler(Interface): + """This marks an IScheduler as being eligible for use as the 'upstream=' + argument to a buildbot.scheduler.Dependent instance.""" + + def subscribeToSuccessfulBuilds(target): + """Request that the target callbable be invoked after every + successful buildset. The target will be called with a single + argument: the SourceStamp used by the successful builds.""" + + def listBuilderNames(): + """Return a list of strings indicating the Builders that this + Scheduler might feed.""" + +class IDownstreamScheduler(Interface): + """This marks an IScheduler to be listening to other schedulers. + On reconfigs, these might get notified to check if their upstream + scheduler are stil the same.""" + + def checkUpstreamScheduler(): + """Check if the upstream scheduler is still alive, and if not, + get a new upstream object from the master.""" + + +class ISourceStamp(Interface): + """ + @cvar branch: branch from which source was drawn + @type branch: string or None + + @cvar revision: revision of the source, or None to use CHANGES + @type revision: varies depending on VC + + @cvar patch: patch applied to the source, or None if no patch + @type patch: None or tuple (level diff) + + @cvar changes: the source step should check out hte latest revision + in the given changes + @type changes: tuple of L{buildbot.changes.changes.Change} instances, + all of which are on the same branch + """ + + def canBeMergedWith(self, other): + """ + Can this SourceStamp be merged with OTHER? + """ + + def mergeWith(self, others): + """Generate a SourceStamp for the merger of me and all the other + BuildRequests. This is called by a Build when it starts, to figure + out what its sourceStamp should be.""" + + def getAbsoluteSourceStamp(self, got_revision): + """Get a new SourceStamp object reflecting the actual revision found + by a Source step.""" + + def getText(self): + """Returns a list of strings to describe the stamp. These are + intended to be displayed in a narrow column. If more space is + available, the caller should join them together with spaces before + presenting them to the user.""" + +class IEmailSender(Interface): + """I know how to send email, and can be used by other parts of the + Buildbot to contact developers.""" + pass + +class IEmailLookup(Interface): + def getAddress(user): + """Turn a User-name string into a valid email address. Either return + a string (with an @ in it), None (to indicate that the user cannot + be reached by email), or a Deferred which will fire with the same.""" + +class IStatus(Interface): + """I am an object, obtainable from the buildmaster, which can provide + status information.""" + + def getProjectName(): + """Return the name of the project that this Buildbot is working + for.""" + def getProjectURL(): + """Return the URL of this Buildbot's project.""" + def getBuildbotURL(): + """Return the URL of the top-most Buildbot status page, or None if + this Buildbot does not provide a web status page.""" + def getURLForThing(thing): + """Return the URL of a page which provides information on 'thing', + which should be an object that implements one of the status + interfaces defined in L{buildbot.interfaces}. Returns None if no + suitable page is available (or if no Waterfall is running).""" + + def getChangeSources(): + """Return a list of IChangeSource objects.""" + + def getChange(number): + """Return an IChange object.""" + + def getSchedulers(): + """Return a list of ISchedulerStatus objects for all + currently-registered Schedulers.""" + + def getBuilderNames(categories=None): + """Return a list of the names of all current Builders.""" + def getBuilder(name): + """Return the IBuilderStatus object for a given named Builder. Raises + KeyError if there is no Builder by that name.""" + + def getSlaveNames(): + """Return a list of buildslave names, suitable for passing to + getSlave().""" + def getSlave(name): + """Return the ISlaveStatus object for a given named buildslave.""" + + def getBuildSets(): + """Return a list of active (non-finished) IBuildSetStatus objects.""" + + def generateFinishedBuilds(builders=[], branches=[], + num_builds=None, finished_before=None, + max_search=200): + """Return a generator that will produce IBuildStatus objects each + time you invoke its .next() method, starting with the most recent + finished build and working backwards. + + @param builders: this is a list of Builder names, and the generator + will only produce builds that ran on the given + Builders. If the list is empty, produce builds from + all Builders. + + @param branches: this is a list of branch names, and the generator + will only produce builds that used the given + branches. If the list is empty, produce builds from + all branches. + + @param num_builds: the generator will stop after providing this many + builds. The default of None means to produce as + many builds as possible. + + @type finished_before: int: a timestamp, seconds since the epoch + @param finished_before: if provided, do not produce any builds that + finished after the given timestamp. + + @type max_search: int + @param max_search: this method may have to examine a lot of builds + to find some that match the search parameters, + especially if there aren't any matching builds. + This argument imposes a hard limit on the number + of builds that will be examined within any given + Builder. + """ + + def subscribe(receiver): + """Register an IStatusReceiver to receive new status events. The + receiver will immediately be sent a set of 'builderAdded' messages + for all current builders. It will receive further 'builderAdded' and + 'builderRemoved' messages as the config file is reloaded and builders + come and go. It will also receive 'buildsetSubmitted' messages for + all outstanding BuildSets (and each new BuildSet that gets + submitted). No additional messages will be sent unless the receiver + asks for them by calling .subscribe on the IBuilderStatus objects + which accompany the addedBuilder message.""" + + def unsubscribe(receiver): + """Unregister an IStatusReceiver. No further status messgaes will be + delivered.""" + +class IBuildSetStatus(Interface): + """I represent a set of Builds, each run on a separate Builder but all + using the same source tree.""" + + def getSourceStamp(): + """Return a SourceStamp object which can be used to re-create + the source tree that this build used. + + This method will return None if the source information is no longer + available.""" + pass + def getReason(): + pass + def getID(): + """Return the BuildSet's ID string, if any. The 'try' feature uses a + random string as a BuildSetID to relate submitted jobs with the + resulting BuildSet.""" + def getResponsibleUsers(): + pass # not implemented + def getInterestedUsers(): + pass # not implemented + def getBuilderNames(): + """Return a list of the names of all Builders on which this set will + do builds.""" + def getBuildRequests(): + """Return a list of IBuildRequestStatus objects that represent my + component Builds. This list might correspond to the Builders named by + getBuilderNames(), but if builder categories are used, or 'Builder + Aliases' are implemented, then they may not.""" + def isFinished(): + pass + def waitUntilSuccess(): + """Return a Deferred that fires (with this IBuildSetStatus object) + when the outcome of the BuildSet is known, i.e., upon the first + failure, or after all builds complete successfully.""" + def waitUntilFinished(): + """Return a Deferred that fires (with this IBuildSetStatus object) + when all builds have finished.""" + def getResults(): + pass + +class IBuildRequestStatus(Interface): + """I represent a request to build a particular set of source code on a + particular Builder. These requests may be merged by the time they are + finally turned into a Build.""" + + def getSourceStamp(): + """Return a SourceStamp object which can be used to re-create + the source tree that this build used. This method will + return an absolute SourceStamp if possible, and its results + may change as the build progresses. Specifically, a "HEAD" + build may later be more accurately specified by an absolute + SourceStamp with the specific revision information. + + This method will return None if the source information is no longer + available.""" + pass + def getBuilderName(): + pass + def getBuilds(): + """Return a list of IBuildStatus objects for each Build that has been + started in an attempt to satify this BuildRequest.""" + + def subscribe(observer): + """Register a callable that will be invoked (with a single + IBuildStatus object) for each Build that is created to satisfy this + request. There may be multiple Builds created in an attempt to handle + the request: they may be interrupted by the user or abandoned due to + a lost slave. The last Build (the one which actually gets to run to + completion) is said to 'satisfy' the BuildRequest. The observer will + be called once for each of these Builds, both old and new.""" + def unsubscribe(observer): + """Unregister the callable that was registered with subscribe().""" + def getSubmitTime(): + """Return the time when this request was submitted""" + def setSubmitTime(t): + """Sets the time when this request was submitted""" + + +class ISlaveStatus(Interface): + def getName(): + """Return the name of the build slave.""" + + def getAdmin(): + """Return a string with the slave admin's contact data.""" + + def getHost(): + """Return a string with the slave host info.""" + + def isConnected(): + """Return True if the slave is currently online, False if not.""" + + def lastMessageReceived(): + """Return a timestamp (seconds since epoch) indicating when the most + recent message was received from the buildslave.""" + +class ISchedulerStatus(Interface): + def getName(): + """Return the name of this Scheduler (a string).""" + + def getPendingBuildsets(): + """Return an IBuildSet for all BuildSets that are pending. These + BuildSets are waiting for their tree-stable-timers to expire.""" + # TODO: this is not implemented anywhere + + +class IBuilderStatus(Interface): + def getName(): + """Return the name of this Builder (a string).""" + + def getState(): + # TODO: this isn't nearly as meaningful as it used to be + """Return a tuple (state, builds) for this Builder. 'state' is the + so-called 'big-status', indicating overall status (as opposed to + which step is currently running). It is a string, one of 'offline', + 'idle', or 'building'. 'builds' is a list of IBuildStatus objects + (possibly empty) representing the currently active builds.""" + + def getSlaves(): + """Return a list of ISlaveStatus objects for the buildslaves that are + used by this builder.""" + + def getPendingBuilds(): + """Return an IBuildRequestStatus object for all upcoming builds + (those which are ready to go but which are waiting for a buildslave + to be available.""" + + def getCurrentBuilds(): + """Return a list containing an IBuildStatus object for each build + currently in progress.""" + # again, we could probably provide an object for 'waiting' and + # 'interlocked' too, but things like the Change list might still be + # subject to change + + def getLastFinishedBuild(): + """Return the IBuildStatus object representing the last finished + build, which may be None if the builder has not yet finished any + builds.""" + + def getBuild(number): + """Return an IBuildStatus object for a historical build. Each build + is numbered (starting at 0 when the Builder is first added), + getBuild(n) will retrieve the Nth such build. getBuild(-n) will + retrieve a recent build, with -1 being the most recent build + started. If the Builder is idle, this will be the same as + getLastFinishedBuild(). If the Builder is active, it will be an + unfinished build. This method will return None if the build is no + longer available. Older builds are likely to have less information + stored: Logs are the first to go, then Steps.""" + + def getEvent(number): + """Return an IStatusEvent object for a recent Event. Builders + connecting and disconnecting are events, as are ping attempts. + getEvent(-1) will return the most recent event. Events are numbered, + but it probably doesn't make sense to ever do getEvent(+n).""" + + def generateFinishedBuilds(branches=[], + num_builds=None, + max_buildnum=None, finished_before=None, + max_search=200, + ): + """Return a generator that will produce IBuildStatus objects each + time you invoke its .next() method, starting with the most recent + finished build, then the previous build, and so on back to the oldest + build available. + + @param branches: this is a list of branch names, and the generator + will only produce builds that involve the given + branches. If the list is empty, the generator will + produce all builds regardless of what branch they + used. + + @param num_builds: if provided, the generator will stop after + providing this many builds. The default of None + means to produce as many builds as possible. + + @param max_buildnum: if provided, the generator will start by + providing the build with this number, or the + highest-numbered preceding build (i.e. the + generator will not produce any build numbered + *higher* than max_buildnum). The default of None + means to start with the most recent finished + build. -1 means the same as None. -2 means to + start with the next-most-recent completed build, + etc. + + @type finished_before: int: a timestamp, seconds since the epoch + @param finished_before: if provided, do not produce any builds that + finished after the given timestamp. + + @type max_search: int + @param max_search: this method may have to examine a lot of builds + to find some that match the search parameters, + especially if there aren't any matching builds. + This argument imposes a hard limit on the number + of builds that will be examined. + """ + + def subscribe(receiver): + """Register an IStatusReceiver to receive new status events. The + receiver will be given builderChangedState, buildStarted, and + buildFinished messages.""" + + def unsubscribe(receiver): + """Unregister an IStatusReceiver. No further status messgaes will be + delivered.""" + +class IEventSource(Interface): + def eventGenerator(branches=[]): + """This function creates a generator which will yield all of this + object's status events, starting with the most recent and progressing + backwards in time. These events provide the IStatusEvent interface. + At the moment they are all instances of buildbot.status.builder.Event + or buildbot.status.builder.BuildStepStatus . + + @param branches: a list of branch names. The generator should only + return events that are associated with these branches. If the list is + empty, events for all branches should be returned (i.e. an empty list + means 'accept all' rather than 'accept none'). + """ + +class IBuildStatus(Interface): + """I represent the status of a single Build/BuildRequest. It could be + in-progress or finished.""" + + def getBuilder(): + """ + Return the BuilderStatus that owns this build. + + @rtype: implementor of L{IBuilderStatus} + """ + + def isFinished(): + """Return a boolean. True means the build has finished, False means + it is still running.""" + + def waitUntilFinished(): + """Return a Deferred that will fire when the build finishes. If the + build has already finished, this deferred will fire right away. The + callback is given this IBuildStatus instance as an argument.""" + + def getProperty(propname): + """Return the value of the build property with the given name. Raises + KeyError if there is no such property on this build.""" + + def getReason(): + """Return a string that indicates why the build was run. 'changes', + 'forced', and 'periodic' are the most likely values. 'try' will be + added in the future.""" + + def getSourceStamp(): + """Return a SourceStamp object which can be used to re-create + the source tree that this build used. + + This method will return None if the source information is no longer + available.""" + # TODO: it should be possible to expire the patch but still remember + # that the build was r123+something. + + def getChanges(): + """Return a list of Change objects which represent which source + changes went into the build.""" + + def getResponsibleUsers(): + """Return a list of Users who are to blame for the changes that went + into this build. If anything breaks (at least anything that wasn't + already broken), blame them. Specifically, this is the set of users + who were responsible for the Changes that went into this build. Each + User is a string, corresponding to their name as known by the VC + repository.""" + + def getInterestedUsers(): + """Return a list of Users who will want to know about the results of + this build. This is a superset of getResponsibleUsers(): it adds + people who are interested in this build but who did not actually + make the Changes that went into it (build sheriffs, code-domain + owners).""" + + def getNumber(): + """Within each builder, each Build has a number. Return it.""" + + def getPreviousBuild(): + """Convenience method. Returns None if the previous build is + unavailable.""" + + def getSteps(): + """Return a list of IBuildStepStatus objects. For invariant builds + (those which always use the same set of Steps), this should always + return the complete list, however some of the steps may not have + started yet (step.getTimes()[0] will be None). For variant builds, + this may not be complete (asking again later may give you more of + them).""" + + def getTimes(): + """Returns a tuple of (start, end). 'start' and 'end' are the times + (seconds since the epoch) when the Build started and finished. If + the build is still running, 'end' will be None.""" + + # while the build is running, the following methods make sense. + # Afterwards they return None + + def getETA(): + """Returns the number of seconds from now in which the build is + expected to finish, or None if we can't make a guess. This guess will + be refined over time.""" + + def getCurrentStep(): + """Return an IBuildStepStatus object representing the currently + active step.""" + + # Once you know the build has finished, the following methods are legal. + # Before ths build has finished, they all return None. + + def getSlavename(): + """Return the name of the buildslave which handled this build.""" + + def getText(): + """Returns a list of strings to describe the build. These are + intended to be displayed in a narrow column. If more space is + available, the caller should join them together with spaces before + presenting them to the user.""" + + def getResults(): + """Return a constant describing the results of the build: one of the + constants in buildbot.status.builder: SUCCESS, WARNINGS, or + FAILURE.""" + + def getLogs(): + """Return a list of logs that describe the build as a whole. Some + steps will contribute their logs, while others are are less important + and will only be accessible through the IBuildStepStatus objects. + Each log is an object which implements the IStatusLog interface.""" + + def getTestResults(): + """Return a dictionary that maps test-name tuples to ITestResult + objects. This may return an empty or partially-filled dictionary + until the build has completed.""" + + # subscription interface + + def subscribe(receiver, updateInterval=None): + """Register an IStatusReceiver to receive new status events. The + receiver will be given stepStarted and stepFinished messages. If + 'updateInterval' is non-None, buildETAUpdate messages will be sent + every 'updateInterval' seconds.""" + + def unsubscribe(receiver): + """Unregister an IStatusReceiver. No further status messgaes will be + delivered.""" + +class ITestResult(Interface): + """I describe the results of a single unit test.""" + + def getName(): + """Returns a tuple of strings which make up the test name. Tests may + be arranged in a hierarchy, so looking for common prefixes may be + useful.""" + + def getResults(): + """Returns a constant describing the results of the test: SUCCESS, + WARNINGS, FAILURE.""" + + def getText(): + """Returns a list of short strings which describe the results of the + test in slightly more detail. Suggested components include + 'failure', 'error', 'passed', 'timeout'.""" + + def getLogs(): + # in flux, it may be possible to provide more structured information + # like python Failure instances + """Returns a dictionary of test logs. The keys are strings like + 'stdout', 'log', 'exceptions'. The values are strings.""" + + +class IBuildStepStatus(Interface): + """I hold status for a single BuildStep.""" + + def getName(): + """Returns a short string with the name of this step. This string + may have spaces in it.""" + + def getBuild(): + """Returns the IBuildStatus object which contains this step.""" + + def getTimes(): + """Returns a tuple of (start, end). 'start' and 'end' are the times + (seconds since the epoch) when the Step started and finished. If the + step has not yet started, 'start' will be None. If the step is still + running, 'end' will be None.""" + + def getExpectations(): + """Returns a list of tuples (name, current, target). Each tuple + describes a single axis along which the step's progress can be + measured. 'name' is a string which describes the axis itself, like + 'filesCompiled' or 'tests run' or 'bytes of output'. 'current' is a + number with the progress made so far, while 'target' is the value + that we expect (based upon past experience) to get to when the build + is finished. + + 'current' will change over time until the step is finished. It is + 'None' until the step starts. When the build is finished, 'current' + may or may not equal 'target' (which is merely the expectation based + upon previous builds).""" + + def getURLs(): + """Returns a dictionary of URLs. Each key is a link name (a short + string, like 'results' or 'coverage'), and each value is a URL. These + links will be displayed along with the LogFiles. + """ + + def getLogs(): + """Returns a list of IStatusLog objects. If the step has not yet + finished, this list may be incomplete (asking again later may give + you more of them).""" + + + def isFinished(): + """Return a boolean. True means the step has finished, False means it + is still running.""" + + def waitUntilFinished(): + """Return a Deferred that will fire when the step finishes. If the + step has already finished, this deferred will fire right away. The + callback is given this IBuildStepStatus instance as an argument.""" + + # while the step is running, the following methods make sense. + # Afterwards they return None + + def getETA(): + """Returns the number of seconds from now in which the step is + expected to finish, or None if we can't make a guess. This guess will + be refined over time.""" + + # Once you know the step has finished, the following methods are legal. + # Before ths step has finished, they all return None. + + def getText(): + """Returns a list of strings which describe the step. These are + intended to be displayed in a narrow column. If more space is + available, the caller should join them together with spaces before + presenting them to the user.""" + + def getResults(): + """Return a tuple describing the results of the step: (result, + strings). 'result' is one of the constants in + buildbot.status.builder: SUCCESS, WARNINGS, FAILURE, or SKIPPED. + 'strings' is an optional list of strings that the step wants to + append to the overall build's results. These strings are usually + more terse than the ones returned by getText(): in particular, + successful Steps do not usually contribute any text to the overall + build.""" + + # subscription interface + + def subscribe(receiver, updateInterval=10): + """Register an IStatusReceiver to receive new status events. The + receiver will be given logStarted and logFinished messages. It will + also be given a ETAUpdate message every 'updateInterval' seconds.""" + + def unsubscribe(receiver): + """Unregister an IStatusReceiver. No further status messgaes will be + delivered.""" + +class IStatusEvent(Interface): + """I represent a Builder Event, something non-Build related that can + happen to a Builder.""" + + def getTimes(): + """Returns a tuple of (start, end) like IBuildStepStatus, but end==0 + indicates that this is a 'point event', which has no duration. + SlaveConnect/Disconnect are point events. Ping is not: it starts + when requested and ends when the response (positive or negative) is + returned""" + + def getText(): + """Returns a list of strings which describe the event. These are + intended to be displayed in a narrow column. If more space is + available, the caller should join them together with spaces before + presenting them to the user.""" + + +LOG_CHANNEL_STDOUT = 0 +LOG_CHANNEL_STDERR = 1 +LOG_CHANNEL_HEADER = 2 + +class IStatusLog(Interface): + """I represent a single Log, which is a growing list of text items that + contains some kind of output for a single BuildStep. I might be finished, + in which case this list has stopped growing. + + Each Log has a name, usually something boring like 'log' or 'output'. + These names are not guaranteed to be unique, however they are usually + chosen to be useful within the scope of a single step (i.e. the Compile + step might produce both 'log' and 'warnings'). The name may also have + spaces. If you want something more globally meaningful, at least within a + given Build, try:: + + '%s.%s' % (log.getStep.getName(), log.getName()) + + The Log can be presented as plain text, or it can be accessed as a list + of items, each of which has a channel indicator (header, stdout, stderr) + and a text chunk. An HTML display might represent the interleaved + channels with different styles, while a straight download-the-text + interface would just want to retrieve a big string. + + The 'header' channel is used by ShellCommands to prepend a note about + which command is about to be run ('running command FOO in directory + DIR'), and append another note giving the exit code of the process. + + Logs can be streaming: if the Log has not yet finished, you can + subscribe to receive new chunks as they are added. + + A ShellCommand will have a Log associated with it that gathers stdout + and stderr. Logs may also be created by parsing command output or + through other synthetic means (grepping for all the warnings in a + compile log, or listing all the test cases that are going to be run). + Such synthetic Logs are usually finished as soon as they are created.""" + + + def getName(): + """Returns a short string with the name of this log, probably 'log'. + """ + + def getStep(): + """Returns the IBuildStepStatus which owns this log.""" + # TODO: can there be non-Step logs? + + def isFinished(): + """Return a boolean. True means the log has finished and is closed, + False means it is still open and new chunks may be added to it.""" + + def waitUntilFinished(): + """Return a Deferred that will fire when the log is closed. If the + log has already finished, this deferred will fire right away. The + callback is given this IStatusLog instance as an argument.""" + + def subscribe(receiver, catchup): + """Register an IStatusReceiver to receive chunks (with logChunk) as + data is added to the Log. If you use this, you will also want to use + waitUntilFinished to find out when the listener can be retired. + Subscribing to a closed Log is a no-op. + + If 'catchup' is True, the receiver will immediately be sent a series + of logChunk messages to bring it up to date with the partially-filled + log. This allows a status client to join a Log already in progress + without missing any data. If the Log has already finished, it is too + late to catch up: just do getText() instead. + + If the Log is very large, the receiver will be called many times with + a lot of data. There is no way to throttle this data. If the receiver + is planning on sending the data on to somewhere else, over a narrow + connection, you can get a throttleable subscription by using + C{subscribeConsumer} instead.""" + + def unsubscribe(receiver): + """Remove a receiver previously registered with subscribe(). Attempts + to remove a receiver which was not previously registered is a no-op. + """ + + def subscribeConsumer(consumer): + """Register an L{IStatusLogConsumer} to receive all chunks of the + logfile, including all the old entries and any that will arrive in + the future. The consumer will first have their C{registerProducer} + method invoked with a reference to an object that can be told + C{pauseProducing}, C{resumeProducing}, and C{stopProducing}. Then the + consumer's C{writeChunk} method will be called repeatedly with each + (channel, text) tuple in the log, starting with the very first. The + consumer will be notified with C{finish} when the log has been + exhausted (which can only happen when the log is finished). Note that + a small amount of data could be written via C{writeChunk} even after + C{pauseProducing} has been called. + + To unsubscribe the consumer, use C{producer.stopProducing}.""" + + # once the log has finished, the following methods make sense. They can + # be called earlier, but they will only return the contents of the log up + # to the point at which they were called. You will lose items that are + # added later. Use C{subscribe} or C{subscribeConsumer} to avoid missing + # anything. + + def hasContents(): + """Returns True if the LogFile still has contents available. Returns + False for logs that have been pruned. Clients should test this before + offering to show the contents of any log.""" + + def getText(): + """Return one big string with the contents of the Log. This merges + all non-header chunks together.""" + + def readlines(channel=LOG_CHANNEL_STDOUT): + """Read lines from one channel of the logfile. This returns an + iterator that will provide single lines of text (including the + trailing newline). + """ + + def getTextWithHeaders(): + """Return one big string with the contents of the Log. This merges + all chunks (including headers) together.""" + + def getChunks(): + """Generate a list of (channel, text) tuples. 'channel' is a number, + 0 for stdout, 1 for stderr, 2 for header. (note that stderr is merged + into stdout if PTYs are in use).""" + +class IStatusLogConsumer(Interface): + """I am an object which can be passed to IStatusLog.subscribeConsumer(). + I represent a target for writing the contents of an IStatusLog. This + differs from a regular IStatusReceiver in that it can pause the producer. + This makes it more suitable for use in streaming data over network + sockets, such as an HTTP request. Note that the consumer can only pause + the producer until it has caught up with all the old data. After that + point, C{pauseProducing} is ignored and all new output from the log is + sent directoy to the consumer.""" + + def registerProducer(producer, streaming): + """A producer is being hooked up to this consumer. The consumer only + has to handle a single producer. It should send .pauseProducing and + .resumeProducing messages to the producer when it wants to stop or + resume the flow of data. 'streaming' will be set to True because the + producer is always a PushProducer. + """ + + def unregisterProducer(): + """The previously-registered producer has been removed. No further + pauseProducing or resumeProducing calls should be made. The consumer + should delete its reference to the Producer so it can be released.""" + + def writeChunk(chunk): + """A chunk (i.e. a tuple of (channel, text)) is being written to the + consumer.""" + + def finish(): + """The log has finished sending chunks to the consumer.""" + +class IStatusReceiver(Interface): + """I am an object which can receive build status updates. I may be + subscribed to an IStatus, an IBuilderStatus, or an IBuildStatus.""" + + def buildsetSubmitted(buildset): + """A new BuildSet has been submitted to the buildmaster. + + @type buildset: implementor of L{IBuildSetStatus} + """ + + def requestSubmitted(request): + """A new BuildRequest has been submitted to the buildmaster. + + @type request: implementor of L{IBuildRequestStatus} + """ + + def builderAdded(builderName, builder): + """ + A new Builder has just been added. This method may return an + IStatusReceiver (probably 'self') which will be subscribed to receive + builderChangedState and buildStarted/Finished events. + + @type builderName: string + @type builder: L{buildbot.status.builder.BuilderStatus} + @rtype: implementor of L{IStatusReceiver} + """ + + def builderChangedState(builderName, state): + """Builder 'builderName' has changed state. The possible values for + 'state' are 'offline', 'idle', and 'building'.""" + + def buildStarted(builderName, build): + """Builder 'builderName' has just started a build. The build is an + object which implements IBuildStatus, and can be queried for more + information. + + This method may return an IStatusReceiver (it could even return + 'self'). If it does so, stepStarted and stepFinished methods will be + invoked on the object for the steps of this one build. This is a + convenient way to subscribe to all build steps without missing any. + This receiver will automatically be unsubscribed when the build + finishes. + + It can also return a tuple of (IStatusReceiver, interval), in which + case buildETAUpdate messages are sent ever 'interval' seconds, in + addition to the stepStarted and stepFinished messages.""" + + def buildETAUpdate(build, ETA): + """This is a periodic update on the progress this Build has made + towards completion.""" + + def stepStarted(build, step): + """A step has just started. 'step' is the IBuildStepStatus which + represents the step: it can be queried for more information. + + This method may return an IStatusReceiver (it could even return + 'self'). If it does so, logStarted and logFinished methods will be + invoked on the object for logs created by this one step. This + receiver will be automatically unsubscribed when the step finishes. + + Alternatively, the method may return a tuple of an IStatusReceiver + and an integer named 'updateInterval'. In addition to + logStarted/logFinished messages, it will also receive stepETAUpdate + messages about every updateInterval seconds.""" + + def stepTextChanged(build, step, text): + """The text for a step has been updated. + + This is called when calling setText() on the step status, and + hands in the text list.""" + + def stepText2Changed(build, step, text2): + """The text2 for a step has been updated. + + This is called when calling setText2() on the step status, and + hands in text2 list.""" + + def stepETAUpdate(build, step, ETA, expectations): + """This is a periodic update on the progress this Step has made + towards completion. It gets an ETA (in seconds from the present) of + when the step ought to be complete, and a list of expectation tuples + (as returned by IBuildStepStatus.getExpectations) with more detailed + information.""" + + def logStarted(build, step, log): + """A new Log has been started, probably because a step has just + started running a shell command. 'log' is the IStatusLog object + which can be queried for more information. + + This method may return an IStatusReceiver (such as 'self'), in which + case the target's logChunk method will be invoked as text is added to + the logfile. This receiver will automatically be unsubsribed when the + log finishes.""" + + def logChunk(build, step, log, channel, text): + """Some text has been added to this log. 'channel' is one of + LOG_CHANNEL_STDOUT, LOG_CHANNEL_STDERR, or LOG_CHANNEL_HEADER, as + defined in IStatusLog.getChunks.""" + + def logFinished(build, step, log): + """A Log has been closed.""" + + def stepFinished(build, step, results): + """A step has just finished. 'results' is the result tuple described + in IBuildStepStatus.getResults.""" + + def buildFinished(builderName, build, results): + """ + A build has just finished. 'results' is the result tuple described + in L{IBuildStatus.getResults}. + + @type builderName: string + @type build: L{buildbot.status.builder.BuildStatus} + @type results: tuple + """ + + def builderRemoved(builderName): + """The Builder has been removed.""" + +class IControl(Interface): + def addChange(change): + """Add a change to all builders. Each Builder will decide for + themselves whether the change is interesting or not, and may initiate + a build as a result.""" + + def submitBuildSet(buildset): + """Submit a BuildSet object, which will eventually be run on all of + the builders listed therein.""" + + def getBuilder(name): + """Retrieve the IBuilderControl object for the given Builder.""" + +class IBuilderControl(Interface): + def requestBuild(request): + """Queue a L{buildbot.process.base.BuildRequest} object for later + building.""" + + def requestBuildSoon(request): + """Submit a BuildRequest like requestBuild, but raise a + L{buildbot.interfaces.NoSlaveError} if no slaves are currently + available, so it cannot be used to queue a BuildRequest in the hopes + that a slave will eventually connect. This method is appropriate for + use by things like the web-page 'Force Build' button.""" + + def resubmitBuild(buildStatus, reason="<rebuild, no reason given>"): + """Rebuild something we've already built before. This submits a + BuildRequest to our Builder using the same SourceStamp as the earlier + build. This has no effect (but may eventually raise an exception) if + this Build has not yet finished.""" + + def getPendingBuilds(): + """Return a list of L{IBuildRequestControl} objects for this Builder. + Each one corresponds to a pending build that has not yet started (due + to a scarcity of build slaves). These upcoming builds can be canceled + through the control object.""" + + def getBuild(number): + """Attempt to return an IBuildControl object for the given build. + Returns None if no such object is available. This will only work for + the build that is currently in progress: once the build finishes, + there is nothing to control anymore.""" + + def ping(timeout=30): + """Attempt to contact the slave and see if it is still alive. This + returns a Deferred which fires with either True (the slave is still + alive) or False (the slave did not respond). As a side effect, adds + an event to this builder's column in the waterfall display + containing the results of the ping.""" + # TODO: this ought to live in ISlaveControl, maybe with disconnect() + # or something. However the event that is emitted is most useful in + # the Builder column, so it kinda fits here too. + +class IBuildRequestControl(Interface): + def subscribe(observer): + """Register a callable that will be invoked (with a single + IBuildControl object) for each Build that is created to satisfy this + request. There may be multiple Builds created in an attempt to handle + the request: they may be interrupted by the user or abandoned due to + a lost slave. The last Build (the one which actually gets to run to + completion) is said to 'satisfy' the BuildRequest. The observer will + be called once for each of these Builds, both old and new.""" + def unsubscribe(observer): + """Unregister the callable that was registered with subscribe().""" + def cancel(): + """Remove the build from the pending queue. Has no effect if the + build has already been started.""" + +class IBuildControl(Interface): + def getStatus(): + """Return an IBuildStatus object for the Build that I control.""" + def stopBuild(reason="<no reason given>"): + """Halt the build. This has no effect if the build has already + finished.""" + +class ILogFile(Interface): + """This is the internal interface to a LogFile, used by the BuildStep to + write data into the log. + """ + def addStdout(data): + pass + def addStderr(data): + pass + def addHeader(data): + pass + def finish(): + """The process that is feeding the log file has finished, and no + further data will be added. This closes the logfile.""" + +class ILogObserver(Interface): + """Objects which provide this interface can be used in a BuildStep to + watch the output of a LogFile and parse it incrementally. + """ + + # internal methods + def setStep(step): + pass + def setLog(log): + pass + + # methods called by the LogFile + def logChunk(build, step, log, channel, text): + pass + +class IBuildSlave(Interface): + # this is a marker interface for the BuildSlave class + pass + +class ILatentBuildSlave(IBuildSlave): + """A build slave that is not always running, but can run when requested. + """ + substantiated = Attribute('Substantiated', + 'Whether the latent build slave is currently ' + 'substantiated with a real instance.') + + def substantiate(): + """Request that the slave substantiate with a real instance. + + Returns a deferred that will callback when a real instance has + attached.""" + + # there is an insubstantiate too, but that is not used externally ATM. + + def buildStarted(sb): + """Inform the latent build slave that a build has started. + + ``sb`` is a LatentSlaveBuilder as defined in buildslave.py. The sb + is the one for whom the build started. + """ + + def buildFinished(sb): + """Inform the latent build slave that a build has finished. + + ``sb`` is a LatentSlaveBuilder as defined in buildslave.py. The sb + is the one for whom the build finished. + """ |