# # \brief Sculpt OS # \author Norman Feske # \date 2017-09-07 # # # Note: the string must be exactly 5 bytes long. # proc sculpt_version { } { return "24.10" } proc assert_platform_supported { } { if {[have_board pc]} return if {[have_board imx8q_evk]} return if {[have_board mnt_reform2]} return if {[have_board mnt_pocket]} return if {[have_board linux]} return if {[have_board pinephone]} return puts "Platform is unsupported."; exit 0; } assert_platform_supported proc log_core { } { global ::env if {[info exists ::env(LOG)]} { if {$::env(LOG) == "core"} { return true } } return false } if {[log_core]} { if {[have_spec nova]} { proc kernel_output { } { return "serial logmem" } } } else { # disable default enabled serial log output via core proc boot_output { } { return "" } if {[have_spec nova]} { proc kernel_output { } { return "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 { } { if {[log_core]} { return { } } return { } } ## # ROM routes that relabel generic driver names to platform-specific binaries # proc driver_routes { } { set result(pc) { } set result(mnt_reform2) { } set result(mnt_pocket) { } 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_spec nova]} return return { } } install_config { ### start ### } [log_core_start_node] { } [log_kernel_start_node] { } [log_route] { } [log_route] { } [log_route] [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 } 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 clipboard drivers numlock_remap global_keys leitzentrale usb system ram_fs manager } 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 fr_bepo 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 nitpicker } { 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] [managed_config_path fb] # # 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 that is not /pkg/< set pattern "(\<$node\[^\>]+?pkg=\")" append pattern {(?![\w\d\-]+/pkg/[\w\d\-_]+/[\w\d\-\._]+)(.*)} append pattern "(\")" while {[regexp $pattern $content dummy head pkg tail]} { if {[regexp [_depot_archive_path_pattern] $pkg dummy user type name] && $type == "pkg"} { set pkg_path $user/pkg/[current_pkg $name] regsub $pattern $content "$head$pkg_path$tail" content } elseif {![regexp {/} $pkg dummy]} { set pkg_path [depot_user]/pkg/[current_pkg $pkg] regsub $pattern $content "$head$pkg_path$tail" content } else { puts stderr "Error: malformed depot-archive path '$pkg'," puts stderr " expected '/pkg/' or" puts stderr " '' or" puts stderr " '/pkg//'" exit } } # 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