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 if (( $styles =~ /\bJAVAMODULE\b/ ) && ( ! ($variables->{'JAVAPRODUCT'} ))) { next; } 377 378 # Controlling the language! 379 # Only language independent feature or feature with the correct language will be included into the table 380 381 next if $onefeature->{'ismultilingual'} && ($onefeature->{'specificlanguage'} ne $language); 382 383 my $feature_gid =get_feature_gid($onefeature); 384 385 my $feature = { 386 'Feature' => $feature_gid, 387 'Feature_Parent' => get_feature_parent($onefeature), 388 'Title' => $onefeature->{'Name'}, 389 'Description' => $onefeature->{'Description'}, 390 'Display' => get_feature_display($onefeature), 391 'Level' => get_feature_level($onefeature), 392 'Directory_' => get_feature_directory($onefeature), 393 'Attributes' => get_feature_attributes($onefeature) 394 }; 395 push @$features, $feature; 396 397 # collecting all feature in global feature collector (so that properties can be set in property table) 398 $installer::globals::featurecollector{$feature_gid} = 1; 399 400 # collecting all language feature in feature collector for check of language selection 401 if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && $onefeature->{'ParentID'} ne $installer::globals::rootmodulegid) 402 { 403 $installer::globals::multilingual_only_modules{$feature_gid} = 1; 404 } 405 406 # collecting all application feature in global feature collector for check of application selection 407 if ( $styles =~ /\bAPPLICATIONMODULE\b/ ) 408 { 409 $installer::globals::application_modules{$feature_gid} = 1; 410 } 411 } 412 413 return $features; 414} 415 416 417 418 419=head add_missing_features($features) 420 421 When we are building a release, then there may be features missing 422 that where present in the source release. As missing features 423 would prevent patches from being created, we add the missing 424 features. 425 426 The returned feature hash is either identical to the given 427 $features or is a copy with the missing features added. 428 429=cut 430 431sub add_missing_features ($) 432{ 433 my ($features) = @_; 434 435 return $features if ! $installer::globals::is_release; 436 437 # Aquire the feature list of the source release. 438 my $source_feature_table = $installer::globals::source_msi->GetTable("Feature"); 439 my $feature_column_index = $source_feature_table->GetColumnIndex("Feature"); 440 441 # Prepare fast lookup of the target features. 442 my %target_feature_map = map {$_->{'Feature'} => $_} @$features; 443 444 # Find missing features. 445 my @missing_features = (); 446 foreach my $source_feature_row (@{$source_feature_table->GetAllRows()}) 447 { 448 my $feature_gid = $source_feature_row->GetValue($feature_column_index); 449 if ( ! defined $target_feature_map{$feature_gid}) 450 { 451 push @missing_features, $source_feature_row; 452 } 453 } 454 455 # Return when there are no missing features. 456 return $features if scalar @missing_features==0; 457 458 # Process the missing features. 459 my $extended_features = [@$features]; 460 foreach my $missing_feature_row (@missing_features) 461 { 462 my %feature = map 463 {$_ => $missing_feature_row->GetValue($_)} 464 ('Feature', 'Feature_Parent', 'Title', 'Description', 'Display', 'Level', 'Directory_', 'Attributes'); 465 push @$extended_features, \%feature; 466 467 $installer::logger::Lang->printf("added missing feature %s\n", $feature->{'Feature'}); 468 } 469 return $extended_features; 470} 471 472 473 474 475sub create_feature_table ($$$) 476{ 477 my ($basedir, $language, $features) = @_; 478 479 my @feature_table = (); 480 installer::windows::idtglobal::write_idt_header(\@feature_table, "feature"); 481 482 foreach my $feature (@$features) 483 { 484 my $line = join("\t", 485 $feature->{'Feature'}, 486 $feature->{'Feature_Parent'}, 487 $feature->{'Title'}, 488 $feature->{'Description'}, 489 $feature->{'Display'}, 490 $feature->{'Level'}, 491 $feature->{'Directory_'}, 492 $feature->{'Attributes'}) . "\n"; 493 494 push(@feature_table, $line); 495 } 496 497 my $filename = $basedir . $installer::globals::separator . "Feature.idt" . "." . $language; 498 installer::files::save_file($filename ,\@feature_table); 499 $installer::logger::Lang->printf("Created idt file: %s\n", $filename); 500} 501 5021; 503