apm-admin-procs.tcl
Does not contain a contract.
- Location:
- /packages/acs-admin/tcl/apm-admin-procs.tcl
Related Files
[ hide source ] | [ make this the default ]
File Contents
ad_library { Definitions for the APM administration interface. @creation-date 29 September 2000 @author Bryan Quinn (bquinn@arsdigita.com) @cvs-id $Id: apm-admin-procs.tcl,v 1.32.2.15 2024/08/05 14:20:21 gustafn Exp $ } ad_proc -private apm_parameter_section_slider {package_key} { Build a dynamic section dimensional slider. } { set sections [db_list apm_parameter_sections { select distinct(section_name) from apm_parameters where package_key = :package_key }] if { [llength $sections] > 1 } { lappend section_list [list $package_key $package_key [list "where" "section_name is null"]] foreach section $sections { if { $section ne "" } { lappend section_list [list $section $section [list "where" "section_name = [ns_dbquotevalue $section]"]] } } lappend section_list [list all "All" [list]] return [list [list section_name "Section:" $package_key $section_list]] } else { return "" } } ad_proc -deprecated apm_header { { -form "" } args } { Generates HTML for the header of a page (including context bar). Must only be used for APM admin pages (under /acs-admin/apm). We are adding the APM index page to the context bar so it doesn't have to be added on each page @author Peter Marklund } { set apm_title "Package Manager" set apm_url "/acs-admin/apm/" if { [llength $args] == 0 } { set title $apm_title set context_bar [ad_context_bar $title] } else { set title [lindex $args end] set context [concat [list [list $apm_url $apm_title]] $args] set cmd [list ad_context_bar --] foreach elem $context { lappend cmd $elem } set context_bar [eval $cmd] # this is rather a hack, but just needed for streaming output # a more general solution can be provided at some later time... regsub "#acs-kernel.Main_Site#" $context_bar \ [_ acs-kernel.Main_Site] context_bar } append body [ad_header $title ""] "\n" if {$form ne ""} { append body "<form $form>" } return "$body\n <h3>$title</h3> $context_bar <hr> " } ad_proc -deprecated apm_shell_wrap { cmd } { The value provided by this proc is unclear, quite hardcoded, and it is used nowhere in usptream code. @see many possible plain tcl idioms @return a command string, wrapped it shell-style (with backslashes) in case lines get too long. } { set out "" set line_length 0 foreach element $cmd { if { $line_length + [string length $element] > 72 } { append out "\\\n " set line_length 4 } append out "$element " incr line_length [expr { [string length $element] + 1 }] } append out "\n" } d_proc -private apm_package_selection_widget { pkg_info_list {to_install ""} {operation "all"} {form pkgsForm} } { Provides a widget for selecting packages. Displays dependency information if available. @param pkg_info_list list of package infos for all packages to be listed @param to_install list of package_keys to install @param operation filter for added operations (all, upgrade, install) } { if {$pkg_info_list eq ""} { return "" } set counter 0 if {[llength $to_install] > 0} { set label [dict get {install Install upgrade Upgrade all Install/Update} $operation] } else { set label [subst { <input type="checkbox" name="_dummy" id="bulkaction-control" title="[_ acs-templating.lt_Checkuncheck_all_rows]"> }] template::add_event_listener \ -id bulkaction-control \ -preventdefault=false \ -script [subst {acs_ListCheckAll('$form', this.checked);}] } set widget [subst { <blockquote><table class='list-table' cellpadding='3' cellspacing='5' summary="Available Packages"> <tr class='list-header'><th>$label</th><th>Package</th><th>Package Key</th><th>Comment</th></tr> }] foreach pkg_info $pkg_info_list { incr counter set package_key [pkg_info_key $pkg_info] set package_path [pkg_info_path $pkg_info] set spec_file [pkg_info_spec $pkg_info] set package [apm_read_package_info_file $spec_file] set package_name [dict get $package package-name] set version_name [dict get $package name] set id $form-$package_key ns_log Debug "Selection widget: $package_key, Dependency: [pkg_info_dependency_p $pkg_info]" if { [pkg_info_dependency_p $pkg_info] == "t" } { # # Dependency passed. # set checked [expr { $package_key in $to_install ? "checked " : "" }] append widget [subst { <tr class='[expr {$counter % 2 ? "odd" : "even"}]'> <td align='center'><input type='checkbox' $checked name='package_key' value='$package_key' id='$id'></td> <td>$package_name $version_name</td> <td>$package_key</td> <td><span style='color:green'>Dependencies satisfied.</span></td> </tr> }] } elseif { [pkg_info_dependency_p $pkg_info] == "f" } { # # Dependency failed. # append widget [subst { <tr class='[expr {$counter % 2 ? "odd" : "even"}]'> <td align='center'><input type='checkbox' name='package_key' value='$package_key' id='$id'></td> <td>$package_name $version_name</td> <td>$package_key</td> <td><span style='color:red'> }] foreach comment [pkg_info_comment $pkg_info] { append widget "$comment<br>" } append widget \ </span></td> \ </tr> } else { # # No dependency information. # See if the install is already installed with a higher version number. # if {[apm_package_registered_p $package_key]} { set higher_version_p [apm_higher_version_installed_p $package_key $version_name] } else { set higher_version_p 2 } if {$higher_version_p == 2 } { if {$operation eq "upgrade"} { incr counter -1 continue } set comment "New install." } elseif {$higher_version_p == 1 } { if {$operation eq "install"} { incr counter -1 continue } set comment "Upgrade." } elseif {$higher_version_p == 0} { set comment "Package version already installed." } else { set comment "Installing older version of package." } set install_checked [expr {$package_key in $to_install ? "checked" : ""}] append widget [subst { <tr class='[expr {$counter % 2 ? "odd" : "even"}]'> <td align='center'><input type='checkbox' $install_checked name='package_key' value='$package_key' id='$id'></td> <td>$package_name $version_name</td> <td>$package_key</td> <td>$comment</td> </tr> }] } } if {$counter == 0} { set widget "" } else { append widget {</table></blockquote>} } return $widget } d_proc -public apm_higher_version_installed_p { package_key version_name } { @param package_key The package in question. @param version_name The name of the currently installed version. @return The return value of this procedure doesn't really fit with its name. What it returns is: <ul> <li>-1 if there's already a higher version of the given package installed than the version_name you gave it. <li>0 if the same version is installed as the one you supplied. <li>1 if the version you gave is higher than the highest version installed, or no version of this package is installed. </ul> } { set package_version_name [apm_highest_version_name $package_key] if {$package_version_name eq ""} { return 1 } return [apm_version_names_compare $version_name $package_version_name] } d_proc -private apm_build_repository { {-debug:boolean 0} {-channels *} {-head_channel HEAD} } { Rebuild the repository on the local machine. Only useful for the openacs.org site. Adapted from Lars' build-repository.tcl page. @param debug Set to 1 to test with only a small subset of packages instead of the whole cvs tree. @param head_channel The artificial branch label to apply to HEAD. Should be one minor version past the current release. @param channels Generate apm files for the matching channels only @author Lars Pind (lars@collaboraid.biz) @return 0 for success. Also outputs debug messages to log. } { #---------------------------------------------------------------------- # Configuration Settings #---------------------------------------------------------------------- set cd_helper [file join $::acs::rootdir bin cd-helper] set cvs_command cvs set cvs_root :pserver:anonymous@cvs.openacs.org:/cvsroot set work_dir [file join $::acs::rootdir repository-builder][file separator] set repository_dir [file join $::acs::rootdir www repository][file separator] set repository_url https://openacs.org/repository/ set exclude_package_list {} set channel_index_template [template::themed_template /packages/acs-admin/www/apm/repository-channel-index] set index_template [template::themed_template /packages/acs-admin/www/apm/repository-index] #---------------------------------------------------------------------- # Prepare output #---------------------------------------------------------------------- ns_log Debug "Repository: Building Package Repository" #---------------------------------------------------------------------- # Find available channels #---------------------------------------------------------------------- # Prepare work dir file mkdir $work_dir cd $work_dir set msg [ exec $cd_helper $work_dir $cvs_command -d $cvs_root -z3 co openacs-4/readme.txt ] set output [ exec $cd_helper $work_dir $cvs_command -d $cvs_root -z3 log -h openacs-4/readme.txt ] set lines [split $output \n] for { set i 0 } { $i < [llength $lines] } { incr i } { if { [string trim [lindex $lines $i]] eq "symbolic names:" } { incr i break } } array set channel_tag [list] array set channel_bugfix_version [list] for { } { $i < [llength $lines] } { incr i } { # Tag lines have the form tag: cvs-version # openacs-5-0-0-final: 1.25.2.5 if { ![regexp {^\s+([^:]+):\s+([0-9.]+)} [lindex $lines $i] match tag_name version_name] } { break } # Look for tags named 'openacs-x-y-compat' if { [regexp {^openacs-([1-9][0-9]*-[0-9]+)-compat$} $tag_name match oacs_version] } { lassign [split $oacs_version "-"] major_version minor_version if { $major_version >= 5 && $minor_version >= 3} { set channel "${major_version}-${minor_version}" ns_log Notice "Repository: Found channel $channel using tag $tag_name" set channel_tag($channel) $tag_name } } elseif { [regexp {^openacs-([1-9][0-9]*-[0-9]+-[0-9]+)-final$} $tag_name match oacs_version] } { lassign [split $oacs_version "-"] major_version minor_version patch_version #ns_log Notice "Repository: tag <$tag_name> oacs version <$oacs_version> split into /$major_version/$minor_version/$patch_version/" if { $major_version >= 5 && $minor_version >= 8} { set channel "${major_version}-${minor_version}-$patch_version" ns_log Notice "Repository: Found channel $channel using tag $tag_name" set channel_tag($channel) $tag_name } } } set channel_tag($head_channel) HEAD set channel_tag(5-10) oacs-5-10 ns_log Notice "Repository: Channels are: [array get channel_tag]" #---------------------------------------------------------------------- # Read all package .info files, building manifest file #---------------------------------------------------------------------- # Wipe and re-create the working directory file delete -force -- $work_dir file mkdir ${work_dir} set update_pretty_date [lc_time_fmt [clock format [clock seconds] -format "%Y-%m-%d %T"] %c] #cd $work_dir foreach channel [lsort -decreasing [array names channel_tag]] { if {![string match $channels $channel]} continue ns_log Notice "Repository: Channel $channel using tag $channel_tag($channel)" # Wipe and re-create the checkout directory file delete -force -- "${work_dir}openacs-4" file delete -force -- "${work_dir}dotlrn" file mkdir "${work_dir}dotlrn/packages" # Prepare channel directory set channel_dir "${work_dir}repository/$channel/" file mkdir $channel_dir # Store the list of packages we've seen for this channel, so we don't include the same package twice # Seems odd, but we have to do this given the forked packages sitting in /contrib set packages [list] # Checkout from the tag given by channel_tag($channel) if { $debug_p } { # Smaller list for debugging purposes set checkout_list [list $work_dir $cvs_root openacs-4/packages/acs-core-docs ] } else { # Full list for real use set checkout_list [list \ $work_dir $cvs_root openacs-4/packages \ $work_dir $cvs_root openacs-4/contrib/packages] } foreach { cur_work_dir cur_cvs_root cur_module } $checkout_list { #cd $cur_work_dir set cmd [list exec $cd_helper $cur_work_dir cvs -d $cur_cvs_root -z3 co] if { $channel_tag($channel) ne "HEAD" } { lappend cmd -r $channel_tag($channel) } catch { {*}$cmd $cur_module } output ns_log Notice "Repository: $cur_module [llength $output] files ($channel_tag($channel))" } #cd $work_dir set manifest "<manifest>\n" template::multirow create packages \ package_path package_key version pretty_name \ package_type summary description \ release_date vendor_url vendor \ maturity maturity_text \ license license_url download_url set work_dirs [list ${work_dir}openacs-4/packages ${work_dir}openacs-4/contrib/packages ] foreach packages_dir $work_dirs { foreach spec_file [lsort [apm_scan_packages $packages_dir]] { set package_path [file join {*}[lrange [file split $spec_file] 0 end-1]] set package_key [lindex [file split $spec_file] end-1] if { $package_key in $exclude_package_list } { ns_log Debug "Repository: Package $package_key is on list of packages to exclude - skipping" continue } if { [array exists pkg_info] } { array unset pkg_info } if { [info exists pkg_info] } { unset pkg_info } ad_try { array set pkg_info [apm_read_package_info_file $spec_file] if { $pkg_info(package.key) in $packages } { ns_log Debug "Repository: Skipping package $package_key, because we already have another version of it" } else { lappend packages $pkg_info(package.key) append manifest \ " <package>" \n \ " <package-key>[ns_quotehtml $pkg_info(package.key)]</package-key>\n" \ " <version>[ns_quotehtml $pkg_info(name)]</version>\n" \ " <pretty-name>[ns_quotehtml $pkg_info(package-name)]</pretty-name>\n" \ " <package-type>[ns_quotehtml $pkg_info(package.type)]</package-type>\n" \ " <summary>[ns_quotehtml $pkg_info(summary)]</summary>\n" \ " <description format=\"[ns_quotehtml $pkg_info(description.format)]\">" \ [ns_quotehtml $pkg_info(description)] "</description>\n" \ " <release-date>[ns_quotehtml $pkg_info(release-date)]</release-date>\n" \ " <vendor url=\"[ns_quotehtml $pkg_info(vendor.url)]\">" \ [ns_quotehtml $pkg_info(vendor)] "</vendor>\n" \ " <license url=\"[ns_quotehtml $pkg_info(license.url)]\">" \ [ns_quotehtml $pkg_info(license)] "</license>\n" \ " <maturity>$pkg_info(maturity)</maturity>\n" foreach e $pkg_info(install) { append manifest " <install package=\"$e\"/>\n" } set apm_file "${channel_dir}${pkg_info(package.key)}-${pkg_info(name)}.apm" ns_log Notice "Repository: Building package $package_key for channel $channel" set files [apm_get_package_files \ -all \ -include_data_model_files \ -all_db_types \ -package_key $pkg_info(package.key) \ -package_path $package_path] if { [llength $files] == 0 } { ns_log Notice "Repository: No files in package" } else { ns_log Notice "Repository: [llength $files] files in package $pkg_info(package.key) ($channel)" set cmd [list exec [apm_tar_cmd] cf - 2>/dev/null] # The path to the 'packages' directory in the checkout set packages_root_path [file join {*}[lrange [file split $spec_file] 0 end-2]] set fp [ad_opentmpfile tmp_filename] foreach file $files { puts $fp $package_key/$file } close $fp lappend cmd -C $packages_root_path --files-from $tmp_filename lappend cmd "|" [apm_gzip_cmd] -c ">" $apm_file ns_log Notice "Executing: exec $cd_helper $packages_root_path $cmd" if {[catch "exec $cd_helper $packages_root_path $cmd" errmsg]} { ns_log Error "Error during tar in repository creation for\ file ${channel_dir}$pkg_info(package.key)-$pkg_info(name).apm:\ \n$errmsg\n$::errorCode,$::errorInfo" } file delete -- $tmp_filename } set apm_url "${repository_url}$channel/$pkg_info(package.key)-$pkg_info(name).apm" template::multirow append packages \ $package_path $package_key $pkg_info(name) $pkg_info(package-name) \ $pkg_info(package.type) $pkg_info(summary) $pkg_info(description) \ $pkg_info(release-date) $pkg_info(vendor.url) $pkg_info(vendor) \ $pkg_info(maturity) $pkg_info(maturity_text) \ $pkg_info(license) $pkg_info(license.url) $apm_url append manifest " <download-url>$apm_url</download-url>\n" foreach elm $pkg_info(provides) { append manifest " <provides " \ "url=\"[ns_quotehtml [lindex $elm 0]]\" " \ "version=\"[ns_quotehtml [lindex $elm 1]]\" />\n" } foreach elm $pkg_info(requires) { append manifest " <requires " \ "url=\"[ns_quotehtml [lindex $elm 0]]\" " \ "version=\"[ns_quotehtml [lindex $elm 1]]\" />\n" } append manifest " </package>\n" } } on error {errorMsg} { ns_log Notice "Repository: Error on spec_file $spec_file: $errorMsg\n$::errorInfo\n" } } } append manifest "</manifest>\n" ns_log Notice "Repository: Writing $channel manifest to ${channel_dir}manifest.xml" set fw [open "${channel_dir}manifest.xml" w] puts $fw $manifest close $fw ns_log Notice "Repository: Writing $channel index page to ${channel_dir}index.adp" set fw [open "${channel_dir}index.adp" w] set packages [lsort $packages] puts $fw "<master>\n<property name=\"doc(title)\">OpenACS $channel Compatible Packages</property>\n\n" puts $fw "<h1>OpenACS $channel (CVS tag $channel_tag($channel))</h1> <p>Packages can be installed with the OpenACS Automated Installer on your OpenACS site at <code>/acs-admin/install</code>. Only packages potentially compatible with your OpenACS kernel will be shown.</p> " set category_title(core) "Core Packages" set package_keys(core) { acs-admin acs-api-browser acs-authentication acs-automated-testing acs-bootstrap-installer acs-content-repository acs-core-docs acs-kernel acs-lang acs-mail-lite acs-messaging acs-reference acs-service-contract acs-subsite acs-tcl acs-templating ref-timezones acs-translations intermedia-driver openacs-default-theme notifications search tsearch2-driver } set category_title(common-app) "Common Applications" set package_keys(common-app) { xowiki xotcl-request-monitor file-storage acs-developer-support forums calendar news faq } set category_title(extra) "Extra Packages and Libraries" set package_keys(extra) "" foreach p $packages { if {$p ni $package_keys(core) && $p ni $package_keys(common-app)} { lappend package_keys(extra) $p } } foreach category {core common-app extra} { template::multirow create pkgs \ package_path package_key version pretty_name \ package_type summary description \ release_date vendor_url vendor \ maturity maturity_text \ license license_url download_url template::multirow foreach packages { if {$package_key in $package_keys($category)} { template::multirow append pkgs \ $package_path $package_key $version $pretty_name \ $package_type $summary $description \ $release_date $vendor_url $vendor \ $maturity $maturity_text \ $license $license_url $download_url } } puts $fw "\n<h2>$category_title($category)</h2>\n" puts $fw [template::adp_include $channel_index_template \ [list channel $channel &pkgs pkgs update_pretty_date $update_pretty_date]] } close $fw ns_log Notice "Repository: Channel $channel complete." } ns_log Notice "Repository: Finishing Repository" foreach channel [array names channel_tag] { if {[regexp {^([1-9][0-9]*)-([0-9]+)$} $channel . major minor]} { # # *-compat channels: The "patchlevel" of these channels is # the highest possible value, higher than the released # -final channels. # set tag_order([format %.3d $major]-[format %.3d $minor]-999) $channel set tag_label($channel) "OpenACS $major.$minor" } elseif {[regexp {^([1-9][0-9]*)-([0-9]+)-([0-9]+)$} $channel . major minor patch]} { # # *-final channels: a concrete patchlevel is provided. # set tag_order([format %.3d $major]-[format %.3d $minor]-[format %.3d $patch]) $channel set tag_label($channel) "OpenACS $major.$minor.$patch" } else { set tag_order(999-999-999) $channel set tag_label($channel) "OpenACS $channel" } } # Write the index page ns_log Notice "Repository: Writing repository index page to ${work_dir}repository/index.adp" template::multirow create channels name tag label foreach key [lsort -decreasing [array names tag_order]] { set channel $tag_order($key) template::multirow append channels $channel $channel_tag($channel) $tag_label($channel) } set fw [open "${work_dir}repository/index.adp" w] puts $fw "<master>\n<property name=\"doc(title)\">OpenACS Package Repository</property>\n\n" puts $fw [template::adp_include -- $index_template \ [list &channels channels update_pretty_date $update_pretty_date]] close $fw # Add a redirector for outdated releases set fw [open "${work_dir}repository/index.vuh" w] puts $fw "ns_returnredirect /repository/" close $fw # Without the trailing slash set work_repository_dirname "${work_dir}repository" set repository_dirname [string range $repository_dir 0 end-1] set repository_bak "[string range $repository_dir 0 end-1]_bak" ns_log Notice "Repository: Moving work repository $work_repository_dirname to live repository dir at <a href=\"/repository\/>$repository_dir</a>\n" if { [file exists $repository_bak] } { file delete -force -- $repository_bak } if { [file exists $repository_dirname] } { file rename -- $repository_dirname $repository_bak } file rename -- $work_repository_dirname $repository_dirname ns_log Debug "Repository: DONE" return 0 } d_proc -private apm_git_repo_tags { -path:required } { Extracts the available tags from an OpenACS Git repo. This assumes the specific Git setup for our repo, hence it is meant for internal use only. @return list of tag names. } { set cd_helper [file join $::acs::rootdir bin cd-helper] set git_command git set output [exec $cd_helper $path $git_command tag] return [regexp -line -inline -all {openacs-\d+-\d+(-\d+)?-(compat|final)} $output] } d_proc -private apm_git_repo_branches { -path:required } { Extracts the available branches from an OpenACS Git repo. This assumes the specific Git setup for our repo, hence it is meant for internal use only. @return list of branch names } { set cd_helper [file join $::acs::rootdir bin cd-helper] set git_command git set output [exec $cd_helper $path $git_command branch -r] return [regexp -line -inline -all {oacs-\d+-\d+} $output] } d_proc -private apm_git_repo_channels { -path:required } { Extracts the available tags and branches from an OpenACS Git repo. This assumes the specific Git setup for our repo, hence it is meant for internal use only. @return list of branch names } { set channels [apm_git_repo_branches -path $path] lappend channels {*}[apm_git_repo_tags -path $path] } d_proc -private apm_git_checkout_repo { -path:required -branch:required } { Checks out a repository branch or tag, making also sure that this is up to date via 'git pull' (if this is a branch) This assumes the specific Git setup for our repo, hence it is meant for internal use only. @return list of branch names } { set cd_helper [file join $::acs::rootdir bin cd-helper] set git_command git try { ns_log Notice "Checking out '$path'" exec -ignorestderr -- $cd_helper $path $git_command checkout $branch } on error {errmsg} { # # Checking out a branch that was already checked # out will complain. As we know the branch exists # for this repo, we are pretty confident this # error can be ignored. # ns_log notice "Checking out existing branch '$branch' for '$path' complained:" $errmsg } # # If we are on a branch, make sure repo is up to date. # if {$branch in [apm_git_repo_branches -path $path]} { ns_log Notice "Updating '$path'" exec -ignorestderr -- $cd_helper $path $git_command pull } } d_proc -private apm_git_fetch_repo { -path:required -repo:required } { Fetches a repo from the Git mirror. Clones it first when it does not exist. This assumes the specific Git setup for our repo, hence it is meant for internal use only. @return list of branch names } { set git_url https://github.com/openacs set cd_helper [file join $::acs::rootdir bin cd-helper] set git_command git set repo_dir ${path}${repo} if {[file isdirectory $repo_dir]} { # # Folder exists. We fetch from the repo to see if new branches # exist. # ns_log notice "Fetching new branches for '$repo_dir'" exec -ignorestderr -- $cd_helper $repo_dir $git_command fetch origin } else { # # Folder does not exist. Clone the repo from scratch. # ns_log notice "Cloning '${git_url}/${repo}.git' in '$repo_dir'" try { exec -ignorestderr -- $cd_helper $path $git_command clone ${git_url}/${repo}.git } on error {errmsg} { if {$repo eq "openacs-core"} { error $errmsg } # # Tolerate errors when cloning non-core packages: some # legacy packages require authentication and would fail. # ns_log warning "Could not clone '$repo' from '${git_url}/${repo}.git':" $errmsg } } } d_proc -private apm_git_build_repository { {-debug:boolean 0} {-force_fresh:boolean false} {-channels *} {-min_final_version 5.8.0} {-min_compat_version 5.3.0} } { Rebuild the repository on the local machine. Only useful for the openacs.org site. Adapted from the CVS implementation, which came from Lars' build-repository.tcl page. @param debug Set to 1 to test with only a small subset of packages and branches instead of all of them. @param force_fresh Force a frech clone of the Git repos. @param channels A string match style pattern. Generate apm files for the matching channels only } { #---------------------------------------------------------------------- # Configuration Settings #---------------------------------------------------------------------- set sep [file separator] set cd_helper [file join $::acs::rootdir bin cd-helper] set work_dir [file join $::acs::rootdir repository-builder]${sep} set repository_dir [file join $::acs::rootdir www repository]${sep} set repository_url /repository/ set exclude_package_list {} set channel_index_template [template::themed_template /packages/acs-admin/www/apm/repository-channel-index] set index_template [template::themed_template /packages/acs-admin/www/apm/repository-index] # # Make sure workdir exists. Clear it before we start if requested. # if {$force_fresh_p} { file delete -force -- $work_dir } file mkdir $work_dir #---------------------------------------------------------------------- # Prepare output #---------------------------------------------------------------------- ns_log Debug "Repository: Building Package Repository" #---------------------------------------------------------------------- # Find available channels #---------------------------------------------------------------------- # # The core repo is considered the source of truth concerning # available channels. We fetch it first. # apm_git_fetch_repo -path $work_dir -repo openacs-core set core_repo_dir ${work_dir}openacs-core # # Channels that exist both from tags and from branches will be # taken from tags. # # Among tags, the compat one will have precedence over the final # one. # set core_channels [list] foreach tag [apm_git_repo_tags -path $core_repo_dir] { if {[regexp {^openacs-(.*)-(final|compat)} $tag _ channel type]} { if {![dict exists $core_channels $channel] || $type eq "compat" } { dict set core_channels $channel $tag } } } # # The latest release branch is special. It will have precedence # over the corresponding tag: this way people will get a fresher # version. # set branches [lsort -dictionary [apm_git_repo_branches -path $core_repo_dir]] set latest_branch [lindex $branches end] foreach branch $branches { regsub {^oacs-} $branch {} channel if {![dict exists $core_channels $channel] || $branch eq $latest_branch } { dict set core_channels $channel $branch } } # # We don't want to generate a channel for ancient versions of # packages. Here we remove those channels that are too old. For # some old versions, we will only generate the compat packages. # foreach {channel branch} $core_channels { regsub -all -- - $channel {.} channel_version if {([regexp {^.*-final} $branch] && [apm_version_names_compare $channel_version $min_final_version] == -1) || [apm_version_names_compare $channel_version $min_compat_version] == -1 } { dict unset core_channels $channel } } # # The HEAD channel is always included. # lappend core_channels HEAD HEAD if {$debug_p} { # # When debugging, only pick the last branch. # set core_channels [lrange $core_channels end-1 end] } ns_log notice "Repository channels:" $core_channels # # The core packages are those included in the openacs-core # repository. # set core_packages_dir ${core_repo_dir}${sep}packages set core_packages [list] foreach package_folder [glob \ -types d \ -directory $core_packages_dir *] { lappend core_packages [file tail $package_folder] } ns_log notice "Core packages:" $core_packages set non_core_packages_dir ${work_dir}openacs-non-core${sep} file mkdir $non_core_packages_dir # # This is the list of all packages that are not included in the # openacs-core repository. We currently maintain this list as # hardcoded here. One improvement would be to fetch it from the # Git host directly, either via scraping or via API. # # As long as this does not change, every time a new package is # added to the Git mirror, one should also add the corresponding # package key to this list. # set non_core_packages { accounts-desk accounts-finance accounts-ledger accounts-payables accounts-payroll accounts-receivables acs-datetime acs-events acs-interface acs-ldap-authentication acs-mail acs-notification acs-object-management acs-object-management acs-outdated acs-person address-book adserver ae-portlet ajax-filestorage-ui ajax-photoalbum-ui ajaxhelper ams anon-eval application-track application-track-portlet assessment assessment-portlet attachments attendance auth-cas auth-http auth-ldap auth-pam auth-server authorize-gateway bboard-portlet beehive beehive-portlet bm-portlet bookmarks bookshelf boomerang bootstrap-icons bug-tracker bulk-mail caldav calendar calendar-includelet calendar-portlet captcha cards cards-portlet categories chat chat-includelet chat-portlet clickthrough clipboard cms cms-news-demo connections contacts contacts-lite contacts-portlet content-includelet content-portlet cookie-consent courses cronjob curriculum curriculum-central curriculum-portlet curriculum-tracker customer-service datamanager datamanager-portlet dbm diagram directory docker-s6 dotfolio dotfolio-ui dotkul dotkul-admin dotlrn dotlrn-admin dotlrn-ae dotlrn-application-track dotlrn-assessment dotlrn-attendance dotlrn-bboard dotlrn-beehive dotlrn-bm dotlrn-calendar dotlrn-cards dotlrn-catalog dotlrn-chat dotlrn-contacts dotlrn-content dotlrn-curriculum dotlrn-datamanager dotlrn-dotlrn dotlrn-ecommerce dotlrn-edit-this-page dotlrn-eduwiki dotlrn-evaluation dotlrn-expense-tracking dotlrn-faq dotlrn-forums dotlrn-fs dotlrn-glossar dotlrn-homework dotlrn-imsld dotlrn-invoices dotlrn-jabber dotlrn-lamsint dotlrn-latest dotlrn-learning-content dotlrn-lorsm dotlrn-messages dotlrn-mmplayer dotlrn-news dotlrn-news-aggregator dotlrn-photo-album dotlrn-portlet dotlrn-project-manager dotlrn-quota dotlrn-random-photo dotlrn-recruiting dotlrn-research dotlrn-static dotlrn-survey dotlrn-syllabus dotlrn-tasks dotlrn-user-tracking dotlrn-weblogger dotlrn-wikipedia dotlrn-wps dotlrn-xowiki dotlrndoc download dynamic-types ec-serial-numbers ecommerce edit-this-page edit-this-page-portlet eduwiki eduwiki-portlet email-handler evaluation evaluation-portlet expense-tracking expenses ezic-gateway fa-icons fabrik facebook-api faq faq-portlet feed-parser file-manager file-storage file-storage-includelet forums forums-includelet forums-portlet fs-portlet gatekeeper general-comments glossar glossar-portlet glossary highcharts image-magick ims-ent imsld imsld-portlet inventory-control invoices invoices-portlet jabber jabber-portlet lab-report lab-report-central lams-conf lamsint lamsint-portlet lars-blogger latest latest-portlet layout-managed-subsite layout-manager learning-content learning-content-portlet logger lors lors-central lorsm lorsm-includelet lorsm-portlet mail-tracking messages messages-portlet mmplayer mmplayer-portlet monitoring new-portal news news-aggregator news-aggregator-portlet news-includelet news-portlet notes oacs-dav oct-election online-catalog openacs-bootstrap3-theme openacs-bootstrap5-theme openfts-driver organizations package-builder page pages payflowpro payment-gateway photo-album photo-album-portlet places planner poll postal-address postcard press proctoring-support profile-provider project-manager project-manager-portlet quota quota-portlet random-photo-portlet ratings recruiting recruiting-portlet redirect ref-currency ref-gifi ref-itu ref-unspec ref-us-counties ref-us-states ref-us-zipcodes related-items research-portlet richtext-ckeditor4 richtext-ckeditor5 richtext-tinymce richtext-xinha robot-detection rss-support rules s5 sample-gateway schema-browser scholarship-fund scorm-core scorm-importer scorm-player scorm-simple-lms shipping-gateway shipping-tracking simple-survey simulation site-wide-search skin sloan-bboard soap-db soap-gateway spam spreadsheet static-pages static-portlet survey survey-builder-ui survey-library survey-portlet survey-reports t-account tasks tasks-portlet telecom-number theme-selva theme-zen timezones trackback tracker tsoap user-preferences user-profile user-tracking user-tracking-portlet value-based-shipping version-control views weblogger-portlet webmail webmail-system wiki wikipedia wikipedia-portlet workflow wp-slim wps-portlet xcms-ui xml-rpc xolp xooauth xotcl-core xotcl-request-monitor xowf xowf-monaco-plugin xowiki xowiki-includelet xowiki-portlet } if {$debug_p} { # # When debugging, pick only a subset of all packages. # set non_core_packages [lrange $non_core_packages 0 10] } foreach package_key $non_core_packages { apm_git_fetch_repo -path $non_core_packages_dir -repo $package_key } #---------------------------------------------------------------------- # Read all package .info files, building manifest file #---------------------------------------------------------------------- set update_pretty_date [lc_time_fmt [clock format [clock seconds] -format "%Y-%m-%d %T"] %c] foreach {channel branch} $core_channels { ns_log Notice "Repository: Channel $channel using branch $branch" # # Checkout the channel branch on the core repository. # apm_git_checkout_repo -path $core_repo_dir -branch $branch # # Try to check out the channel from the non-core packages. # set branch_packages [list] foreach package_key $non_core_packages { set package_dir ${non_core_packages_dir}${package_key} if {![file isdirectory $package_dir]} { ns_log notice "Package '$package_key' was not cloned in '$package_dir', skipping." continue } # # Not all packages will have a release branch. Skip the # package when the branch is not found. # if {$branch in [apm_git_repo_channels -path $package_dir]} { apm_git_checkout_repo -path $package_dir -branch $branch lappend branch_packages $package_key } } # # Now collect the info files for all core and non-core # packages belonging to this branch. # set info_files [list] foreach package_key $core_packages { if {[catch { set info_file [apm_package_info_file_path -path $core_packages_dir $package_key] } errmsg]} { ns_log warning "Cannot find an .info file on '$branch' for core package '$package_key':" $errmsg continue } lappend info_files $info_file } foreach package_key $branch_packages { if {[catch { set info_file [apm_package_info_file_path -path $non_core_packages_dir $package_key] } errmsg]} { ns_log warning "Cannot find an .info file on '$branch' for non.core package '$package_key':" $errmsg continue } lappend info_files $info_file } # Prepare channel directory set channel_dir "${work_dir}repository${sep}${channel}${sep}" file mkdir $channel_dir set manifest "<manifest>\n" template::multirow create packages \ package_path package_key version pretty_name \ package_type summary description \ release_date vendor_url vendor \ maturity maturity_text \ license license_url download_url set packages [list] foreach spec_file [lsort $info_files] { set package_path [file join {*}[lrange [file split $spec_file] 0 end-1]] set package_key [lindex [file split $spec_file] end-1] if { $package_key in $exclude_package_list } { ns_log Debug "Repository: Package $package_key is on list of packages to exclude - skipping" continue } unset -nocomplain pkg_info ad_try { array set pkg_info [apm_read_package_info_file $spec_file] if { $pkg_info(package.key) in $packages } { ns_log Debug "Repository: Skipping package $package_key, because we already have another version of it" } else { lappend packages $pkg_info(package.key) append manifest \ " <package>" \n \ " <package-key>[ns_quotehtml $pkg_info(package.key)]</package-key>\n" \ " <version>[ns_quotehtml $pkg_info(name)]</version>\n" \ " <pretty-name>[ns_quotehtml $pkg_info(package-name)]</pretty-name>\n" \ " <package-type>[ns_quotehtml $pkg_info(package.type)]</package-type>\n" \ " <summary>[ns_quotehtml $pkg_info(summary)]</summary>\n" \ " <description format=\"[ns_quotehtml $pkg_info(description.format)]\">" \ [ns_quotehtml $pkg_info(description)] "</description>\n" \ " <release-date>[ns_quotehtml $pkg_info(release-date)]</release-date>\n" \ " <vendor url=\"[ns_quotehtml $pkg_info(vendor.url)]\">" \ [ns_quotehtml $pkg_info(vendor)] "</vendor>\n" \ " <license url=\"[ns_quotehtml $pkg_info(license.url)]\">" \ [ns_quotehtml $pkg_info(license)] "</license>\n" \ " <maturity>$pkg_info(maturity)</maturity>\n" foreach e $pkg_info(install) { append manifest " <install package=\"$e\"/>\n" } set apm_file "${channel_dir}${pkg_info(package.key)}-${pkg_info(name)}.apm" ns_log Notice "Repository: Building package $package_key for channel $channel" set files [apm_get_package_files \ -all \ -include_data_model_files \ -all_db_types \ -package_key $pkg_info(package.key) \ -package_path $package_path] if { [llength $files] == 0 } { ns_log Notice "Repository: No files in package" } else { ns_log Notice "Repository: [llength $files] files in package $pkg_info(package.key) ($channel)" set cmd [list exec [apm_tar_cmd] cf - 2>/dev/null] # The path to the 'packages' directory in the checkout set packages_root_path [file join {*}[lrange [file split $spec_file] 0 end-2]] set fp [ad_opentmpfile tmp_filename] foreach file $files { puts $fp $package_key/$file } close $fp lappend cmd -C $packages_root_path --files-from $tmp_filename lappend cmd "|" [apm_gzip_cmd] -c ">" $apm_file ns_log Notice "Executing: exec $cd_helper $packages_root_path $cmd" if {[catch "exec $cd_helper $packages_root_path $cmd" errmsg]} { ns_log Error "Error during tar in repository creation for\ file ${channel_dir}$pkg_info(package.key)-$pkg_info(name).apm:\ \n$errmsg\n$::errorCode,$::errorInfo" } file delete -- $tmp_filename } set apm_url "${repository_url}$channel/$pkg_info(package.key)-$pkg_info(name).apm" template::multirow append packages \ $package_path $package_key $pkg_info(name) $pkg_info(package-name) \ $pkg_info(package.type) $pkg_info(summary) $pkg_info(description) \ $pkg_info(release-date) $pkg_info(vendor.url) $pkg_info(vendor) \ $pkg_info(maturity) $pkg_info(maturity_text) \ $pkg_info(license) $pkg_info(license.url) $apm_url append manifest " <download-url>$apm_url</download-url>\n" foreach elm $pkg_info(provides) { append manifest " <provides " \ "url=\"[ns_quotehtml [lindex $elm 0]]\" " \ "version=\"[ns_quotehtml [lindex $elm 1]]\" />\n" } foreach elm $pkg_info(requires) { append manifest " <requires " \ "url=\"[ns_quotehtml [lindex $elm 0]]\" " \ "version=\"[ns_quotehtml [lindex $elm 1]]\" />\n" } append manifest " </package>\n" } } on error {errorMsg} { ns_log Notice "Repository: Error on spec_file $spec_file: $errorMsg\n$::errorInfo\n" } } append manifest "</manifest>\n" ns_log Notice "Repository: Writing $channel manifest to ${channel_dir}manifest.xml" set fw [open "${channel_dir}manifest.xml" w] puts $fw $manifest close $fw ns_log Notice "Repository: Writing $channel index page to ${channel_dir}index.adp" set fw [open "${channel_dir}index.adp" w] set packages [lsort $packages] puts $fw "<master>\n<property name=\"doc(title)\">OpenACS $channel Compatible Packages</property>\n\n" puts $fw "<h1>OpenACS $channel (Git branch $branch)</h1> <p>Packages can be installed with the OpenACS Automated Installer on your OpenACS site at <code>/acs-admin/install</code>. Only packages potentially compatible with your OpenACS kernel will be shown.</p> " set category_title(core) "Core Packages" set package_keys(core) $core_packages set category_title(common-app) "Common Applications" set package_keys(common-app) { xowiki xotcl-request-monitor file-storage acs-developer-support forums calendar news faq } set category_title(extra) "Extra Packages and Libraries" set package_keys(extra) "" foreach p $packages { if {$p ni $package_keys(core) && $p ni $package_keys(common-app)} { lappend package_keys(extra) $p } } foreach category {core common-app extra} { template::multirow create pkgs \ package_path package_key version pretty_name \ package_type summary description \ release_date vendor_url vendor \ maturity maturity_text \ license license_url download_url template::multirow foreach packages { if {$package_key in $package_keys($category)} { template::multirow append pkgs \ $package_path $package_key $version $pretty_name \ $package_type $summary $description \ $release_date $vendor_url $vendor \ $maturity $maturity_text \ $license $license_url $download_url } } puts $fw "\n<h2>$category_title($category)</h2>\n" puts $fw [template::adp_include $channel_index_template \ [list channel $channel &pkgs pkgs update_pretty_date $update_pretty_date]] } close $fw ns_log Notice "Repository: Channel $channel complete." } ns_log Notice "Repository: Finishing Repository" foreach channel [dict keys $core_channels] { if {[regexp {^([1-9][0-9]*)-([0-9]+)$} $channel . major minor]} { # # *-compat channels: The "patchlevel" of these channels is # the highest possible value, higher than the released # -final channels. # set tag_order([format %.3d $major]-[format %.3d $minor]-999) $channel set tag_label($channel) "OpenACS $major.$minor" } elseif {[regexp {^([1-9][0-9]*)-([0-9]+)-([0-9]+)$} $channel . major minor patch]} { # # *-final channels: a concrete patchlevel is provided. # set tag_order([format %.3d $major]-[format %.3d $minor]-[format %.3d $patch]) $channel set tag_label($channel) "OpenACS $major.$minor.$patch" } else { set tag_order(999-999-999) $channel set tag_label($channel) "OpenACS $channel" } } # Write the index page ns_log Notice "Repository: Writing repository index page to ${work_dir}repository/index.adp" template::multirow create channels name tag label foreach key [lsort -decreasing [array names tag_order]] { set channel $tag_order($key) template::multirow append channels $channel [dict get $core_channels $channel] $tag_label($channel) } set fw [open "${work_dir}repository/index.adp" w] puts $fw "<master>\n<property name=\"doc(title)\">OpenACS Package Repository</property>\n\n" puts $fw [template::adp_include -- $index_template \ [list &channels channels update_pretty_date $update_pretty_date]] close $fw # Add a redirector for outdated releases set fw [open "${work_dir}repository/index.vuh" w] puts $fw "ns_returnredirect /repository/" close $fw # Without the trailing slash set work_repository_dirname "${work_dir}repository" set repository_dirname [string range $repository_dir 0 end-1] set repository_bak "[string range $repository_dir 0 end-1]_bak" ns_log Notice "Repository: Moving work repository $work_repository_dirname to live repository dir at <a href=\"/repository\/>$repository_dir</a>\n" if { [file exists $repository_bak] } { file delete -force -- $repository_bak } if { [file exists $repository_dirname] } { file rename -- $repository_dirname $repository_bak } file rename -- $work_repository_dirname $repository_dirname ns_log Debug "Repository: DONE" return 0 } # # Local variables: # mode: tcl # tcl-indent-level: 4 # indent-tabs-mode: nil # End: