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 |