src/Pure/Admin/ci_build.scala
changeset 80281 17d2f775907a
parent 80261 e3f472221f8f
child 80411 a9fce67fb8b2
equal deleted inserted replaced
80280:7987b33fb6c5 80281:17d2f775907a
     4 Build profile for continuous integration services.
     4 Build profile for continuous integration services.
     5 */
     5 */
     6 
     6 
     7 package isabelle
     7 package isabelle
     8 
     8 
       
     9 
       
    10 import scala.collection.mutable
     9 
    11 
    10 import java.time.ZoneId
    12 import java.time.ZoneId
    11 import java.time.format.DateTimeFormatter
    13 import java.time.format.DateTimeFormatter
    12 import java.util.{Properties => JProperties, Map => JMap}
    14 import java.util.{Properties => JProperties, Map => JMap}
    13 import java.nio.file.Files
    15 import java.nio.file.Files
    18 
    20 
    19   case class Result(rc: Int)
    21   case class Result(rc: Int)
    20   case object Result {
    22   case object Result {
    21     def ok: Result = Result(Process_Result.RC.ok)
    23     def ok: Result = Result(Process_Result.RC.ok)
    22     def error: Result = Result(Process_Result.RC.error)
    24     def error: Result = Result(Process_Result.RC.error)
    23   }
       
    24 
       
    25 
       
    26   /* executor profile */
       
    27 
       
    28   case class Profile(threads: Int, jobs: Int, numa: Boolean)
       
    29 
       
    30   object Profile {
       
    31     def from_host: Profile = {
       
    32       Isabelle_System.hostname() match {
       
    33         case "hpcisabelle" => Profile(8, 8, numa = true)
       
    34         case "lxcisa1" => Profile(4, 10, numa = false)
       
    35         case _ => Profile(2, 2, numa = false)
       
    36       }
       
    37     }
       
    38   }
    25   }
    39 
    26 
    40 
    27 
    41   /* build config */
    28   /* build config */
    42 
    29 
    60       smtp_port = options.int("ci_mail_smtp_port"),
    47       smtp_port = options.int("ci_mail_smtp_port"),
    61       user = options.string("ci_mail_user"),
    48       user = options.string("ci_mail_user"),
    62       password = options.string("ci_mail_password"))
    49       password = options.string("ci_mail_password"))
    63   }
    50   }
    64 
    51 
       
    52 
    65   /* ci build jobs */
    53   /* ci build jobs */
       
    54 
       
    55   sealed trait Hosts {
       
    56     def hosts_spec: String
       
    57     def max_jobs: Option[Int]
       
    58     def prefs: List[Options.Spec]
       
    59     def numa_shuffling: Boolean
       
    60     def build_cluster: Boolean
       
    61   }
       
    62 
       
    63   case class Local(host_spec: String, jobs: Int, threads: Int, numa_shuffling: Boolean = true)
       
    64     extends Hosts {
       
    65     def hosts_spec: String = host_spec
       
    66     def max_jobs: Option[Int] = Some(jobs)
       
    67     def prefs: List[Options.Spec] = List(Options.Spec.eq("threads", threads.toString))
       
    68     def build_cluster: Boolean = false
       
    69   }
       
    70 
       
    71   case class Cluster(hosts_spec: String, numa_shuffling: Boolean = true) extends Hosts {
       
    72     def max_jobs: Option[Int] = None
       
    73     def prefs: List[Options.Spec] = Nil
       
    74     def build_cluster: Boolean = true
       
    75   }
    66 
    76 
    67   sealed trait Trigger
    77   sealed trait Trigger
    68   case object On_Commit extends Trigger
    78   case object On_Commit extends Trigger
    69 
    79 
    70   object Timed {
    80   object Timed {
    74         val start1 = now.midnight + start_time
    84         val start1 = now.midnight + start_time
    75         (before.time < start0.time && start0.time <= now.time) ||
    85         (before.time < start0.time && start0.time <= now.time) ||
    76           (before.time < start1.time && start1.time <= now.time)
    86           (before.time < start1.time && start1.time <= now.time)
    77       }
    87       }
    78   }
    88   }
    79   
    89 
    80   case class Timed(in_interval: (Date, Date) => Boolean) extends Trigger
    90   case class Timed(in_interval: (Date, Date) => Boolean) extends Trigger
    81 
    91 
    82   sealed case class Job(
    92   sealed case class Job(
    83     name: String,
    93     name: String,
    84     description: String,
    94     description: String,
    85     profile: Profile,
    95     hosts: Hosts,
    86     config: Build_Config,
    96     config: Build_Config,
    87     components: List[String] = Nil,
    97     components: List[String] = Nil,
    88     trigger: Trigger = On_Commit
    98     trigger: Trigger = On_Commit
    89   ) {
    99   ) {
    90     override def toString: String = name
   100     override def toString: String = name
   100     error("Unknown job " + quote(name))
   110     error("Unknown job " + quote(name))
   101 
   111 
   102   val timing =
   112   val timing =
   103     Job(
   113     Job(
   104       "benchmark", "runs benchmark and timing sessions",
   114       "benchmark", "runs benchmark and timing sessions",
   105       Profile(threads = 6, jobs = 1, numa = false),
   115       Local("benchmark", jobs = 1, threads = 6, numa_shuffling = false),
   106       Build_Config(
   116       Build_Config(
   107         documents = false,
   117         documents = false,
   108         select = List(
   118         select = List(
   109           Path.explode("$ISABELLE_HOME/src/Benchmarks")),
   119           Path.explode("$ISABELLE_HOME/src/Benchmarks")),
   110         selection = Sessions.Selection(session_groups = List("timing"))))
   120         selection = Sessions.Selection(session_groups = List("timing"))))
   159     else Mercurial.repository(path).id()
   169     else Mercurial.repository(path).id()
   160 
   170 
   161   def print_section(title: String): Unit =
   171   def print_section(title: String): Unit =
   162     println("\n=== " + title + " ===\n")
   172     println("\n=== " + title + " ===\n")
   163 
   173 
   164   def ci_build(options: Options, job: Job): Unit = {
   174   def ci_build(options: Options, build_hosts: List[Build_Cluster.Host], job: Job): Unit = {
   165     val profile = job.profile
   175     val hosts = job.hosts
   166     val config = job.config
   176     val config = job.config
   167 
   177 
   168     val isabelle_home = Path.explode(Isabelle_System.getenv_strict("ISABELLE_HOME"))
   178     val isabelle_home = Path.explode(Isabelle_System.getenv_strict("ISABELLE_HOME"))
   169     val isabelle_id = hg_id(isabelle_home)
   179     val isabelle_id = hg_id(isabelle_home)
   170 
   180 
   173       DateTimeFormatter.RFC_1123_DATE_TIME)
   183       DateTimeFormatter.RFC_1123_DATE_TIME)
   174 
   184 
   175     print_section("CONFIGURATION")
   185     print_section("CONFIGURATION")
   176     println(Build_Log.Settings.show())
   186     println(Build_Log.Settings.show())
   177 
   187 
   178     val build_options =
   188     val build_options = with_documents(options, config) + "parallel_proofs=1" + "system_heaps"
   179       with_documents(options, config).int.update("threads", profile.threads) +
   189 
   180         "parallel_proofs=1" + "system_heaps"
   190     println(hosts)
   181 
       
   182     println(
       
   183       "jobs = " + profile.jobs + ", threads = " + profile.threads + ", numa = " + profile.numa)
       
   184 
   191 
   185     print_section("BUILD")
   192     print_section("BUILD")
   186     println("Build started at " + formatted_time)
   193     println("Build started at " + formatted_time)
   187     println("Isabelle id " + isabelle_id)
   194     println("Isabelle id " + isabelle_id)
   188     val pre_result = config.pre_hook(options)
   195     val pre_result = config.pre_hook(options)
   191     val (results, elapsed_time) = {
   198     val (results, elapsed_time) = {
   192       val progress = new Console_Progress(verbose = true)
   199       val progress = new Console_Progress(verbose = true)
   193       val start_time = Time.now()
   200       val start_time = Time.now()
   194       val results = progress.interrupt_handler {
   201       val results = progress.interrupt_handler {
   195         Build.build(
   202         Build.build(
   196           build_options,
   203           build_options ++ hosts.prefs,
       
   204           build_hosts = build_hosts,
   197           selection = config.selection,
   205           selection = config.selection,
   198           progress = progress,
   206           progress = progress,
   199           clean_build = config.clean,
   207           clean_build = config.clean,
   200           numa_shuffling = profile.numa,
   208           numa_shuffling = hosts.numa_shuffling,
   201           max_jobs = Some(profile.jobs),
   209           max_jobs = hosts.max_jobs,
   202           dirs = config.include,
   210           dirs = config.include,
   203           select_dirs = config.select)
   211           select_dirs = config.select)
   204       }
   212       }
   205       val end_time = Time.now()
   213       val end_time = Time.now()
   206       (results, end_time - start_time)
   214       (results, end_time - start_time)
   239     Scala_Project.here,
   247     Scala_Project.here,
   240     { args =>
   248     { args =>
   241       /* arguments */
   249       /* arguments */
   242 
   250 
   243       var options = Options.init()
   251       var options = Options.init()
       
   252       val build_hosts = new mutable.ListBuffer[Build_Cluster.Host]
   244 
   253 
   245       val getopts = Getopts("""
   254       val getopts = Getopts("""
   246 Usage: isabelle ci_build [OPTIONS] JOB
   255 Usage: isabelle ci_build [OPTIONS] JOB
   247 
   256 
   248   Options are:
   257   Options are:
       
   258     -H HOSTS     host specifications of the form NAMES:PARAMETERS (separated by commas)
   249     -o OPTION    override Isabelle system OPTION (via NAME=VAL or NAME)
   259     -o OPTION    override Isabelle system OPTION (via NAME=VAL or NAME)
   250 
   260 
   251   Runs Isabelle builds in ci environment, with the following build jobs:
   261   Runs Isabelle builds in ci environment. For cluster builds, build hosts must
       
   262   be passed explicitly (no registry).
       
   263 
       
   264   The following build jobs are available:
   252 
   265 
   253 """ + Library.indent_lines(4, show_jobs) + "\n",
   266 """ + Library.indent_lines(4, show_jobs) + "\n",
   254         "o:" -> (arg => options = options + arg))
   267         "o:" -> (arg => options = options + arg),
       
   268         "H:" -> (arg => build_hosts ++= Build_Cluster.Host.parse(Registry.load(Nil), arg)))
   255 
   269 
   256       val more_args = getopts(args)
   270       val more_args = getopts(args)
   257 
   271 
   258       val job = more_args match {
   272       val job = more_args match {
   259         case job :: Nil => the_job(job)
   273         case job :: Nil => the_job(job)
   260         case _ => getopts.usage()
   274         case _ => getopts.usage()
   261       }
   275       }
   262 
   276 
   263       ci_build(options, job)
   277       ci_build(options, build_hosts.toList, job)
   264     })
   278     })
   265 }
   279 }
   266 
   280 
   267 class Isabelle_CI_Builds(val jobs: CI_Build.Job*) extends Isabelle_System.Service
   281 class Isabelle_CI_Builds(val jobs: CI_Build.Job*) extends Isabelle_System.Service
   268 
   282