1#************************************************************** 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19# 20#************************************************************** 21 22 23 24package installer::windows::feature; 25 26use installer::existence; 27use installer::exiter; 28use installer::files; 29use installer::globals; 30use installer::sorter; 31use installer::worker; 32use installer::windows::idtglobal; 33use installer::windows::language; 34 35############################################################## 36# Returning the gid for a feature. 37# Attention: Maximum length 38############################################################## 39 40sub get_feature_gid 41{ 42 my ($onefeature) = @_; 43 44 my $gid = ""; 45 46 if ( $onefeature->{'gid'} ) { $gid = $onefeature->{'gid'}; } 47 48 # Attention: Maximum feature length is 38! 49 installer::windows::idtglobal::shorten_feature_gid(\$gid); 50 51 return $gid 52} 53 54############################################################## 55# Returning the gid of the parent. 56# Attention: Maximum length 57############################################################## 58 59sub get_feature_parent 60{ 61 my ($onefeature) = @_; 62 63 my $parentgid = ""; 64 65 if ( $onefeature->{'ParentID'} ) { $parentgid = $onefeature->{'ParentID'}; } 66 67 # The modules, hanging directly below the root, have to be root modules. 68 # Only then it is possible to make the "real" root module invisible by 69 # setting the display to "0". 70 71 if ( $parentgid eq $installer::globals::rootmodulegid ) { $parentgid = ""; } 72 73 # Attention: Maximum feature length is 38! 74 installer::windows::idtglobal::shorten_feature_gid(\$parentgid); 75 76 return $parentgid 77} 78 79############################################################## 80# Returning the display for a feature. 81# 0: Feature is not shown 82# odd: subfeatures are shown 83# even: subfeatures are not shown 84############################################################## 85 86sub get_feature_display 87{ 88 my ($onefeature) = @_; 89 90 my $display; 91 my $parentid = ""; 92 93 if ( $onefeature->{'ParentID'} ) { $parentid = $onefeature->{'ParentID'}; } 94 95 if ( $parentid eq "" ) 96 { 97 $display = "0"; # root module is not visible 98 } 99 elsif ( $onefeature->{'gid'} eq "gid_Module_Prg") # program module shows subfeatures 100 { 101 $display = "1"; # root module shows subfeatures 102 } 103 else 104 { 105 $display = "2"; # all other modules do not show subfeatures 106 } 107 108 # special case: Feature has flag "HIDDEN_ROOT" -> $display is 0 109 my $styles = ""; 110 if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } 111 if ( $styles =~ /\bHIDDEN_ROOT\b/ ) { $display = "0"; } 112 113 # Special handling for language modules. Only visible in multilingual installation set 114 if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && ( ! $installer::globals::ismultilingual )) { $display = "0"; } 115 116 # Special handling for c05office. No program module visible. 117 if (( $onefeature->{'gid'} eq "gid_Module_Prg" ) && ( $installer::globals::product =~ /c05office/i )) { $display = "0"; } 118 119 # making all feature invisible in Language packs! 120 if ( $installer::globals::languagepack ) { $display = "0"; } 121 122 return $display 123} 124 125############################################################## 126# Returning the level for a feature. 127############################################################## 128 129sub get_feature_level 130{ 131 my ($onefeature) = @_; 132 133 my $level = "20"; # the default 134 135 my $localdefault = ""; 136 137 if ( $onefeature->{'Default'} ) { $localdefault = $onefeature->{'Default'}; } 138 139 if ( $localdefault eq "NO" ) # explicitely set Default = "NO" 140 { 141 $level = "200"; # deselected in default installation, base is 100 142 if ( $installer::globals::patch ) { $level = "20"; } 143 } 144 145 # special handling for Java and Ada 146 if ( $onefeature->{'Name'} ) 147 { 148 if ( $onefeature->{'Name'} =~ /java/i ) { $level = $level + 40; } 149 } 150 151 # if FeatureLevel is defined in scp, this will be used 152 153 if ( $onefeature->{'FeatureLevel'} ) { $level = $onefeature->{'FeatureLevel'}; } 154 155 return $level 156} 157 158############################################################## 159# Returning the directory for a feature. 160############################################################## 161 162sub get_feature_directory 163{ 164 my ($onefeature) = @_; 165 166 my $directory; 167 168 $directory = "INSTALLLOCATION"; 169 170 return $directory 171} 172 173############################################################## 174# Returning the directory for a feature. 175############################################################## 176 177sub get_feature_attributes 178{ 179 my ($onefeature) = @_; 180 181 my $attributes; 182 183 # No advertising of features and no leaving on network. 184 # Feature without parent must not have the "2" 185 186 my $parentgid = ""; 187 if ( $onefeature->{'ParentID'} ) { $parentgid = $onefeature->{'ParentID'}; } 188 189 if (( $parentgid eq "" ) || ( $parentgid eq $installer::globals::rootmodulegid )) { $attributes = "8"; } 190 else { $attributes = "10"; } 191 192 return $attributes 193} 194 195################################################################################# 196# Replacing one variable in one files 197################################################################################# 198 199sub replace_one_variable 200{ 201 my ($translationfile, $variable, $searchstring) = @_; 202 203 for ( my $i = 0; $i <= $#{$translationfile}; $i++ ) 204 { 205 ${$translationfile}[$i] =~ s/\%$searchstring/$variable/g; 206 } 207} 208 209################################################################################# 210# Replacing the variables in the feature names and descriptions 211################################################################################# 212 213sub replace_variables 214{ 215 my ($translationfile, $variableshashref) = @_; 216 217 foreach $key (keys %{$variableshashref}) 218 { 219 my $value = $variableshashref->{$key}; 220 replace_one_variable($translationfile, $value, $key); 221 } 222} 223 224################################################################################# 225# Collecting the feature recursively. 226################################################################################# 227 228sub collect_modules_recursive 229{ 230 my ($modulesref, $parentid, $feature, $directaccess, $directgid, $directparent, $directsortkey, $sorted) = @_; 231 232 my @allchildren = (); 233 my $childrenexist = 0; 234 235 # Collecting children from Module $parentid 236 237 my $modulegid; 238 foreach $modulegid ( keys %{$directparent}) 239 { 240 if ( $directparent->{$modulegid} eq $parentid ) 241 { 242 my %childhash = ( "gid" => "$modulegid", "Sortkey" => "$directsortkey->{$modulegid}"); 243 push(@allchildren, \%childhash); 244 $childrenexist = 1; 245 } 246 } 247 248 # Sorting children 249 250 if ( $childrenexist ) 251 { 252 # Sort children 253 installer::sorter::sort_array_of_hashes_numerically(\@allchildren, "Sortkey"); 254 255 # Adding children to new array 256 my $childhashref; 257 foreach $childhashref ( @allchildren ) 258 { 259 my $gid = $childhashref->{'gid'}; 260 261 # Saving all lines, that have this 'gid' 262 263 my $unique; 264 foreach $unique ( keys %{$directgid} ) 265 { 266 if ( $directgid->{$unique} eq $gid ) 267 { 268 push(@{$feature}, ${$modulesref}[$directaccess->{$unique}]); 269 if ( $sorted->{$unique} == 1 ) { installer::exiter::exit_program("ERROR: Sorting feature failed! \"$unique\" already sorted.", "sort_feature"); } 270 $sorted->{$unique} = 1; 271 } 272 } 273 274 collect_modules_recursive($modulesref, $gid, $feature, $directaccess, $directgid, $directparent, $directsortkey, $sorted); 275 } 276 } 277} 278 279################################################################################# 280# Sorting the feature in specified order. Evaluated is the key "Sortkey", that 281# is set in scp2 projects. 282# The display order of modules in Windows Installer is dependent from the order 283# in the idt file. Therefore the order of the modules array has to be adapted 284# to the Sortkey order, before the idt file is created. 285################################################################################# 286 287sub sort_feature 288{ 289 my ($modulesref) = @_; 290 291 my @feature = (); 292 293 my %directaccess = (); 294 my %directparent = (); 295 my %directgid = (); 296 my %directsortkey = (); 297 my %sorted = (); 298 299 for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) 300 { 301 my $onefeature = ${$modulesref}[$i]; 302 303 my $uniquekey = $onefeature->{'uniquekey'}; 304 my $modulegid = $onefeature->{'gid'}; 305 306 $directaccess{$uniquekey} = $i; 307 308 $directgid{$uniquekey} = $onefeature->{'gid'}; 309 310 # ParentID and Sortkey are not saved for the 'uniquekey', but only for the 'gid' 311 312 if ( $onefeature->{'ParentID'} ) { $directparent{$modulegid} = $onefeature->{'ParentID'}; } 313 else { $directparent{$modulegid} = ""; } 314 315 if ( $onefeature->{'Sortkey'} ) { $directsortkey{$modulegid} = $onefeature->{'Sortkey'}; } 316 else { $directsortkey{$modulegid} = "9999"; } 317 318 # Bookkeeping: 319 $sorted{$uniquekey} = 0; 320 } 321 322 # Searching all feature recursively, beginning with ParentID = "" 323 my $parentid = ""; 324 collect_modules_recursive($modulesref, $parentid, \@feature, \%directaccess, \%directgid, \%directparent, \%directsortkey, \%sorted); 325 326 # Bookkeeping 327 my $modulekey; 328 foreach $modulekey ( keys %sorted ) 329 { 330 if ( $sorted{$modulekey} == 0 ) 331 { 332 $installer::logger::Lang->printf( 333 "Warning: Module \"%s\" could not be sorted. Added to the end of the module array.\n", 334 $modulekey); 335 push(@feature, ${$modulesref}[$directaccess{$modulekey}]); 336 } 337 } 338 339 return \@feature; 340} 341 342################################################################################# 343# Adding a unique key to the modules array. The gid is not unique for 344# multilingual modules. Only the combination from gid and specific language 345# is unique. Uniqueness is required for sorting mechanism. 346################################################################################# 347 348sub add_uniquekey 349{ 350 my ( $modulesref ) = @_; 351 352 for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) 353 { 354 my $uniquekey = ${$modulesref}[$i]->{'gid'}; 355 if ( ${$modulesref}[$i]->{'specificlanguage'} ) { $uniquekey = $uniquekey . "_" . ${$modulesref}[$i]->{'specificlanguage'}; } 356 ${$modulesref}[$i]->{'uniquekey'} = $uniquekey; 357 } 358} 359 360################################################################################# 361# Creating the file Feature.idt dynamically 362# Content: 363# Feature Feature_Parent Title Description Display Level Directory_ Attributes 364################################################################################# 365 366sub prepare_feature_table ($$$) 367{ 368 my ($modules, $language, $variables) = @_; 369 370 my $features = []; 371 372 foreach my $onefeature (@$modules) 373 { 374 # Java and Ada only, if the correct settings are set 375 my $styles = $onefeature->{'Styles'}; 376 $styles = "" unless defined $styles; 377 if (( $styles =~ /\bJAVAMODULE\b/ ) && ( ! ($variables->{'JAVAPRODUCT'} ))) { next; } 378 379 # Controlling the language! 380 # Only language independent feature or feature with the correct language will be included into the table 381 382 next if $onefeature->{'ismultilingual'} && ($onefeature->{'specificlanguage'} ne $language); 383 384 my $feature_gid =get_feature_gid($onefeature); 385 386 my $feature = { 387 'Feature' => $feature_gid, 388 'Feature_Parent' => get_feature_parent($onefeature), 389 'Title' => $onefeature->{'Name'}, 390 'Description' => $onefeature->{'Description'}, 391 'Display' => get_feature_display($onefeature), 392 'Level' => get_feature_level($onefeature), 393 'Directory_' => get_feature_directory($onefeature), 394 'Attributes' => get_feature_attributes($onefeature) 395 }; 396 push @$features, $feature; 397 398 # collecting all feature in global feature collector (so that properties can be set in property table) 399 $installer::globals::featurecollector{$feature_gid} = 1; 400 401 # collecting all language feature in feature collector for check of language selection 402 if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && $onefeature->{'ParentID'} ne $installer::globals::rootmodulegid) 403 { 404 $installer::globals::multilingual_only_modules{$feature_gid} = 1; 405 } 406 407 # collecting all application feature in global feature collector for check of application selection 408 if ( $styles =~ /\bAPPLICATIONMODULE\b/ ) 409 { 410 $installer::globals::application_modules{$feature_gid} = 1; 411 } 412 } 413 414 return $features; 415} 416 417 418 419 420=head add_missing_features($features) 421 422 When we are building a release, then there may be features missing 423 that where present in the source release. As missing features 424 would prevent patches from being created, we add the missing 425 features. 426 427 The returned feature hash is either identical to the given 428 $features or is a copy with the missing features added. 429 430=cut 431 432sub add_missing_features ($) 433{ 434 my ($features) = @_; 435 436 return $features if ! $installer::globals::is_release; 437 438 # Acquire the feature list of the source release. 439 my $source_feature_table = $installer::globals::source_msi->GetTable("Feature"); 440 my $feature_column_index = $source_feature_table->GetColumnIndex("Feature"); 441 442 # Prepare fast lookup of the target features. 443 my %target_feature_map = map {$_->{'Feature'} => $_} @$features; 444 445 # Find missing features. 446 my @missing_features = (); 447 foreach my $source_feature_row (@{$source_feature_table->GetAllRows()}) 448 { 449 my $feature_gid = $source_feature_row->GetValue($feature_column_index); 450 if ( ! defined $target_feature_map{$feature_gid}) 451 { 452 push @missing_features, $source_feature_row; 453 } 454 } 455 456 # Return when there are no missing features. 457 return $features if scalar @missing_features==0; 458 459 # Process the missing features. 460 my $extended_features = [@$features]; 461 foreach my $missing_feature_row (@missing_features) 462 { 463 my %feature = map 464 {$_ => $missing_feature_row->GetValue($_)} 465 ('Feature', 'Feature_Parent', 'Title', 'Description', 'Display', 'Level', 'Directory_', 'Attributes'); 466 push @$extended_features, \%feature; 467 468 $installer::logger::Lang->printf("added missing feature %s\n", $feature->{'Feature'}); 469 } 470 return $extended_features; 471} 472 473 474 475 476sub create_feature_table ($$$) 477{ 478 my ($basedir, $language, $features) = @_; 479 480 my @feature_table = (); 481 installer::windows::idtglobal::write_idt_header(\@feature_table, "feature"); 482 483 foreach my $feature (@$features) 484 { 485 my $line = join("\t", 486 $feature->{'Feature'}, 487 $feature->{'Feature_Parent'}, 488 $feature->{'Title'}, 489 $feature->{'Description'}, 490 $feature->{'Display'}, 491 $feature->{'Level'}, 492 $feature->{'Directory_'}, 493 $feature->{'Attributes'}) . "\n"; 494 495 push(@feature_table, $line); 496 } 497 498 my $filename = $basedir . $installer::globals::separator . "Feature.idt" . "." . $language; 499 installer::files::save_file($filename ,\@feature_table); 500 $installer::logger::Lang->printf("Created idt file: %s\n", $filename); 501} 502 5031; 504