diff --git a/etc/build.props b/etc/build.props
--- a/etc/build.props
+++ b/etc/build.props
@@ -1,27 +1,27 @@
title = AFP/Tools
module = $AFP_BASE/tools/lib/classes/afp_tools.jar
requirements = \
env:ISABELLE_SCALA_JAR \
$ISABELLE_HOME_USER/contrib/ci-extras-1/lib/ci-extras.jar
sources = \
tools/admin/afp_build_hugo.scala \
tools/migration/afp_migrate_metadata.scala \
tools/migration/afp_obfuscate_emails.scala \
tools/afp_build.scala \
tools/afp_check_metadata.scala \
tools/afp_check_roots.scala \
tools/afp_dependencies.scala \
tools/afp_site_gen.scala \
tools/afp_structure.scala \
tools/afp_tool.scala \
tools/hugo.scala \
tools/metadata.scala \
tools/rake.scala \
tools/toml.scala \
tools/utils.scala
resources = \
tools/SmartStoplist.txt:SmartStoplist.txt \
tools/migration/public_suffix_list.dat:public_suffix_list.dat
services = \
afp.Tools \
- afp.Build_Tools
+ afp.CI_Builds
diff --git a/tools/afp_build.scala b/tools/afp_build.scala
--- a/tools/afp_build.scala
+++ b/tools/afp_build.scala
@@ -1,576 +1,392 @@
/* Author: Lars Hupel and Fabian Huch, TU Muenchen
Unified AFP CI integration.
*/
package afp
import isabelle._
+import isabelle.CI_Build._
import afp.Metadata.{Email, Entry}
import java.time.{Instant, ZoneId}
import java.time.format.DateTimeFormatter
import java.util.{Map => JMap, Properties => JProperties}
import javax.mail.internet.{InternetAddress, MimeMessage}
import javax.mail.{Authenticator, Message, MessagingException, PasswordAuthentication, Transport, Session => JSession}
import scala.sys.process._
object AFP_Build {
-
- /* Result */
-
- case class Result(rc: Int)
- case object Result {
- def ok: Result = Result(Process_Result.RC.ok)
- def error: Result = Result(Process_Result.RC.error)
- }
-
-
- /* Profile */
-
- case class Profile(threads: Int, jobs: Int, numa: Boolean)
-
- object Profile {
- def from_host: Profile = {
- Isabelle_System.hostname() match {
- case "hpcisabelle" => Profile(8, 8, numa = true)
- case "lxcisa1" => Profile(4, 10, numa = false)
- case _ => Profile(2, 2, numa = false)
- }
- }
- }
-
-
- /* Config */
-
- case class Build_Config(
- documents: Boolean = true,
- clean: Boolean = true,
- include: List[Path] = Nil,
- select: List[Path] = Nil,
- pre_hook: () => Result = () => { Result.ok },
- post_hook: Build.Results => Result = { _ => Result.ok },
- selection: Sessions.Selection = Sessions.Selection.empty
- )
-
-
- /* Status */
-
- object Status {
- def merge(statuses: List[Status]): Status =
- statuses.foldLeft(Ok: Status)(_ merge _)
-
- def from_results(results: Build.Results, session: String): Status =
- if (results.cancelled(session))
- Skipped
- else if (results(session).ok)
- Ok
- else
- Failed
- }
-
- case object Ok extends Status("ok")
- case object Skipped extends Status("skipped")
- case object Failed extends Status("failed")
-
-
- /* Mailing */
+ /* mailing */
case class Mail(subject: String, recipients: List[String], text: String) {
def send(): Unit = {
val user = System.getProperty("mail.smtp.user")
val sender = System.getProperty("mail.smtp.from")
val password = System.getProperty("mail.smtp.password")
System.setProperty("mail.smtp.ssl.protocols", "TLSv1.2")
val authenticator = new Authenticator() {
override def getPasswordAuthentication =
new PasswordAuthentication(user, password)
}
val session = JSession.getDefaultInstance(System.getProperties, authenticator)
val message = new MimeMessage(session)
message.setFrom(new InternetAddress("ci@isabelle.systems", "Isabelle/Jenkins"))
message.setSender(new InternetAddress(sender))
message.setSubject(subject)
message.setText(text, "UTF-8")
message.setSentDate(new java.util.Date())
recipients.foreach { recipient =>
message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient))
}
try {
Transport.send(message)
} catch {
case ex: MessagingException => println(s"Sending mail failed: ${ex.getMessage}")
}
}
}
object Mail {
def apply(subject: String, recipients: List[String], text: String) =
new Mail(subject, recipients, text)
def failed_subject(name: Entry.Name): String = s"Build of AFP entry $name failed"
def failed_text(session_name: String, entry: Entry.Name, isabelle_id: String,
afp_id: String, result: Process_Result): String = s"""
The build for the session
$session_name,
belonging to the AFP entry
$entry
failed.
You are receiving this mail because you are the maintainer of that AFP
entry.
The following information might help you with resolving the problem.
Build log: ${Isabelle_System.getenv("BUILD_URL")}
Isabelle ID: $isabelle_id
AFP ID: $afp_id
Timeout? ${result.timeout}
Exit code: ${result.rc}
Last 50 lines from stdout (if available):
${result.out_lines.takeRight(50).mkString("\n")}
Last 50 lines from stderr (if available):
${result.err_lines.takeRight(50).mkString("\n")}
"""
}
- sealed abstract class Status(val str: String) {
- def merge(that: Status): Status = (this, that) match {
- case (Ok, s) => s
- case (Failed, _) => Failed
- case (Skipped, Failed) => Failed
- case (Skipped, _) => Skipped
- }
- }
-
- /* Metadata tooling */
+ /* metadata tooling */
case class Metadata_Tools private(structure: AFP_Structure, entries: Map[Entry.Name, Entry]) {
def maintainers(name: Entry.Name): List[Email] = {
entries.get(name) match {
case None => Nil
case Some(entry) => entry.notifies
}
}
val sessions: Map[Entry.Name, List[String]] =
entries.values.map(entry =>
entry.name -> structure.entry_sessions(entry.name).map(_.name)).toMap
def session_entry(session_name: String): Option[Entry.Name] = {
val entry = sessions.find { case (_, sessions) => sessions.contains(session_name) }
entry.map { case (name, _) => name }
}
def by_entry(sessions: List[String]): Map[Option[Entry.Name], List[String]] =
sessions.groupBy(session_entry)
def notify(name: Entry.Name, subject: String, text: String): Boolean = {
val recipients = entries.get(name).map(_.notifies).getOrElse(Nil)
if (recipients.nonEmpty) Mail(subject, recipients.map(_.address), text).send()
recipients.nonEmpty
}
}
object Metadata_Tools {
def apply(structure: AFP_Structure, entries: List[Entry]): Metadata_Tools =
new Metadata_Tools(structure, entries.map(entry => entry.name -> entry).toMap)
def load(afp: AFP_Structure): Metadata_Tools = Metadata_Tools(afp, afp.load())
}
- /* Utilities */
+ /* utilities */
def status_as_json(isabelle_id: String, afp_id: String, start_time: String,
status: Map[Option[String], Status]): String = {
val entries_strings = status.collect {
case (Some(entry), status) =>
s"""{"entry": "$entry", "status": "${status.str}"}"""
}
val entries_string = entries_strings.mkString("[", ",\n", "]")
s"""
{"build_data":
{"isabelle_id": "$isabelle_id",
"afp_id": "$afp_id",
"time": "$start_time",
"url": "${Isabelle_System.getenv("BUILD_URL")}",
"job": "${Isabelle_System.getenv("JOB_NAME")}"
},
"entries":
$entries_string
}
"""
}
def status_as_html(status: Map[Option[String], Status]): String = {
val entries_strings = status.collect {
case (None, Failed) =>
s"
Distribution"
case (Some(entry), Failed) =>
s"""$entry"""
}
if (entries_strings.isEmpty)
""
else
entries_strings.mkString("Failed entries: ")
}
private def load_properties(): JProperties = {
val props = new JProperties()
val file_name = Isabelle_System.getenv("ISABELLE_CI_PROPERTIES")
if (file_name != "") {
val file = Path.explode(file_name).file
if (file.exists())
props.load(new java.io.FileReader(file))
props
}
else
props
}
private def compute_timing(results: Build.Results, group: Option[String]): Timing = {
val timings = results.sessions.collect {
case session if group.forall(results.info(session).groups.contains(_)) =>
results(session).timing
}
timings.foldLeft(Timing.zero)(_ + _)
}
- private def with_documents(options: Options, config: Build_Config): Options = {
- if (config.documents)
- options
- .bool.update("browser_info", true)
- .string.update("document", "pdf")
- .string.update("document_variants", "document:outline=/proof,/ML")
- else
- options
- }
-
- final def hg_id(path: Path): String =
- Mercurial.repository(path).id()
-
- final def print_section(title: String): Unit =
- println(s"\n=== $title ===\n")
-
- def ci_profile_build(profile: Profile, config: Build_Config): Unit = {
- val isabelle_home = Path.explode(Isabelle_System.getenv_strict("ISABELLE_HOME"))
- val isabelle_id = hg_id(isabelle_home)
-
- val start_time = Instant.now().atZone(ZoneId.systemDefault).format(DateTimeFormatter.RFC_1123_DATE_TIME)
-
- print_section("CONFIGURATION")
- println(Build_Log.Settings.show())
- val props = load_properties()
- System.getProperties.asInstanceOf[JMap[AnyRef, AnyRef]].putAll(props)
-
- val options =
- with_documents(Options.init(), config)
- .int.update("parallel_proofs", 1)
- .int.update("threads", profile.threads)
-
- println(s"jobs = ${profile.jobs}, threads = ${profile.threads}, numa = ${profile.numa}")
-
- print_section("BUILD")
- println(s"Build started at $start_time")
- println(s"Isabelle id $isabelle_id")
- val pre_result = config.pre_hook()
-
- print_section("LOG")
- val (results, elapsed_time) = {
- val progress = new Console_Progress(verbose = true)
- val start_time = Time.now()
- val results = progress.interrupt_handler {
- Build.build(
- options + "system_heaps",
- selection = config.selection,
- progress = progress,
- clean_build = config.clean,
- verbose = true,
- numa_shuffling = profile.numa,
- max_jobs = profile.jobs,
- dirs = config.include,
- select_dirs = config.select)
- }
- val end_time = Time.now()
- (results, end_time - start_time)
- }
-
- print_section("TIMING")
-
- val groups = results.sessions.map(results.info).flatMap(_.groups)
- for (group <- groups)
- println(s"Group $group: " + compute_timing(results, Some(group)).message_resources)
-
- val total_timing = compute_timing(results, None).copy(elapsed = elapsed_time)
- println("Overall: " + total_timing.message_resources)
-
- if (!results.ok) {
- print_section("FAILED SESSIONS")
-
- for (name <- results.sessions) {
- if (results.cancelled(name)) {
- println(s"Session $name: CANCELLED")
- }
- else {
- val result = results(name)
- if (!result.ok)
- println(s"Session $name: FAILED ${ result.rc }")
- }
- }
- }
-
- val post_result = config.post_hook(results)
-
- System.exit(List(pre_result.rc, results.rc, post_result.rc).max)
- }
-
-
- /* Isabelle CI tools */
-
- def ci_build_afp = Isabelle_Tool("ci_build_afp", "builds the AFP, without slow sessions",
- Scala_Project.here,
- { args =>
- val getopts = Getopts("""
-Usage: isabelle ci_build_afp
-
- Builds the AFP, without slow sessions.
-""")
- getopts(args)
-
- val afp = AFP_Structure()
- val status_file = Path.explode("$ISABELLE_HOME/status.json").file
- val deps_file = Path.explode("$ISABELLE_HOME/dependencies.json").file
-
- val profile = Profile.from_host
- def pre_hook(): Result = {
- println(s"AFP id ${afp.hg_id}")
- if (status_file.exists())
- status_file.delete()
- Result.ok
- }
- def post_hook: Result = {
- print_section("DEPENDENCIES")
- println("Generating dependencies file ...")
- val result = Isabelle_System.bash("isabelle afp_dependencies")
- result.check
- println("Writing dependencies file ...")
- File.write(deps_file, result.out)
- print_section("COMPLETED")
- Result.ok
- }
- val config = Build_Config(clean = false, include = List(afp.thys_dir), pre_hook = pre_hook,
- post_hook = _ => post_hook, selection = Sessions.Selection(
- session_groups = List("AFP"),
- exclude_session_groups = List("slow")))
-
- ci_profile_build(profile, config)
- })
-
-
- def ci_build_all = Isabelle_Tool("ci_build_all", "builds Isabelle + AFP (without slow)",
- Scala_Project.here,
- { args =>
- val getopts = Getopts("""
-Usage: isabelle ci_build_all
-
- Builds Isabelle + AFP (without slow).
- """)
- getopts(args)
-
- val start_time = Instant.now().atZone(ZoneId.systemDefault).format(DateTimeFormatter.RFC_1123_DATE_TIME)
- val afp = AFP_Structure()
- val isabelle_home = Path.explode(Isabelle_System.getenv_strict("ISABELLE_HOME"))
- val isabelle_id = hg_id(isabelle_home)
-
- val status_file = Path.explode("$ISABELLE_HOME/status.json").file
- val report_file = Path.explode("$ISABELLE_HOME/report.html").file
- val deps_file = Path.explode("$ISABELLE_HOME/dependencies.json").file
- def can_send_mails = System.getProperties.containsKey("mail.smtp.host")
+ /* ci build jobs */
- val profile = Profile.from_host
- def pre_hook(): Result = {
- println(s"AFP id ${afp.hg_id}")
- if (status_file.exists())
- status_file.delete()
- if (report_file.exists())
- report_file.delete()
-
- File.write(report_file, "")
-
- if (!can_send_mails) {
- println(s"Mail configuration not found.")
- Result.error
- } else {
- Result.ok
- }
- }
- def post_hook(results: Build.Results): Result = {
- print_section("DEPENDENCIES")
- println("Generating dependencies file ...")
- val result = Isabelle_System.bash("isabelle afp_dependencies")
- result.check
- println("Writing dependencies file ...")
- File.write(deps_file, result.out)
-
- val metadata = Metadata_Tools.load(afp)
+ val afp =
+ Job("afp", "builds the AFP, without slow sessions", Profile.from_host,
+ {
+ val afp = AFP_Structure()
- val status = metadata.by_entry(results.sessions.toList).view.mapValues { sessions =>
- Status.merge(sessions.map(Status.from_results(results, _)))
- }.toMap
-
- print_section("REPORT")
- println("Writing report file ...")
- File.write(report_file, status_as_html(status))
-
- print_section("SITEGEN")
- println("Writing status file ...")
- File.write(status_file, status_as_json(isabelle_id, afp.hg_id, start_time, status))
- println("Running sitegen ...")
+ val status_file = Path.explode("$ISABELLE_HOME/status.json").file
+ val deps_file = Path.explode("$ISABELLE_HOME/dependencies.json").file
- val script = afp.base_dir + Path.explode("admin/sitegen-devel")
- val sitegen_rc = List(script.file.toString, status_file.toString, deps_file.toString).!
- if (sitegen_rc > 0) {
- println("sitegen failed")
- }
+ def pre_hook(): Result = {
+ println(s"AFP id ${ afp.hg_id }")
+ if (status_file.exists())
+ status_file.delete()
+ Result.ok
+ }
- if (!results.ok) {
- print_section("NOTIFICATIONS")
- for (session_name <- results.sessions) {
- val result = results(session_name)
- if (!result.ok && !results.cancelled(session_name) && can_send_mails) {
- metadata.session_entry(session_name).foreach { entry =>
- val subject = Mail.failed_subject(entry)
- val text = Mail.failed_text(session_name, entry, isabelle_id, afp.hg_id, result)
- val notified = metadata.notify(entry, subject, text)
- if (!notified) println(s"Entry $entry: WARNING no maintainers specified")
+ def post_hook(results: Build.Results, start_time: Time): Result = {
+ print_section("DEPENDENCIES")
+ println("Generating dependencies file ...")
+ val result = Isabelle_System.bash("isabelle afp_dependencies")
+ result.check
+ println("Writing dependencies file ...")
+ File.write(deps_file, result.out)
+ print_section("COMPLETED")
+ Result.ok
+ }
+
+ Build_Config(
+ clean = false, include = List(afp.thys_dir), pre_hook = pre_hook,
+ post_hook = post_hook, selection = Sessions.Selection(
+ session_groups = List("AFP"),
+ exclude_session_groups = List("slow")))
+ })
+
+ val all =
+ Job("all", "builds Isabelle + AFP (without slow)", Profile.from_host,
+ {
+ val afp = AFP_Structure()
+ val isabelle_home = Path.explode(Isabelle_System.getenv_strict("ISABELLE_HOME"))
+ val isabelle_id = hg_id(isabelle_home)
+
+ val status_file = Path.explode("$ISABELLE_HOME/status.json").file
+ val report_file = Path.explode("$ISABELLE_HOME/report.html").file
+ val deps_file = Path.explode("$ISABELLE_HOME/dependencies.json").file
+
+ def can_send_mails = System.getProperties.containsKey("mail.smtp.host")
+
+ def pre_hook(): Result = {
+ println(s"AFP id ${ afp.hg_id }")
+ if (status_file.exists())
+ status_file.delete()
+ if (report_file.exists())
+ report_file.delete()
+
+ File.write(report_file, "")
+
+ if (!can_send_mails) {
+ println(s"Mail configuration not found.")
+ Result.error
+ } else {
+ Result.ok
+ }
+ }
+
+ def post_hook(results: Build.Results, start_time: Time): Result = {
+ print_section("DEPENDENCIES")
+ println("Generating dependencies file ...")
+ val result = Isabelle_System.bash("isabelle afp_dependencies")
+ result.check
+ println("Writing dependencies file ...")
+ File.write(deps_file, result.out)
+
+ val metadata = Metadata_Tools.load(afp)
+
+ val status = metadata.by_entry(results.sessions.toList).view.mapValues { sessions =>
+ Status.merge(sessions.map(Status.from_results(results, _)))
+ }.toMap
+
+ print_section("REPORT")
+ println("Writing report file ...")
+ File.write(report_file, status_as_html(status))
+
+ print_section("SITEGEN")
+ println("Writing status file ...")
+ val formatted_time =
+ start_time.instant.atZone(ZoneId.systemDefault)
+ .format(DateTimeFormatter.RFC_1123_DATE_TIME)
+ File.write(status_file, status_as_json(isabelle_id, afp.hg_id, formatted_time, status))
+ println("Running sitegen ...")
+
+ val script = afp.base_dir + Path.explode("admin/sitegen-devel")
+ val sitegen_rc = List(script.file.toString, status_file.toString, deps_file.toString).!
+ if (sitegen_rc > 0) {
+ println("sitegen failed")
+ }
+
+ if (!results.ok) {
+ print_section("NOTIFICATIONS")
+ for (session_name <- results.sessions) {
+ val result = results(session_name)
+ if (!result.ok && !results.cancelled(session_name) && can_send_mails) {
+ metadata.session_entry(session_name).foreach { entry =>
+ val subject = Mail.failed_subject(entry)
+ val text = Mail.failed_text(session_name, entry, isabelle_id, afp.hg_id, result)
+ val notified = metadata.notify(entry, subject, text)
+ if (!notified) println(s"Entry $entry: WARNING no maintainers specified")
+ }
+ }
}
}
- }
- }
-
- print_section("COMPLETED")
- Result(sitegen_rc)
- }
- val config = Build_Config(clean = false, include = List(afp.thys_dir), pre_hook = pre_hook,
- post_hook = post_hook, selection = Sessions.Selection(
- all_sessions = true,
- exclude_session_groups = List("slow")))
-
- ci_profile_build(profile, config)
- })
-
-
- def ci_build_mac = Isabelle_Tool("ci_build_mac", "builds the AFP (without some sessions) on Mac Os",
- Scala_Project.here,
- { args =>
- val getopts = Getopts("""
-Usage: isabelle ci_build_mac
-
- Builds the AFP (without some sessions) on Mac Os.
- """)
- getopts(args)
-
- val afp = AFP_Structure()
-
- val profile = Profile.from_host.copy(threads = 8, jobs = 1)
- def pre_hook(): Result = {
- println(s"Build for AFP id ${afp.hg_id}")
- Result.ok
- }
- val config = Build_Config(include = List(afp.thys_dir), pre_hook = pre_hook, selection =
- Sessions.Selection(
- all_sessions = true,
- exclude_sessions = List("HOL-Proofs", "HOL-ODE-Numerics", "Linear_Programming", "HOL-Nominal-Examples", "HOL-Analysis"),
- exclude_session_groups = List("slow")))
-
- ci_profile_build(profile, config)
- })
-
-
- def ci_build_slow = Isabelle_Tool("ci_build_slow", "builds the AFP slow sessions",
- Scala_Project.here,
- { args =>
- val getopts = Getopts("""
-Usage: isabelle ci_build_slow
-
- Builds the AFP slow sessions.
- """)
- getopts(args)
-
- val afp = AFP_Structure()
- val profile = Profile.from_host.copy(threads = 8, jobs = 1)
- def pre_hook(): Result = {
- println(s"Build for AFP id ${afp.hg_id}")
- Result.ok
- }
- val config = Build_Config(include = List(afp.thys_dir), pre_hook = pre_hook,
- selection = Sessions.Selection(session_groups = List("slow")))
-
- ci_profile_build(profile, config)
- })
-
-
- val ci_build_testboard = Isabelle_Tool("ci_build_testboard", "builds the AFP testboard",
- Scala_Project.here,
- { args =>
- val getopts = Getopts("""
-Usage: isabelle ci_build_testboard
-
- Builds the AFP testboard.
-""")
- getopts(args)
-
- val afp = AFP_Structure()
- val report_file = Path.explode("$ISABELLE_HOME/report.html").file
+ print_section("COMPLETED")
+ Result(sitegen_rc)
+ }
- val profile = Profile.from_host
- def pre_hook(): Result = {
- println(s"AFP id ${ afp.hg_id }")
- if (report_file.exists())
- report_file.delete()
-
- File.write(report_file, "")
- Result.ok
- }
- def post_hook(results: Build.Results): Result = {
- val metadata = Metadata_Tools.load(afp)
+ Build_Config(
+ clean = false, include = List(afp.thys_dir), pre_hook = pre_hook,
+ post_hook = post_hook, selection = Sessions.Selection(
+ all_sessions = true,
+ exclude_session_groups = List("slow")))
+ })
+
+ val mac =
+ Job("mac", "builds the AFP (without some sessions) on Mac Os",
+ Profile.from_host.copy(threads = 8, jobs = 1),
+ {
+ val afp = AFP_Structure()
- val status = metadata.by_entry(results.sessions.toList).view.mapValues { sessions =>
- Status.merge(sessions.map(Status.from_results(results, _)))
- }.toMap
+ def pre_hook(): Result = {
+ println(s"Build for AFP id ${ afp.hg_id }")
+ Result.ok
+ }
- print_section("REPORT")
- println("Writing report file ...")
- File.write(report_file, status_as_html(status))
- print_section("COMPLETED")
- Result.ok
- }
- val config = Build_Config(clean = false, include = List(afp.thys_dir), pre_hook = pre_hook,
- post_hook = post_hook, selection =
- Sessions.Selection(
- all_sessions = true,
- exclude_session_groups = List("slow")))
+ Build_Config(
+ include = List(afp.thys_dir), pre_hook = pre_hook, selection =
+ Sessions.Selection(
+ all_sessions = true,
+ exclude_sessions = List("HOL-Proofs", "HOL-ODE-Numerics", "Linear_Programming", "HOL-Nominal-Examples", "HOL-Analysis"),
+ exclude_session_groups = List("slow")))
+ })
+
+ val slow =
+ Job("slow", "builds the AFP slow sessions", Profile.from_host.copy(threads = 8, jobs = 1),
+ {
+ val afp = AFP_Structure()
- ci_profile_build(profile, config)
- })
-}
\ No newline at end of file
+ def pre_hook(): Result = {
+ println(s"Build for AFP id ${ afp.hg_id }")
+ Result.ok
+ }
+
+ Build_Config(
+ include = List(afp.thys_dir), pre_hook = pre_hook,
+ selection = Sessions.Selection(session_groups = List("slow")))
+ })
+
+ val testboard =
+ Job("testboard", "builds the AFP testboard", Profile.from_host,
+ {
+ val afp = AFP_Structure()
+ val report_file = Path.explode("$ISABELLE_HOME/report.html").file
+
+ def pre_hook(): Result = {
+ println(s"AFP id ${ afp.hg_id }")
+ if (report_file.exists())
+ report_file.delete()
+
+ File.write(report_file, "")
+ Result.ok
+ }
+
+ def post_hook(results: Build.Results, start_time: Time): Result = {
+ val metadata = Metadata_Tools.load(afp)
+
+ val status = metadata.by_entry(results.sessions.toList).view.mapValues { sessions =>
+ Status.merge(sessions.map(Status.from_results(results, _)))
+ }.toMap
+
+ print_section("REPORT")
+ println("Writing report file ...")
+ File.write(report_file, status_as_html(status))
+ print_section("COMPLETED")
+ Result.ok
+ }
+
+ Build_Config(
+ clean = false, include = List(afp.thys_dir), pre_hook = pre_hook,
+ post_hook = post_hook, selection =
+ Sessions.Selection(
+ all_sessions = true,
+ exclude_session_groups = List("slow")))
+ })
+}
+
+class CI_Builds extends Isabelle_CI_Builds(
+ AFP_Build.afp,
+ AFP_Build.all,
+ AFP_Build.slow,
+ AFP_Build.mac,
+ AFP_Build.testboard)
diff --git a/tools/afp_tool.scala b/tools/afp_tool.scala
--- a/tools/afp_tool.scala
+++ b/tools/afp_tool.scala
@@ -1,28 +1,20 @@
package afp
import isabelle._
import afp.migration._
class Admin_Tools extends Isabelle_Scala_Tools(
AFP_Migrate_Metadata.isabelle_tool,
AFP_Obfuscate_Emails.isabelle_tool,
AFP_Build_Hugo.isabelle_tool,
)
class Tools extends Isabelle_Scala_Tools(
AFP_Site_Gen.isabelle_tool,
AFP_Check_Roots.isabelle_tool,
AFP_Check_Metadata.isabelle_tool,
AFP_Dependencies.isabelle_tool,
)
-
-class Build_Tools extends Isabelle_Scala_Tools(
- AFP_Build.ci_build_afp,
- AFP_Build.ci_build_all,
- AFP_Build.ci_build_mac,
- AFP_Build.ci_build_slow,
- AFP_Build.ci_build_testboard
-)
\ No newline at end of file