#
# \brief Sculpt OS
# \author Norman Feske
# \date 2017-09-07
#
#
# Note: the string must be exactly 5 bytes long.
#
proc sculpt_version { } { return "23.11" }
proc assert_platform_supported { } {
if {[have_board pc]} return
if {[have_board imx8q_evk]} return
if {[have_board mnt_reform2]} return
if {[have_board linux]} return
if {[have_board pinephone]} return
puts "Platform is unsupported.";
exit 0;
}
assert_platform_supported
if {[have_spec nova]} {
proc kernel_output { } { return "novga serial logmem" }
}
##
## Utilities for querying the ingredients of Sculpt
##
##
# Return path to the sculpt definition file that contains the list of
# ingredients
#
proc sculpt_path { } {
global ::env
set filename "default-[board].sculpt"
if {[info exists ::env(SCULPT)]} {
set filename "$::env(SCULPT)-[board].sculpt" }
return [select_from_repositories [file join sculpt $filename]]
}
##
# Return list of ingredients of scenario supplied via the 'SCULPT' argument
#
# If 'SCULPT' is not specified, use 'sculpt/default.sculpt'.
#
proc sculpt_ingredients { } {
global _sculpt_ingredients
if {![info exists _sculpt_ingredients]} {
set fh [open [sculpt_path] "RDONLY"]
set _sculpt_ingredients [split [read $fh] "\n"]
close $fh
}
return $_sculpt_ingredients
}
proc ingredients_of_type { type } {
set result {}
set line 1
foreach i [sculpt_ingredients] {
# skip comment lines
if {[string match "#*" $i]} {
continue }
# skip empty lines
if {[string match "" $i]} {
continue }
regexp {^(.*?):\s*(.*)\s*$} $i dummy tag values
if {$tag == $type} {
lappend result {*}$values}
incr line
}
return $result
}
proc single_ingredient { type default } {
set ingredient [ingredients_of_type $type]
if {[llength $ingredient] == 0} {
return $default }
if {[llength $ingredient] > 1} {
puts stderr "Error: ambigious selection of '$type' in [sculpt_path]"
exit
}
return $ingredient
}
proc ingredient_path { type ingredient } {
return [select_from_repositories [file join sculpt $type $ingredient]] }
proc initial_config_dir { } {
return [file join [run_dir] initial_config] }
proc initial_config_file { rel_path } {
return [file join [initial_config_dir] $rel_path] }
##
## System-image content imported from depot archives
##
create_boot_directory
proc prefixed_with_depot_user { paths } {
return [lmap path $paths { file join [depot_user] $path }] }
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/pkg/sculpt \
{*}[prefixed_with_depot_user [ingredients_of_type import]]
##
## Static init configuration
##
proc log_route { } {
global ::env
if {[info exists ::env(LOG)]} {
if {$::env(LOG) == "core"} {
return { } } }
return { }
}
##
# ROM routes that relabel generic NIC driver names to platform-specific binaries
#
proc nic_driver_routes { } {
set result(pc) {
}
set result(mnt_reform2) {
}
set result(imx8q_evk) {
}
set result(pinephone) {
}
if {[info exists result([board])]} {
return $result([board]); }
return "";
}
proc log_core_start_node { } {
if {[have_board linux]} return
return {
}
}
proc log_kernel_start_node { } {
if {[have_board linux]} return
if {[have_board pinephone]} return
return {
}
}
install_config {
### start ###
} [log_core_start_node] {
} [log_kernel_start_node] {
} [log_route] {
} [log_route] {
} [log_route] [nic_driver_routes] {
}
##
## Initial content of the config file system
##
#
# The directory structure for the initial content is created at the run
# directory, which is imported as 'initial_config.tar' into the config fs.
#
# directory structure
foreach subdir { launcher depot managed keyboard } {
file mkdir [file join [initial_config_dir] $subdir] }
if {[llength [ingredients_of_type presets]] > 0} {
file mkdir [file join [initial_config_dir] presets] }
# configs that are managed by the sculpt manager if absent
set optional_configs {
fonts
nic_router
event_filter
wifi
runtime
gpu_drv
}
foreach config $optional_configs {
set ingredient [single_ingredient $config ""]
if {$ingredient != ""} {
set from [ingredient_path $config $ingredient]
set to [initial_config_file $config]
copy_file $from $to } }
# configs that are expected to be always present
set required_configs {
nitpicker
deploy
fb_drv
clipboard
drivers
numlock_remap
global_keys
leitzentrale
usb
system
ram_fs
}
foreach config $required_configs {
set ingredient [single_ingredient $config "default"]
set from [ingredient_path $config $ingredient]
set to [initial_config_file $config]
check_xml_syntax $from
copy_file $from $to
}
# selection of depot users (pubkey and download files), launchers, and presets
foreach ingredient [ingredients_of_type launcher] {
check_xml_syntax [ingredient_path launcher $ingredient] }
foreach ingredient [ingredients_of_type presets] {
check_xml_syntax [ingredient_path deploy $ingredient] }
foreach type { depot launcher } {
foreach ingredient [ingredients_of_type $type] {
set from [ingredient_path $type $ingredient]
set to [file join [initial_config_dir] $type $ingredient]
file copy $from $to } }
foreach ingredient [ingredients_of_type presets] {
set from [ingredient_path deploy $ingredient]
set to [file join [initial_config_dir] presets $ingredient]
file copy $from $to }
copy_file [genode_dir]/repos/gems/recipes/pkg/sculpt/README [initial_config_file README]
copy_file [genode_dir]/repos/gems/run/sculpt/vimrc [initial_config_file vimrc]
# keyboard layouts
foreach file { en_us de_ch de_de fr_ch fr_fr special } {
set from [genode_dir]/repos/os/src/server/event_filter/$file.chargen
set to [file join [initial_config_dir] keyboard $file]
check_xml_syntax $from
copy_file $from $to }
#
# Pre-populate the managed/ directory of the config fs to avoid diagnostic
# warnings by components starting up before the sculpt manager has generated
# the directory content for the first time.
#
proc managed_config_path { name } {
return [file join [initial_config_dir] managed $name] }
set fd [open [managed_config_path installation] w]
puts $fd ""
close $fd
set fd [open [managed_config_path depot_query] w]
puts $fd ""
close $fd
foreach config { fonts wifi runtime event_filter system } {
set ingredient [single_ingredient $config "default"]
if {$ingredient != ""} {
set from [ingredient_path $config $ingredient]
set to [managed_config_path $config]
check_xml_syntax $from
copy_file $from $to
}
}
copy_file [file join [initial_config_dir] fb_drv] [managed_config_path fb_drv]
#
# VERSION file at the root of the config fs, add newline for 'cat /VERSION'
#
set fd [open [initial_config_file VERSION] w]
puts $fd "[sculpt_version]"
close $fd
# supply VERSION as a boot module to the sculpt manager
file copy [initial_config_file VERSION] [run_dir]/genode/VERSION
#
# Generate build info as boot module
#
proc build_date { } { return [clock format [clock seconds] -format %Y-%m-%d] }
proc genode_version { } {
if {[have_installed git] && [file exists [file join [genode_dir] .git]]} {
return [exec [installed_command git] -C [genode_dir] describe \
{--dirty= } 2> /dev/null] }
set fh [open [file join [genode_dir] VERSION] "RDONLY"]
set version [read $fh]
close $fh
regsub -all {\s} $version "" version
return $version
}
set build_info ""
set fd [open [run_dir]/genode/build_info w]
puts $fd $build_info
close $fd
##
# Return list of 'pkg' attribute values found in 'type' nodes in an XML file
#
proc pkg_attribute_values { xml_file node_path } {
set xpath "$node_path/attribute::pkg"
set values {}
if {[catch {
foreach attr [exec xmllint --xpath $xpath $xml_file] {
regexp {^pkg="(.*)"$} $attr dummy value
lappend values $value
}
}]} {
# no pkg attribute present in 'xml_file'
}
return $values
}
##
# Return list of pkg archives reference by the launchers and deploy config
#
proc referenced_pkg_values { } {
set values {}
# scan launchers
foreach launcher [ingredients_of_type launcher] {
set path [file join [initial_config_dir] launcher $launcher]
lappend values {*}[pkg_attribute_values $path launcher]
}
# scan presets
foreach preset [ingredients_of_type presets] {
set path [file join [initial_config_dir] presets $preset]
lappend values {*}[pkg_attribute_values $path config/start]
}
# scan deploy config
lappend values {*}[pkg_attribute_values [initial_config_file deploy] config/start]
return [lsort -unique $values]
}
#
# Replace pkg values of the form "name" by the form "user/pkg/name" suitable as
# arguments for '_collect_from_depot'.
#
proc pkg_archive_paths { values } {
set result {}
foreach value $values {
if {[regexp {/} $value dummy]} {
lappend result $value
} else {
lappend result "[depot_user]/pkg/$value"
}
}
return $result
}
##
# Return which kind of depot archive is wanted: 'tar', 'omit', or 'list'
#
# tar - includes the depot content referenced by the deploy configuration as
# tar archive named 'depot.tar'
#
# omit - skips the evaluation of the deploy configuration
#
# list - outputs the list of packages needed for the deployment, which can
# taken as input for manually publishing those packages
#
proc depot_archive { } {
global ::env
set archive "tar"
if {[info exists ::env(DEPOT)]} {
set archive "$::env(DEPOT)" }
if {$archive != "omit" && $archive != "tar" && $archive != "list"} {
puts stderr "Error: invalid value of DEPOT=$archive variable!" }
return $archive
}
#
# Trigger the creation / updating of referenced depot content
#
# This step may update pkg versions if '--depot-auto-update' is enabled.
#
if {[depot_archive] == "tar" || [depot_archive] == "list"} {
_collect_from_depot [pkg_archive_paths [referenced_pkg_values]]
}
#
# Augment deploy config or launcher file with current package versions
#
proc current_pkg { pkg } { return $pkg/[_current_depot_archive_version pkg $pkg] }
#
# Supplement file with versioned pkg archive paths
#
# \path deploy config, or launcher, or preset to augment
# \node XML node type containing the 'pkg' attribute to modify
#
# Each matching XML node is inspected regarding its 'pkg' attribute. If its
# 'pkg' attribute contains a single identifier (rather than a valid pkg path),
# the attribute value is replaced by a valid pkg path referring to the current
# version of the pkg and the [depot_user].
#
proc augment_pkg_versions { path node } {
set fd [open $path r]
set content [read $fd]
close $fd
# filter 'pkg' attribute
set pattern "(\<$node\[^\>\]+?pkg=\")(\[^/\]+)(\")"
while {[regexp $pattern $content dummy head pkg tail]} {
set pkg_path [depot_user]/pkg/[current_pkg $pkg]
regsub $pattern $content "$head$pkg_path$tail" content
}
# write back the filtered launcher snippet, deploy config, or preset
set fd [open $path w]
puts $fd $content
close $fd
}
# launcher snippets
foreach launcher [ingredients_of_type launcher] {
augment_pkg_versions [file join [initial_config_dir] launcher $launcher] "launcher" }
# presets
foreach preset [ingredients_of_type presets] {
augment_pkg_versions [file join [initial_config_dir] presets $preset] "start" }
# deploy config
augment_pkg_versions [initial_config_file deploy] "start"
# update arch attribute of deploy config and presets
proc augment_arch_attribute { file } {
exec sed -i "/config/s/arch=\"\"/arch=\"[depot_spec]\"/" $file }
foreach preset [ingredients_of_type presets] {
augment_arch_attribute [file join [initial_config_dir] presets $preset] }
augment_arch_attribute [initial_config_file deploy]
##
## Depot content integrated in the Sculpt image
##
proc create_depot_archive { } {
if {[depot_archive] == "tar"} {
create_tar_from_depot_binaries [run_dir]/genode/depot.tar \
{*}[pkg_archive_paths [referenced_pkg_values]]
} elseif {[depot_archive] == "list"} {
puts "Do not forget to publish:"
puts [pkg_archive_paths [referenced_pkg_values]]
}
}
create_depot_archive
#
# Create initial_config.tar to be mounted at the root of the config fs
#
exec sh -c "tar cf [run_dir]/genode/initial_config.tar -C [initial_config_dir] ."
file delete -force [initial_config_dir]
##
## Auxiliary boot modules
##
#
# Linux-specific tweaks
#
if {[have_board linux]} {
#
# The Linux version of core does not export a platform_info ROM module.
#
install_boot_module "platform_info" {}
#
# Managed dataspaces as used by cached_fs_rom are not supported on Linux.
#
copy_file [run_dir]/genode/fs_rom [run_dir]/genode/cached_fs_rom
}
# support for the Leitzentrale GUI
copy_file [genode_dir]/repos/gems/src/app/backdrop/genode_logo.png [run_dir]/genode/
copy_file [genode_dir]/repos/gems/run/sculpt/drop_shadow.png [run_dir]/genode/
#
# Generate depot index from gems/run/sculpt/index
#
set fd [open [genode_dir]/repos/gems/run/sculpt/index r]
set pkg_index [read $fd]
close $fd
# filter 'pkg' attribute
set pattern {(\]+?path=")([^/]+)(")}
while {[regexp $pattern $pkg_index dummy head pkg tail]} {
set pkg_path [depot_user]/pkg/[current_pkg $pkg]
regsub $pattern $pkg_index "$head$pkg_path$tail" pkg_index
}
# write filtered pkg index into the depot
file mkdir [depot_dir]/[depot_user]/index
set fd [open [depot_dir]/[depot_user]/index/[sculpt_version] w]
puts $fd $pkg_index
close $fd
##
## Create boot image
##
build [ingredients_of_type build]
append boot_modules [build_artifacts]
build_boot_image $boot_modules