added support for build.env

The build scripts can create an file "build.env" in the build folder to
feed back environment variables to laminar.
After the build, the file is parsed by laminar.

The following variables are used by laminar:

- `SOURCE_BRANCH` branch used in the SCM-checkout (e.g git) for building
- `SOURCE_VERSION` release version of the build
pull/206/head
Maximilian Seesslen 2 months ago
parent a1a95c8e7f
commit 0bd4b53f10

@ -59,7 +59,9 @@ endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare" )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=deprecated-declarations" )
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -DDEBUG")
# Allow passing in the version string, for e.g. patched/packaged versions
@ -206,4 +208,4 @@ if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
install(FILES etc/laminar.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/etc)
configure_file(etc/laminar.service.in laminar.service @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/laminar.service DESTINATION ${CMAKE_INSTALL_PREFIX}${SYSTEMD_UNITDIR})
endif()
endif()

@ -706,6 +706,17 @@ Note that definitions in these files are not expanded by a shell, so `FOO="bar"`
Finally, variables supplied on the command-line call to `laminarc queue`, `laminarc start` or `laminarc run` will be available. See [parameterized runs](#Parameterized-runs)
## Feedback environment variables
The build scripts can send back environment variables to laminar. Therefore it
can create an file "build.env" in the build folder. After the build, the file
is parsed.
The following variables are used by laminar:
- `SOURCE_BRANCH` branch that was used in the SCM-checkout (git, svn etc) for building
- `SOURCE_VERSION` release version of the build
## laminarc
`laminarc` commands are:

@ -46,8 +46,11 @@ static int setParams(int argc, char** argv, T& request) {
char* job = getenv("JOB");
char* num = getenv("RUN");
char* reason = getenv("LAMINAR_REASON");
char* srcBranch = getenv("SOURCE_BRANCH");
char* srcVersion = getenv("SOURCE_VERSION");
auto params = request.initParams(n + (job&&num?2:0) + (reason?1:0));
auto params = request.initParams(n + (job&&num?2:0) + (reason?1:0)
+ (srcBranch?1:0) + (srcVersion?1:0) );
for(int i = 0; i < n; ++i) {
char* name = argv[i];
@ -67,7 +70,15 @@ static int setParams(int argc, char** argv, T& request) {
}
if(reason) {
params[n].setName("=reason");
params[n].setValue(reason);
params[n++].setValue(reason);
}
if(srcBranch) {
params[n].setName("=sourceBranch");
params[n++].setValue(srcBranch);
}
if(srcVersion) {
params[n].setName("=sourceVersion");
params[n++].setValue(srcVersion);
}
return argsConsumed;

@ -100,8 +100,33 @@ Laminar::Laminar(Server &server, Settings settings) :
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, "
"startedAt INT, completedAt INT, result INT, output TEXT, "
"outputLen INT, parentJob TEXT, parentBuild INT, reason TEXT, "
"sourceBranch TEXT, sourceVersion TEXT,"
"PRIMARY KEY (name, number DESC))";
db->exec(create_table_stmt);
// Update 'sourceBranch' if not existing;
db->stmt(
"SELECT COUNT(*) AS CNT FROM pragma_table_info('builds')"
" WHERE name='sourceBranch'")
.fetch<int>([&](int has_source_branch) {
if( !has_source_branch )
{
LLOG(WARNING, "Updating database to contain 'sourceBranch'");
db->exec( "ALTER TABLE 'builds' ADD COLUMN 'sourceBranch' TEXT");
}
});
// Update 'sourceVersion' if not existing;
db->stmt(
"SELECT COUNT(*) AS CNT FROM pragma_table_info('builds')"
" WHERE name='sourceVersion'")
.fetch<int>([&](int has_source_version) {
if( !has_source_version )
{
LLOG(WARNING, "Updating database to contain 'sourceVersion'");
db->exec( "ALTER TABLE 'builds' ADD COLUMN 'sourceVersion' TEXT");
}
});
// Migrate from (name, number) primary key to (name, number DESC).
// SQLite does not allow to alter primary key of existing table, so
@ -250,17 +275,22 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("time", time(nullptr));
j.startObject("data");
if(scope.type == MonitorScope::RUN) {
db->stmt("SELECT queuedAt,startedAt,completedAt,result,reason,parentJob,parentBuild,q.lr IS NOT NULL,q.lr FROM builds "
db->stmt("SELECT queuedAt,startedAt,completedAt,result,reason,sourceBranch,sourceVersion,parentJob,parentBuild,q.lr IS NOT NULL,q.lr FROM builds "
"LEFT JOIN (SELECT name n, MAX(number), completedAt-startedAt lr FROM builds WHERE result IS NOT NULL GROUP BY n) q ON q.n = name "
"WHERE name = ? AND number = ?")
.bind(scope.job, scope.num)
.fetch<time_t, time_t, time_t, int, std::string, std::string, uint, uint, uint>([&](time_t queued, time_t started, time_t completed, int result, std::string reason, std::string parentJob, uint parentBuild, uint lastRuntimeKnown, uint lastRuntime) {
.fetch<time_t, time_t, time_t, int, std::string, std::string, std::string, std::string, uint, uint, uint>(
[&](time_t queued, time_t started, time_t completed, int result,
std::string reason, std::string sourceBranch, std::string sourceVersion,
std::string parentJob, uint parentBuild, uint lastRuntimeKnown, uint lastRuntime) {
j.set("queued", queued);
j.set("started", started);
if(completed)
j.set("completed", completed);
j.set("result", to_string(completed ? RunState(result) : started ? RunState::RUNNING : RunState::QUEUED));
j.set("reason", reason);
j.set("sourceBranch", sourceBranch);
j.set("sourceVersion", sourceVersion);
j.startObject("upstream").set("name", parentJob).set("num", parentBuild).EndObject(2);
if(lastRuntimeKnown)
j.set("etc", started + lastRuntime);
@ -287,18 +317,20 @@ std::string Laminar::getStatus(MonitorScope scope) {
order_by = "(completedAt-startedAt) " + direction + ", number DESC";
else
order_by = "number DESC";
std::string stmt = "SELECT number,startedAt,completedAt,result,reason FROM builds "
std::string stmt = "SELECT number,startedAt,completedAt,result,reason,sourceBranch,sourceVersion FROM builds "
"WHERE name = ? AND result IS NOT NULL ORDER BY "
+ order_by + " LIMIT ?,?";
db->stmt(stmt.c_str())
.bind(scope.job, scope.page * runsPerPage, runsPerPage)
.fetch<uint,time_t,time_t,int,str>([&](uint build,time_t started,time_t completed,int result,str reason){
.fetch<uint,time_t,time_t,int,str,str,str>([&](uint build,time_t started,time_t completed,int result,str reason,str sourceBranch,str sourceVersion){
j.StartObject();
j.set("number", build)
.set("completed", completed)
.set("started", started)
.set("result", to_string(RunState(result)))
.set("reason", reason)
.set("sourceBranch", sourceBranch)
.set("sourceVersion", sourceVersion)
.EndObject();
});
j.EndArray();
@ -323,6 +355,8 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("started", run->startedAt);
j.set("result", to_string(RunState::RUNNING));
j.set("reason", run->reason());
j.set("sourceBranch", run->sourceBranch());
j.set("sourceVersion", run->sourceVersion());
j.EndObject();
}
j.EndArray();
@ -333,6 +367,8 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("number", run->build);
j.set("result", to_string(RunState::QUEUED));
j.set("reason", run->reason());
j.set("sourceBranch", run->sourceBranch());
j.set("sourceVersion", run->sourceVersion());
j.EndObject();
}
}
@ -358,9 +394,12 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("description", desc == jobDescriptions.end() ? "" : desc->second);
} else if(scope.type == MonitorScope::ALL) {
j.startArray("jobs");
db->stmt("SELECT name, number, startedAt, completedAt, result, reason "
db->stmt("SELECT name, number, startedAt, completedAt, result, reason, "
"sourceBranch, sourceVersion "
"FROM builds GROUP BY name HAVING number = MAX(number)")
.fetch<str,uint,time_t,time_t,int,str>([&](str name,uint number, time_t started, time_t completed, int result, str reason){
.fetch<str,uint,time_t,time_t,int,str,str,str>([&](str name,uint number,
time_t started, time_t completed, int result, str reason,
str sourceBranch, str sourceVersion){
j.StartObject();
j.set("name", name);
j.set("number", number);
@ -368,6 +407,8 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.set("started", started);
j.set("completed", completed);
j.set("reason", reason);
j.set("sourceBranch", sourceBranch);
j.set("sourceVersion", sourceVersion);
j.EndObject();
});
j.EndArray();
@ -387,8 +428,11 @@ std::string Laminar::getStatus(MonitorScope scope) {
j.EndObject();
} else { // Home page
j.startArray("recent");
db->stmt("SELECT name,number,node,queuedAt,startedAt,completedAt,result,reason FROM builds WHERE completedAt IS NOT NULL ORDER BY completedAt DESC LIMIT 20")
.fetch<str,uint,str,time_t,time_t,time_t,int,str>([&](str name,uint build,str context,time_t queued,time_t started,time_t completed,int result,str reason){
db->stmt("SELECT name,number,node,queuedAt,startedAt,completedAt,result,"
"reason,sourceBranch,sourceVersion FROM builds WHERE completedAt IS NOT NULL ORDER BY completedAt DESC LIMIT 20")
.fetch<str,uint,str,time_t,time_t,time_t,int,str,str,str>
([&](str name,uint build,str context,time_t queued,time_t started,
time_t completed,int result,str reason,str sourceBranch,str sourceVersion){
j.StartObject();
j.set("name", name)
.set("number", build)
@ -398,6 +442,8 @@ std::string Laminar::getStatus(MonitorScope scope) {
.set("completed", completed)
.set("result", to_string(RunState(result)))
.set("reason", reason)
.set("sourceBranch", sourceBranch)
.set("sourceVersion", sourceVersion)
.EndObject();
});
j.EndArray();
@ -620,8 +666,10 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params, bool f
else
queuedJobs.push_back(run);
db->stmt("INSERT INTO builds(name,number,queuedAt,parentJob,parentBuild,reason) VALUES(?,?,?,?,?,?)")
.bind(run->name, run->build, run->queuedAt, run->parentName, run->parentBuild, run->reason())
db->stmt("INSERT INTO builds(name,number,queuedAt,parentJob,parentBuild,reason,sourceBranch,sourceVersion) VALUES(?,?,?,?,?,?,?,?)")
.bind(run->name, run->build, run->queuedAt, run->parentName,
run->parentBuild, run->reason(),
run->sourceBranch(), run->sourceVersion())
.exec();
// notify clients
@ -633,6 +681,8 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params, bool f
.set("result", to_string(RunState::QUEUED))
.set("queueIndex", frontOfQueue ? 0 : (queuedJobs.size() - 1))
.set("reason", run->reason())
.set("sourceBranch", run->sourceBranch())
.set("sourceVersion", run->sourceVersion())
.EndObject();
http->notifyEvent(j.str(), name.c_str());
@ -722,7 +772,10 @@ bool Laminar::tryStartRun(std::shared_ptr<Run> run, int queueIndex) {
.set("queued", run->queuedAt)
.set("started", run->startedAt)
.set("number", run->build)
.set("reason", run->reason());
.set("reason", run->reason())
.set("sourceBranch", run->sourceBranch())
.set("sourceVersion", run->sourceVersion());
db->stmt("SELECT completedAt - startedAt FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 1")
.bind(run->name)
.fetch<uint>([&](uint etc){
@ -767,9 +820,13 @@ void Laminar::handleRunFinished(Run * r) {
std::swap(maybeZipped, zipped);
}
}
db->stmt("UPDATE builds SET completedAt = ?, result = ?, output = ?, outputLen = ? WHERE name = ? AND number = ?")
.bind(completedAt, int(r->result), maybeZipped, logsize, r->name, r->build)
r->digestBuildEnv( *fsHome );
db->stmt("UPDATE builds SET completedAt = ?, result = ?, output = ?, outputLen = ? "
", sourceBranch = ?, sourceVersion = ? WHERE name = ? AND number = ?")
.bind(completedAt, int(r->result), maybeZipped, logsize,
r->sourceBranch(), r->sourceVersion(), r->name, r->build)
.exec();
// notify clients
@ -782,7 +839,9 @@ void Laminar::handleRunFinished(Run * r) {
.set("completed", completedAt)
.set("started", r->startedAt)
.set("result", to_string(r->result))
.set("reason", r->reason());
.set("reason", r->reason())
.set("sourceBranch", r->sourceBranch())
.set("sourceVersion", r->sourceVersion());
j.startArray("artifacts");
populateArtifacts(j, r->name, r->build);
j.EndArray();

@ -138,6 +138,8 @@
<th class="text-center">Started <a class="sort" :class="(sort.field=='started'?sort.order:'')" v-on:click="do_sort('started')">&nbsp;</a></th>
<th class="text-center">Duration <a class="sort" :class="(sort.field=='duration'?sort.order:'')" v-on:click="do_sort('duration')">&nbsp;</a></th>
<th class="text-center vp-sm-hide">Reason <a class="sort" :class="(sort.field=='reason'?sort.order:'')" v-on:click="do_sort('reason')">&nbsp;</a></th>
<th class="text-center vp-sm-hide">Branch <a class="sort" :class="(sort.field=='sourceBranch'?sort.order:'')" v-on:click="do_sort('sourceBranch')">&nbsp;</a></th>
<th class="text-center vp-sm-hide">Version <a class="sort" :class="(sort.field=='sourceVersion'?sort.order:'')" v-on:click="do_sort('sourceVersion')">&nbsp;</a></th>
</tr></thead>
<tr v-for="job in jobsQueued.concat(jobsRunning).concat(jobsRecent)" track-by="$index">
<td style="width:1px"><span v-html="runIcon(job.result)"></span></td>
@ -145,6 +147,8 @@
<td class="text-center"><span v-if="job.result!='queued'">{{formatDate(job.started)}}</span></td>
<td class="text-center"><span v-if="job.result!='queued'">{{formatDuration(job.started, job.completed)}}</span></td>
<td class="text-center vp-sm-hide">{{job.reason}}</td>
<td class="text-center vp-sm-hide">{{job.sourceBranch}}</td>
<td class="text-center vp-sm-hide">{{job.sourceVersion}}</td>
</tr>
</table>
<div style="float: right; margin: 15px; display: inline-grid; grid-auto-flow: column; gap: 10px; align-items: center">
@ -176,8 +180,10 @@
<dt v-show="runComplete(job)">Completed</dt><dd v-show="job.completed">{{formatDate(job.completed)}}</dd>
<dt v-show="job.started">Duration</dt><dd v-show="job.started">{{formatDuration(job.started, job.completed)}}</dd>
</dl>
<dl v-show="job.artifacts.length">
<dt>Artifacts</dt>
<dl>
<dt v-show="job.sourceBranch.length">Branch</dt><dd>{{job.sourceBranch}}</dd>
<dt v-show="job.sourceVersion.length">Version</dt><dd>{{job.sourceVersion}}</dd>
<dt v-show="job.artifacts.length">Artifacts</dt>
<dd>
<ul style="margin-bottom: 0">
<li v-for="art in job.artifacts"><a :href="art.url" target="_self">{{art.filename}}</a> [{{ art.size | iecFileSize }}]</li>

@ -318,6 +318,7 @@ button:not([disabled]) { cursor: pointer; color: var(--main-fg); }
#page-run-detail {
display: grid;
grid-template-columns: minmax(400px, auto) 1fr;
align-items: start;
gap: 5px;
}
@media (max-width: 780px) {

@ -70,6 +70,10 @@ Run::Run(std::string name, uint num, ParamMap pm, kj::Path&& rootPath) :
parentBuild = atoi(it->second.c_str());
} else if(it->first == "=reason") {
reasonMsg = it->second;
} else if(it->first == "=sourceBranch") {
sourceBranchStr = it->second;
} else if(it->first == "=sourceVersion") {
sourceVersionStr = it->second;
} else {
LLOG(ERROR, "Unknown internal job parameter", it->first);
}
@ -200,6 +204,14 @@ std::string Run::reason() const {
return reasonMsg;
}
std::string Run::sourceBranch() const {
return sourceBranchStr;
}
std::string Run::sourceVersion() const {
return sourceVersionStr;
}
bool Run::abort() {
// if the Maybe is empty, wait() was already called on this process
KJ_IF_MAYBE(p, pid) {
@ -208,3 +220,32 @@ bool Run::abort() {
}
return false;
}
bool Run::digestBuildEnv(const kj::Directory &fsHome)
{
kj::Path runDir{"run"};
std::string runNumStr = std::to_string(build);
// Maybe set from previous build
unsetenv("SOURCE_BRANCH");
unsetenv("SOURCE_VERSION");
if( ! fsHome.exists( runDir/name/runNumStr/"build.env" ) )
{
return( false );
}
setEnvFromFile(rootPath, runDir/name/runNumStr/"build.env");
if(getenv("SOURCE_BRANCH"))
{
sourceBranchStr=getenv("SOURCE_BRANCH");
}
if(getenv("SOURCE_VERSION"))
{
sourceVersionStr=getenv("SOURCE_VERSION");
}
return( true );
}

@ -61,8 +61,11 @@ public:
// aborts this run
bool abort();
bool digestBuildEnv(const kj::Directory &fsHome);
std::string reason() const;
std::string sourceBranch() const;
std::string sourceVersion() const;
kj::Promise<void> whenStarted() { return startedFork.addBranch(); }
kj::Promise<RunState> whenFinished() { return finishedFork.addBranch(); }
@ -96,6 +99,8 @@ private:
kj::Path rootPath;
std::string reasonMsg;
std::string sourceBranchStr;
std::string sourceVersionStr;
kj::PromiseFulfillerPair<void> started;
kj::ForkedPromise<void> startedFork;

Loading…
Cancel
Save