From 0bd4b53f10a8edf8d0ff388eb6d0f06c785c88ef Mon Sep 17 00:00:00 2001 From: Maximilian Seesslen Date: Sun, 24 Mar 2024 12:08:32 +0100 Subject: [PATCH] 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 --- CMakeLists.txt | 6 ++- UserManual.md | 11 +++++ src/client.cpp | 15 ++++++- src/laminar.cpp | 89 +++++++++++++++++++++++++++++++++------- src/resources/index.html | 10 ++++- src/resources/style.css | 1 + src/run.cpp | 41 ++++++++++++++++++ src/run.h | 7 +++- 8 files changed, 158 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 594b71b..cbcaa90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() \ No newline at end of file +endif() diff --git a/UserManual.md b/UserManual.md index 0228f46..47f5a7d 100644 --- a/UserManual.md +++ b/UserManual.md @@ -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: diff --git a/src/client.cpp b/src/client.cpp index 26b4a0f..0ded266 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -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; diff --git a/src/laminar.cpp b/src/laminar.cpp index 6b5b281..e20a1bd 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -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 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 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 queued, time_t started, time_t completed, int result, std::string reason, std::string parentJob, uint parentBuild, uint lastRuntimeKnown, uint lastRuntime) { + .fetch( + [&](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 build,time_t started,time_t completed,int result,str reason){ + .fetch([&](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 name,uint number, time_t started, time_t completed, int result, str reason){ + .fetch([&](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 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 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 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 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, 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 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(); diff --git a/src/resources/index.html b/src/resources/index.html index b5e0e8f..c378c6d 100644 --- a/src/resources/index.html +++ b/src/resources/index.html @@ -138,6 +138,8 @@ Started   Duration   Reason   + Branch   + Version   @@ -145,6 +147,8 @@ {{formatDate(job.started)}} {{formatDuration(job.started, job.completed)}} {{job.reason}} + {{job.sourceBranch}} + {{job.sourceVersion}}
@@ -176,8 +180,10 @@
Completed
{{formatDate(job.completed)}}
Duration
{{formatDuration(job.started, job.completed)}}
-
-
Artifacts
+
+
Branch
{{job.sourceBranch}}
+
Version
{{job.sourceVersion}}
+
Artifacts
  • {{art.filename}} [{{ art.size | iecFileSize }}]
  • diff --git a/src/resources/style.css b/src/resources/style.css index 290b0e6..1a44017 100644 --- a/src/resources/style.css +++ b/src/resources/style.css @@ -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) { diff --git a/src/run.cpp b/src/run.cpp index 0a1feaf..ee3f48e 100644 --- a/src/run.cpp +++ b/src/run.cpp @@ -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 ); +} diff --git a/src/run.h b/src/run.h index 7e9e67f..03dc140 100644 --- a/src/run.h +++ b/src/run.h @@ -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 whenStarted() { return startedFork.addBranch(); } kj::Promise whenFinished() { return finishedFork.addBranch(); } @@ -96,6 +99,8 @@ private: kj::Path rootPath; std::string reasonMsg; + std::string sourceBranchStr; + std::string sourceVersionStr; kj::PromiseFulfillerPair started; kj::ForkedPromise startedFork;