env.ck 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. /*++
  2. Copyright (c) 2016 Minoca Corp. All Rights Reserved
  3. Module Name:
  4. env.ck
  5. Abstract:
  6. This build module contains the environment and functions used throughout
  7. the Minoca OS build.
  8. Author:
  9. Evan Green 14-Apr-2016
  10. Environment:
  11. Build
  12. --*/
  13. TRUE = 1;
  14. FALSE = 0;
  15. arch ?= getenv("ARCH");
  16. //debug ?= getenv("DEBUG");
  17. variant ?= getenv("VARIANT");
  18. arch ?= "x86";
  19. debug ?= "dbg";
  20. variant ?= "";
  21. release_level ?= "SystemReleaseDevelopment";
  22. outroot = "^/../..";
  23. binroot = outroot + "/bin";
  24. stripped_dir = binroot + "/stripped";
  25. cflags ?= getenv("CFLAGS") ? [getenv("CFLAGS")] : ["-Wall", "-Werror"];
  26. cppflags ?= getenv("CPPFLAGS") ? [getenv("CPPFLAGS")] : [];
  27. ldflags ?= getenv("LDFLAGS") ? [getenv("LDFLAGS")] : [];
  28. asflags ?= getenv("ASFLAGS") ? [getenv("ASLAGS")] : [];
  29. global_config = {
  30. "ARCH": arch,
  31. "DEBUG": debug,
  32. "VARIANT": variant,
  33. "BUILD_CC": getenv("BUILD_CC"),
  34. "BUILD_AR": getenv("BUILD_AR"),
  35. "BUILD_OBJCOPY": getenv("BUILD_OBJCOPY"),
  36. "BUILD_STRIP": getenv("BUILD_STRIP"),
  37. "CC": getenv("CC"),
  38. "AR": getenv("AR"),
  39. "OBJCOPY": getenv("OBJCOPY"),
  40. "STRIP": getenv("STRIP"),
  41. "RCC": getenv("RCC"),
  42. "IASL", getenv("IASL"),
  43. "SHELL": getenv("SHELL"),
  44. };
  45. build_os ?= uname_s();
  46. build_arch ?= uname_m();
  47. if (build_arch == "i686") {
  48. build_arch = "x86";
  49. } else if (build_arch == "x86-64") {
  50. build_arch = "x64";
  51. }
  52. assert(((arch == "x86") || (arch == "armv7") ||
  53. (arch == "armv6") || (arch == "x64")),
  54. "Invalid architecture");
  55. assert(((build_arch == "x86") || (build_arch == "armv7") ||
  56. (build_arch == "armv6") || (build_arch == "x64")),
  57. "Unknown build architecture");
  58. assert(((debug == "dbg") || (debug == "rel")), "Invalid debug setting");
  59. //
  60. // Set up build architecture-specific globals.
  61. //
  62. build_bfd_arch = "";
  63. build_obj_format = "";
  64. if (build_arch == "x86") {
  65. build_bfd_arch = "i386";
  66. build_obj_format = "elf32-i386";
  67. if (build_os == "Windows") {
  68. build_obj_format = "pe-i386";
  69. }
  70. } else if ((build_arch == "armv7") || (build_arch == "armv6")) {
  71. build_bfd_arch = "arm";
  72. build_obj_format = "elf32-littlearm";
  73. } else if (build_arch == "x64") {
  74. build_bfd_arch = "x86-64";
  75. build_obj_format = "elf64-x86-64";
  76. } else {
  77. print("Set build_bfd_arch and build_obj_format for new architecture.\n");
  78. }
  79. //
  80. // Set up target architecture-specific globals.
  81. //
  82. bfd_arch = "";
  83. obj_format = "";
  84. if (arch == "x86") {
  85. bfd_arch = "i386";
  86. obj_format = "elf32-i386";
  87. if (variant == "q") {
  88. tool_prefix = "i586-pc-minoca-";
  89. } else {
  90. tool_prefix = "i686-pc-minoca-";
  91. }
  92. } else if ((arch == "armv7") || (arch == "armv6")) {
  93. bfd_arch = "arm";
  94. obj_format = "elf32-littlearm";
  95. tool_prefix = "arm-none-minoca-";
  96. } else if (arch == "x64") {
  97. bfd_arch = "x86-64";
  98. obj_format = "elf64-x86-64";
  99. tool_prefix = "x86_64-pc-minoca-";
  100. }
  101. //
  102. // Set a default build compiler if none was set.
  103. //
  104. global_config["BUILD_CC"] ?= "gcc";
  105. global_config["BUILD_AR"] ?= "ar";
  106. global_config["BUILD_OBJCOPY"] ?= "objcopy";
  107. global_config["BUILD_STRIP"] ?= "strip";
  108. global_config["RCC"] ?= "windres";
  109. global_config["IASL"] ?= "iasl";
  110. global_config["SHELL"] ?= "sh";
  111. echo = "echo";
  112. //
  113. // Pin down CFLAGS and the like.
  114. //
  115. if (debug == "dbg") {
  116. cflags += ["-O1", "-DDEBUG=1"];
  117. } else {
  118. cflags += ["-O2", "-Wno-unused-but-set-variable", "-DNDEBUG"];
  119. }
  120. cflags += ["-fno-builtin",
  121. "-fno-omit-frame-pointer",
  122. "-g",
  123. "-save-temps=obj",
  124. "-ffunction-sections",
  125. "-fdata-sections",
  126. "-fvisibility=hidden"];
  127. cppflags += ["-I$//include"];
  128. build_cflags = cflags + [];
  129. build_cppflags = cppflags + [];
  130. cflags += ["-fpic"];
  131. if (build_os == "Windows") {
  132. build_cflags += ["-mno-ms-bitfields"];
  133. } else {
  134. build_cflags += ["-fpic"];
  135. }
  136. if (arch == "armv6") {
  137. cppflags += ["-march=armv6zk", "-marm", "-mfpu=vfp"];
  138. }
  139. if (arch == "x86") {
  140. cflags += ["-mno-ms-bitfields"];
  141. if (variant == "q") {
  142. cppflags += ["-Wa,-momit-lock-prefix=yes", "-march=i586"];
  143. }
  144. }
  145. asflags += ["-Wa,-g"];
  146. ldflags += ["-Wl,--gc-sections"];
  147. objcopy_flags ?= [
  148. "--rename-section .data=.rodata,alloc,load,data,contents,readonly",
  149. "-I binary",
  150. "-O " + obj_format,
  151. "-B " + bfd_arch
  152. ];
  153. build_objcopy_flags ?= [
  154. "--rename-section .data=.rodata,alloc,load,data,contents,readonly",
  155. "-I binary",
  156. "-O " + build_obj_format,
  157. "-B " + build_bfd_arch
  158. ];
  159. if (build_os == "Windows") {
  160. build_objcopy_flags += ["--prefix-symbols=_"];
  161. }
  162. //
  163. // Set a default target compiler if one was not set. On Minoca building its own
  164. // architecture, use the native compiler.
  165. //
  166. if ((build_os == "Minoca") && (build_arch == arch)) {
  167. global_config["CC"] ?= global_config["BUILD_CC"];
  168. global_config["AR"] ?= global_config["BUILD_AR"];
  169. global_config["OBJCOPY"] ?= global_config["BUILD_OBJCOPY"];
  170. global_config["STRIP"] ?= global_config["BUILD_STRIP"];
  171. } else {
  172. global_config["CC"] ?= tool_prefix + "gcc";
  173. global_config["AR"] ?= tool_prefix + "ar";
  174. global_config["OBJCOPY"] ?= tool_prefix + "objcopy";
  175. global_config["STRIP"] ?= tool_prefix + "strip";
  176. }
  177. global_config["BASE_CFLAGS"] = cflags;
  178. global_config["BASE_CPPFLAGS"] = cppflags;
  179. global_config["BASE_LDFLAGS"] = ldflags;
  180. global_config["BASE_ASFLAGS"] = asflags;
  181. global_config["OBJCOPY_FLAGS"] = objcopy_flags;
  182. global_config["BUILD_BASE_CFLAGS"] = build_cflags;
  183. global_config["BUILD_BASE_CPPFLAGS"] = build_cppflags;
  184. global_config["BUILD_BASE_LDFLAGS"] = ldflags;
  185. global_config["BUILD_BASE_ASFLAGS"] = asflags;
  186. global_config["BUILD_OBJCOPY_FLAGS"] = build_objcopy_flags;
  187. global_config["IASL_FLAGS"] = ["-we"];
  188. //
  189. // Add a config value, creating it if it does not exist.
  190. //
  191. function add_config(entry, name, value) {
  192. entry["config"] ?= {};
  193. entry["config"][name] ?= [];
  194. entry["config"][name] += [value];
  195. return;
  196. }
  197. //
  198. // Create a phony group target that depends on all the given input entries.
  199. //
  200. function group(name, entries) {
  201. entry = {
  202. "label": name,
  203. "type": "target",
  204. "tool": "phony",
  205. "inputs": entries,
  206. "config": {}
  207. };
  208. return [entry];
  209. }
  210. //
  211. // Create a copy target.
  212. //
  213. function copy(source, destination, destination_label, flags, mode) {
  214. config = {};
  215. if (flags) {
  216. config["CPFLAGS"] = flags;
  217. }
  218. if (mode) {
  219. config["CHMOD_FLAGS"] = mode;
  220. }
  221. entry = {
  222. "type": "target",
  223. "tool": "copy",
  224. "label": destination_label,
  225. "inputs": [source],
  226. "output": destination,
  227. "config": config
  228. };
  229. if (!destination_label) {
  230. entry["label"] = destination;
  231. }
  232. return [entry];
  233. }
  234. //
  235. // Add a stripped version of the target.
  236. //
  237. function strip(params) {
  238. tool_name = "strip";
  239. if (get(params, "build")) {
  240. tool_name = "build_strip";
  241. }
  242. params["type"] = "target";
  243. params["tool"] = tool_name;
  244. return [params];
  245. }
  246. //
  247. // Replace the current target with a copied version in the bin directory. Also
  248. // strip unless told not to.
  249. //
  250. function binplace(params) {
  251. label = get(params, "label");
  252. label ?= get(params, "output");
  253. source = get(params, "output");
  254. source ?= label;
  255. assert(label && source, "Label or output must be defined");
  256. build = get(params, "build");
  257. //
  258. // Set the output since the label is going to be renamed and create the
  259. // copy target.
  260. //
  261. params["output"] = source;
  262. file_name = basename(source);
  263. if (build) {
  264. destination = binroot + "/tools/bin/" + file_name;
  265. } else {
  266. destination = binroot + "/" + file_name;
  267. }
  268. cpflags = get(params, "cpflags");
  269. mode = get(params, "chmod");
  270. new_original_label = label + "_orig";
  271. original_target = ":" + new_original_label;
  272. copied_entry = copy(original_target, destination, label, cpflags, mode)[0];
  273. //
  274. // The original label was given to the copied destination, so tack a _orig
  275. // on the source label.
  276. //
  277. params["label"] = new_original_label;
  278. entries = [copied_entry, params];
  279. //
  280. // Unless asked not to, create a stripped entry as well.
  281. //
  282. if (!get(params, "nostrip")) {
  283. if (build) {
  284. stripped_output = stripped_dir + "/build/" + file_name;
  285. } else {
  286. stripped_output = stripped_dir + "/" + file_name;
  287. }
  288. stripped_entry = {
  289. "label": label + "_stripped",
  290. "inputs": [original_target],
  291. "output": stripped_output,
  292. "build": get(params, "build"),
  293. };
  294. //
  295. // Make the binplaced copy depend on the stripped version.
  296. //
  297. copied_entry["implicit"] = [":" + stripped_entry["label"]];
  298. entries += strip(stripped_entry);
  299. }
  300. return entries;
  301. }
  302. //
  303. // Create a group of object file targets from source file targets. Returns a
  304. // list of the object names in the first element and the object file target
  305. // entries in the second element.
  306. //
  307. function compiled_sources(params) {
  308. inputs = params["inputs"];
  309. objs = [];
  310. entries = [];
  311. sources_config = get(params, "sources_config");
  312. prefix = get(params, "prefix");
  313. build = get(params, "build");
  314. includes = get(params, "includes");
  315. if (includes) {
  316. sources_config ?= {};
  317. sources_config["CPPFLAGS"] ?= [];
  318. for (include in includes) {
  319. sources_config["CPPFLAGS"] += ["-I" + include];
  320. }
  321. }
  322. assert(len(inputs) != 0, "Compilation must have inputs");
  323. for (input in inputs) {
  324. input_parts = split_extension(input);
  325. ext = input_parts[1];
  326. suffix = ".o";
  327. if (ext == "c") {
  328. tool = "cc";
  329. } else if (ext == "cc") {
  330. tool = "cxx";
  331. } else if (ext == "S") {
  332. tool = "as";
  333. } else if (ext == "rc") {
  334. tool = "rcc";
  335. suffix = ".rsc";
  336. } else {
  337. objs += [input];
  338. continue;
  339. }
  340. if (build) {
  341. tool = "build_" + tool;
  342. }
  343. obj_name = input_parts[0] + suffix;
  344. if (prefix) {
  345. obj_name = prefix + "/" + obj_name;
  346. }
  347. obj = {
  348. "type": "target",
  349. "label": obj_name,
  350. "output": obj_name,
  351. "inputs": [input],
  352. "tool": tool,
  353. "config": sources_config,
  354. };
  355. entries += [obj];
  356. objs += [":" + obj_name];
  357. }
  358. if (prefix) {
  359. params["output"] ?= params["label"];
  360. params["output"] = prefix + "/" + params["output"];
  361. }
  362. return [objs, entries];
  363. }
  364. //
  365. // Vanilla link of a set of sources into some sort of executable or shared
  366. // object.
  367. //
  368. function executable(params) {
  369. build = get(params, "build");
  370. compilation = compiled_sources(params);
  371. objs = compilation[0];
  372. entries = compilation[1];
  373. params["type"] = "target";
  374. params["inputs"] = objs;
  375. params["tool"] = "ld";
  376. if (build) {
  377. params["tool"] = "build_ld";
  378. }
  379. //
  380. // Convert options for text_address, linker_script, and entry to actual
  381. // LDFLAGS.
  382. //
  383. text_address = get(params, "text_address");
  384. if (text_address) {
  385. text_option = "-Wl,-Ttext-segment=" + text_address +
  386. " -Wl,-Ttext=" + text_address;
  387. add_config(params, "LDFLAGS", text_option);
  388. }
  389. linker_script = get(params, "linker_script");
  390. if (linker_script) {
  391. script_option = "-Wl,-T" + linker_script;
  392. add_config(params, "LDFLAGS", script_option);
  393. }
  394. entry = get(params, "entry");
  395. if (entry) {
  396. entry_option = "-Wl,-e" + entry + " -Wl,-u" + entry;
  397. add_config(params, "LDFLAGS", entry_option);
  398. }
  399. if (get(params, "binplace")) {
  400. entries += binplace(params);
  401. } else {
  402. entries += [params];
  403. }
  404. return entries;
  405. }
  406. //
  407. // Creates a regular position independent application.
  408. //
  409. function application(params) {
  410. build = get(params, "build");
  411. exename = get(params, "output");
  412. exename ?= params["label"];
  413. if (build && (build_os == "Windows")) {
  414. params["output"] = exename + ".exe";
  415. }
  416. add_config(params, "LDFLAGS", "-pie");
  417. params["binplace"] ?= TRUE;
  418. return executable(params);
  419. }
  420. //
  421. // Creates a shared library or DLL.
  422. //
  423. function shared_library(params) {
  424. build = get(params, "build");
  425. soname = get(params, "output");
  426. soname ?= params["label"];
  427. major_version = get(params, "major_version");
  428. add_config(params, "LDFLAGS", "-shared");
  429. if ((!build) || (build_os != "Windows")) {
  430. soname += ".so";
  431. if (major_version != null) {
  432. soname += "." + major_version;
  433. }
  434. add_config(params, "LDFLAGS", "-Wl,-soname=" + soname);
  435. } else {
  436. soname += ".dll";
  437. }
  438. params["output"] = soname;
  439. params["binplace"] ?= TRUE;
  440. return executable(params);
  441. }
  442. //
  443. // Creates a static archive.
  444. //
  445. function static_library(params) {
  446. compilation = compiled_sources(params);
  447. objs = compilation[0];
  448. entries = compilation[1];
  449. params["type"] = "target";
  450. output = get(params, "output");
  451. output ?= params["label"];
  452. params["output"] = output + ".a";
  453. params["inputs"] = objs;
  454. params["tool"] = "ar";
  455. if (build) {
  456. params["tool"] = "build_ar";
  457. }
  458. if (get(params, "binplace")) {
  459. entries += binplace(params);
  460. } else {
  461. entries += [params];
  462. }
  463. return entries;
  464. }
  465. //
  466. // Create a list of compiled .aml files from a list of asl files. Returns a
  467. // list where the first element is a list of all the resulting target names,
  468. // and the second element is a list of the target entries.
  469. //
  470. function compiled_asl(inputs) {
  471. entries = [];
  472. objs = [];
  473. assert(len(inputs) != 0, "Compilation must have inputs");
  474. for (input in inputs) {
  475. input_parts = split_extension(input);
  476. ext = input_parts[1];
  477. suffix = ".aml";
  478. tool = "iasl";
  479. obj_name = input_parts[0] + suffix;
  480. obj = {
  481. "type": "target",
  482. "label": obj_name,
  483. "output": obj_name,
  484. "inputs": [input],
  485. "tool": tool
  486. };
  487. entries += [obj];
  488. objs += [":" + obj_name];
  489. }
  490. return [objs, entries];
  491. }
  492. //
  493. // Create a group of object file targets from binary files. Returns a
  494. // list of the object names in the first element and the object file target
  495. // entries in the second element.
  496. //
  497. function objectified_binaries(params) {
  498. inputs = params["inputs"];
  499. objs = [];
  500. entries = [];
  501. objcopy_config = get(params, "config");
  502. prefix = get(params, "prefix");
  503. build = get(params, "build");
  504. assert(len(inputs) != 0, "Compilation must have inputs");
  505. for (input in inputs) {
  506. input_parts = split_extension(input);
  507. ext = input_parts[1];
  508. suffix = ".o";
  509. tool = "objcopy";
  510. if (build) {
  511. tool = "build_" + tool;
  512. }
  513. obj_name = input_parts[0] + suffix;
  514. if (prefix) {
  515. obj_name = prefix + "/" + obj_name;
  516. }
  517. obj = {
  518. "type": "target",
  519. "label": obj_name,
  520. "output": obj_name,
  521. "inputs": [input],
  522. "tool": tool,
  523. "config": objcopy_config,
  524. };
  525. entries += [obj];
  526. objs += [":" + obj_name];
  527. }
  528. if (prefix) {
  529. params["output"] ?= params["label"];
  530. params["output"] = prefix + "/" + params["output"];
  531. }
  532. return [objs, entries];
  533. }
  534. //
  535. // Create a single object file from a binary file.
  536. //
  537. function objectified_binary(params) {
  538. return objectified_binaries(params)[1];
  539. }
  540. //
  541. // Create a library from a set of objectified files.
  542. //
  543. function objectified_library(params) {
  544. build = get(params, "build");
  545. compilation = objectified_binaries(params);
  546. objs = compilation[0];
  547. entries = compilation[1];
  548. params["type"] = "target";
  549. output = get(params, "output");
  550. output ?= params["label"];
  551. params["output"] = output + ".a";
  552. params["inputs"] = objs;
  553. params["tool"] = "ar";
  554. if (build) {
  555. params["tool"] = "build_ar";
  556. }
  557. entries += [params];
  558. return entries;
  559. }
  560. //
  561. // Create a flat binary from an executable image.
  562. //
  563. function flattened_binary(params) {
  564. params["type"] = "target";
  565. params["tool"] = "objcopy";
  566. flags = "OBJCOPY_FLAGS";
  567. if (get(params, "build")) {
  568. params["tool"] = "build_objcopy";
  569. flags = "BUILD_OBJCOPY_FLAGS";
  570. }
  571. add_config(params, flags, "-O binary");
  572. if (get(params, "binplace")) {
  573. params["nostrip"] = TRUE;
  574. entries = binplace(params);
  575. } else {
  576. entries = [params];
  577. }
  578. return entries;
  579. }
  580. //
  581. // Create a Minoca kernel driver.
  582. //
  583. function driver(params) {
  584. params["entry"] ?= "DriverEntry";
  585. params["binplace"] ?= TRUE;
  586. soname = get(params, "output");
  587. soname ?= params["label"];
  588. if (soname != "kernel") {
  589. soname += ".drv";
  590. params["output"] = soname;
  591. params["inputs"] += ["//kernel:kernel"];
  592. }
  593. add_config(params, "LDFLAGS", "-shared");
  594. add_config(params, "LDFLAGS", "-Wl,-soname=" + soname);
  595. add_config(params, "LDFLAGS", "-nostdlib");
  596. return executable(params);
  597. }
  598. //
  599. // Define a function for creating a runtime driver .FFS file from an ELF.
  600. //
  601. function uefi_runtime_ffs(name) {
  602. elfconv_config = {
  603. "ELFCONV_FLAGS": "-t efiruntimedriver"
  604. };
  605. pe = {
  606. "type": "target",
  607. "label": name,
  608. "inputs": [":" + name + ".elf"],
  609. "implicit": ["//uefi/tools/elfconv:elfconv"],
  610. "tool": "elfconv",
  611. "config": elfconv_config
  612. };
  613. ffs = {
  614. "type": "target",
  615. "label": name + ".ffs",
  616. "inputs": [":" + name],
  617. "implicit": ["//uefi/tools/genffs:genffs"],
  618. "tool": "genffs_runtime"
  619. };
  620. return [pe, ffs];
  621. }
  622. //
  623. // Define a function that creates a UEFI firmware volume object file based on
  624. // a platform name and a list of FFS inputs.
  625. //
  626. function uefi_fwvol_o(name, ffs) {
  627. fwv_name = name + "fwv";
  628. fwv = {
  629. "type": "target",
  630. "label": fwv_name,
  631. "inputs": ffs,
  632. "implicit": ["//uefi/tools/genfv:genfv"],
  633. "tool": "genfv"
  634. };
  635. fwv_o = {
  636. "label": fwv_name + ".o",
  637. "inputs": [":" + fwv_name],
  638. "tool": "objcopy",
  639. "config": {"OBJCOPY_FLAGS": global_config["OBJCOPY_FLAGS"]}
  640. };
  641. return [fwv, fwv_o];
  642. }
  643. //
  644. // Define a function that creates a version.h file target.
  645. //
  646. function create_version_header(major, minor, revision) {
  647. version_config = {
  648. "FORM": "header",
  649. "MAJOR": major,
  650. "MINOR": minor,
  651. "REVISION": revision,
  652. "RELEASE": release_level
  653. };
  654. version_h = {
  655. "type": "target",
  656. "output": "version.h",
  657. "inputs": ["//.git/HEAD"],
  658. "tool": "gen_version",
  659. "config": version_config + {"FORM": "header"}
  660. };
  661. return [version_h];
  662. }