cmLocalNinjaGenerator.cxx 19 KB
Newer Older
1
2
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
3
#include "cmLocalNinjaGenerator.h"
Brad King's avatar
Brad King committed
4

5
6
7
#include <algorithm>
#include <assert.h>
#include <iterator>
8
#include <memory> // IWYU pragma: keep
9
10
11
12
#include <sstream>
#include <stdio.h>
#include <utility>

13
#include "cmCryptoHash.h"
14
#include "cmCustomCommand.h"
15
#include "cmCustomCommandGenerator.h"
16
#include "cmGeneratedFileStream.h"
17
18
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
19
#include "cmGlobalNinjaGenerator.h"
20
#include "cmMakefile.h"
21
#include "cmNinjaTargetGenerator.h"
22
#include "cmRulePlaceholderExpander.h"
23
#include "cmSourceFile.h"
24
#include "cmState.h"
25
#include "cmStateTypes.h"
26
#include "cmSystemTools.h"
27
#include "cmake.h"
28
#include "cmsys/FStream.hxx"
29

30
cmLocalNinjaGenerator::cmLocalNinjaGenerator(cmGlobalGenerator* gg,
31
                                             cmMakefile* mf)
32
  : cmLocalCommonGenerator(gg, mf, mf->GetState()->GetBinaryDirectory())
33
34
35
36
37
38
  , HomeRelativeOutputPath("")
{
}

// Virtual public methods.

39
40
41
cmRulePlaceholderExpander*
cmLocalNinjaGenerator::CreateRulePlaceholderExpander() const
{
42
43
44
  cmRulePlaceholderExpander* ret =
    new cmRulePlaceholderExpander(this->Compilers, this->VariableMappings,
                                  this->CompilerSysroot, this->LinkerSysroot);
45
46
47
48
  ret->SetTargetImpLib("$TARGET_IMPLIB");
  return ret;
}

49
50
51
52
53
54
cmLocalNinjaGenerator::~cmLocalNinjaGenerator()
{
}

void cmLocalNinjaGenerator::Generate()
{
55
56
  // Compute the path to use when referencing the current output
  // directory from the top output directory.
57
  this->HomeRelativeOutputPath = this->ConvertToRelativePath(
58
    this->GetBinaryDirectory(), this->GetCurrentBinaryDirectory());
59
  if (this->HomeRelativeOutputPath == ".") {
60
    this->HomeRelativeOutputPath.clear();
61
  }
62

63
  this->WriteProcessedMakefile(this->GetBuildFileStream());
64
#ifdef NINJA_GEN_VERBOSE_FILES
65
  this->WriteProcessedMakefile(this->GetRulesFileStream());
66
#endif
67

68
  // We do that only once for the top CMakeLists.txt file.
69
  if (this->IsRootMakefile()) {
70
71
    this->WriteBuildFileTop();

72
73
    this->WritePools(this->GetRulesFileStream());

74
75
76
    const std::string showIncludesPrefix =
      this->GetMakefile()->GetSafeDefinition("CMAKE_CL_SHOWINCLUDES_PREFIX");
    if (!showIncludesPrefix.empty()) {
77
78
      cmGlobalNinjaGenerator::WriteComment(this->GetRulesFileStream(),
                                           "localized /showIncludes string");
79
80
      this->GetRulesFileStream()
        << "msvc_deps_prefix = " << showIncludesPrefix << "\n\n";
81
    }
82
  }
83

84
  const std::vector<cmGeneratorTarget*>& targets = this->GetGeneratorTargets();
85
86
  for (cmGeneratorTarget* target : targets) {
    if (target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
87
      continue;
88
    }
89
    cmNinjaTargetGenerator* tg = cmNinjaTargetGenerator::New(target);
90
    if (tg) {
91
92
93
      tg->Generate();
      // Add the target to "all" if required.
      if (!this->GetGlobalNinjaGenerator()->IsExcluded(
94
95
96
            this->GetGlobalNinjaGenerator()->GetLocalGenerators()[0],
            target)) {
        this->GetGlobalNinjaGenerator()->AddDependencyToAll(target);
97
      }
98
99
      delete tg;
    }
100
  }
101
102
103
104
105

  this->WriteCustomCommandBuildStatements();
}

// TODO: Picked up from cmLocalUnixMakefileGenerator3.  Refactor it.
106
107
std::string cmLocalNinjaGenerator::GetTargetDirectory(
  cmGeneratorTarget const* target) const
108
109
{
  std::string dir = cmake::GetCMakeFilesDirectoryPostSlash();
110
  dir += target->GetName();
111
112
113
114
115
116
117
118
119
120
#if defined(__VMS)
  dir += "_dir";
#else
  dir += ".dir";
#endif
  return dir;
}

// Non-virtual public methods.

121
122
const cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
  const
123
{
124
125
  return static_cast<const cmGlobalNinjaGenerator*>(
    this->GetGlobalGenerator());
126
127
128
129
130
131
132
133
134
}

cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator()
{
  return static_cast<cmGlobalNinjaGenerator*>(this->GetGlobalGenerator());
}

// Virtual protected methods.

135
std::string cmLocalNinjaGenerator::ConvertToIncludeReference(
136
137
  std::string const& path, cmOutputConverter::OutputFormat format,
  bool forceFullPaths)
138
{
139
  if (forceFullPaths) {
140
141
    return this->ConvertToOutputFormat(cmSystemTools::CollapseFullPath(path),
                                       format);
142
  }
143
144
  return this->ConvertToOutputFormat(
    this->ConvertToRelativePath(this->GetBinaryDirectory(), path), format);
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
}

// Private methods.

cmGeneratedFileStream& cmLocalNinjaGenerator::GetBuildFileStream() const
{
  return *this->GetGlobalNinjaGenerator()->GetBuildFileStream();
}

cmGeneratedFileStream& cmLocalNinjaGenerator::GetRulesFileStream() const
{
  return *this->GetGlobalNinjaGenerator()->GetRulesFileStream();
}

const cmake* cmLocalNinjaGenerator::GetCMakeInstance() const
{
  return this->GetGlobalGenerator()->GetCMakeInstance();
}

cmake* cmLocalNinjaGenerator::GetCMakeInstance()
{
  return this->GetGlobalGenerator()->GetCMakeInstance();
}

void cmLocalNinjaGenerator::WriteBuildFileTop()
{
  // For the build file.
  this->WriteProjectHeader(this->GetBuildFileStream());
173
  this->WriteNinjaRequiredVersion(this->GetBuildFileStream());
174
175
176
177
178
179
180
181
182
  this->WriteNinjaFilesInclusion(this->GetBuildFileStream());

  // For the rule file.
  this->WriteProjectHeader(this->GetRulesFileStream());
}

void cmLocalNinjaGenerator::WriteProjectHeader(std::ostream& os)
{
  cmGlobalNinjaGenerator::WriteDivider(os);
183
184
  os << "# Project: " << this->GetProjectName() << std::endl
     << "# Configuration: " << this->ConfigName << std::endl;
185
186
187
  cmGlobalNinjaGenerator::WriteDivider(os);
}

188
189
190
void cmLocalNinjaGenerator::WriteNinjaRequiredVersion(std::ostream& os)
{
  // Default required version
191
  std::string requiredVersion =
192
    this->GetGlobalNinjaGenerator()->RequiredNinjaVersion();
193
194

  // Ninja generator uses the 'console' pool if available (>= 1.5)
195
  if (this->GetGlobalNinjaGenerator()->SupportsConsolePool()) {
196
197
    requiredVersion =
      this->GetGlobalNinjaGenerator()->RequiredNinjaVersionForConsolePool();
198
  }
199

200
201
202
203
204
205
206
207
208
209
  // The Ninja generator writes rules which require support for restat
  // when rebuilding build.ninja manifest (>= 1.8)
  if (this->GetGlobalNinjaGenerator()->SupportsManifestRestat() &&
      this->GetCMakeInstance()->DoWriteGlobVerifyTarget() &&
      !this->GetGlobalNinjaGenerator()->GlobalSettingIsOn(
        "CMAKE_SUPPRESS_REGENERATION")) {
    requiredVersion =
      this->GetGlobalNinjaGenerator()->RequiredNinjaVersionForManifestRestat();
  }

210
211
212
213
  cmGlobalNinjaGenerator::WriteComment(
    os, "Minimal version of Ninja required by this file");
  os << "ninja_required_version = " << requiredVersion << std::endl
     << std::endl;
214
215
}

216
217
218
219
void cmLocalNinjaGenerator::WritePools(std::ostream& os)
{
  cmGlobalNinjaGenerator::WriteDivider(os);

220
221
  const char* jobpools =
    this->GetCMakeInstance()->GetState()->GetGlobalProperty("JOB_POOLS");
222
223
224
  if (!jobpools) {
    jobpools = this->GetMakefile()->GetDefinition("CMAKE_JOB_POOLS");
  }
225
226
227
  if (jobpools) {
    cmGlobalNinjaGenerator::WriteComment(
      os, "Pools defined by global property JOB_POOLS");
228
229
    std::vector<std::string> pools;
    cmSystemTools::ExpandListArgument(jobpools, pools);
230
    for (std::string const& pool : pools) {
231
      const std::string::size_type eq = pool.find('=');
232
233
      unsigned int jobs;
      if (eq != std::string::npos &&
234
          sscanf(pool.c_str() + eq, "=%u", &jobs) == 1) {
235
236
237
        os << "pool " << pool.substr(0, eq) << std::endl;
        os << "  depth = " << jobs << std::endl;
        os << std::endl;
238
      } else {
239
240
241
242
        cmSystemTools::Error("Invalid pool defined by property 'JOB_POOLS': ",
                             pool.c_str());
      }
    }
243
  }
244
245
}

246
247
248
void cmLocalNinjaGenerator::WriteNinjaFilesInclusion(std::ostream& os)
{
  cmGlobalNinjaGenerator::WriteDivider(os);
249
250
  os << "# Include auxiliary files.\n"
     << "\n";
251
252
253
  cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator();
  std::string const ninjaRulesFile =
    ng->NinjaOutputPath(cmGlobalNinjaGenerator::NINJA_RULES_FILE);
254
  std::string const rulesFilePath = ng->EncodePath(ninjaRulesFile);
255
256
  cmGlobalNinjaGenerator::WriteInclude(os, rulesFilePath,
                                       "Include rules file.");
257
258
259
260
261
262
  os << "\n";
}

void cmLocalNinjaGenerator::WriteProcessedMakefile(std::ostream& os)
{
  cmGlobalNinjaGenerator::WriteDivider(os);
263
264
265
  os << "# Write statements declared in CMakeLists.txt:" << std::endl
     << "# " << this->Makefile->GetDefinition("CMAKE_CURRENT_LIST_FILE")
     << std::endl;
266
  if (this->IsRootMakefile()) {
267
    os << "# Which is the root file." << std::endl;
268
  }
269
270
271
272
  cmGlobalNinjaGenerator::WriteDivider(os);
  os << std::endl;
}

273
274
void cmLocalNinjaGenerator::AppendTargetOutputs(cmGeneratorTarget* target,
                                                cmNinjaDeps& outputs)
275
276
277
278
{
  this->GetGlobalNinjaGenerator()->AppendTargetOutputs(target, outputs);
}

279
void cmLocalNinjaGenerator::AppendTargetDepends(cmGeneratorTarget* target,
280
281
                                                cmNinjaDeps& outputs,
                                                cmNinjaTargetDepends depends)
282
{
283
284
  this->GetGlobalNinjaGenerator()->AppendTargetDepends(target, outputs,
                                                       depends);
285
286
}

287
void cmLocalNinjaGenerator::AppendCustomCommandDeps(
288
  cmCustomCommandGenerator const& ccg, cmNinjaDeps& ninjaDeps)
289
{
290
  const std::vector<std::string>& deps = ccg.GetDepends();
291
  for (std::string const& i : deps) {
292
    std::string dep;
293
    if (this->GetRealDependency(i, this->GetConfigName(), dep)) {
294
295
      ninjaDeps.push_back(
        this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(dep));
296
    }
297
298
299
  }
}

300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
std::string cmLocalNinjaGenerator::WriteCommandScript(
  std::vector<std::string> const& cmdLines, std::string const& customStep,
  cmGeneratorTarget const* target) const
{
  std::string scriptPath;
  if (target) {
    scriptPath = target->GetSupportDirectory();
  } else {
    scriptPath = this->GetCurrentBinaryDirectory();
    scriptPath += cmake::GetCMakeFilesDirectory();
  }
  cmSystemTools::MakeDirectory(scriptPath);
  scriptPath += '/';
  scriptPath += customStep;
#ifdef _WIN32
  scriptPath += ".bat";
#else
  scriptPath += ".sh";
#endif

  cmsys::ofstream script(scriptPath.c_str());

#ifndef _WIN32
  script << "set -e\n\n";
#endif

  for (auto const& i : cmdLines) {
    std::string cmd = i;
    // The command line was built assuming it would be written to
    // the build.ninja file, so it uses '$$' for '$'.  Remove this
    // for the raw shell script.
    cmSystemTools::ReplaceString(cmd, "$$", "$");
#ifdef _WIN32
    script << cmd << " || exit /b" << '\n';
#else
    script << cmd << '\n';
#endif
  }

  return scriptPath;
}

342
std::string cmLocalNinjaGenerator::BuildCommandLine(
343
344
  std::vector<std::string> const& cmdLines, std::string const& customStep,
  cmGeneratorTarget const* target) const
345
{
346
  // If we have no commands but we need to build a command anyway, use noop.
347
348
  // This happens when building a POST_BUILD value for link targets that
  // don't use POST_BUILD.
349
  if (cmdLines.empty()) {
350
    return cmGlobalNinjaGenerator::SHELL_NOOP;
351
  }
352

353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
  // If this is a custom step then we will have no '$VAR' ninja placeholders.
  // This means we can deal with long command sequences by writing to a script.
  // Do this if the command lines are on the scale of the OS limit.
  if (!customStep.empty()) {
    size_t cmdLinesTotal = 0;
    for (std::string const& cmd : cmdLines) {
      cmdLinesTotal += cmd.length() + 6;
    }
    if (cmdLinesTotal > cmSystemTools::CalculateCommandLineLengthLimit() / 2) {
      std::string const scriptPath =
        this->WriteCommandScript(cmdLines, customStep, target);
      std::string cmd
#ifndef _WIN32
        = "/bin/sh "
#endif
        ;
      cmd += this->ConvertToOutputFormat(
        this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(scriptPath),
        cmOutputConverter::SHELL);

      // Add an unused argument based on script content so that Ninja
      // knows when the command lines change.
      cmd += " ";
      cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
      cmd += hash.HashFile(scriptPath).substr(0, 16);
      return cmd;
    }
  }

382
  std::ostringstream cmd;
383
  for (std::vector<std::string>::const_iterator li = cmdLines.begin();
384
       li != cmdLines.end(); ++li)
385
#ifdef _WIN32
386
387
  {
    if (li != cmdLines.begin()) {
388
      cmd << " && ";
389
    } else if (cmdLines.size() > 1) {
390
      cmd << "cmd.exe /C \"";
391
    }
392
393
394
395
396
397
398
    // Put current cmdLine in brackets if it contains "||" because it has
    // higher precedence than "&&" in cmd.exe
    if (li->find("||") != std::string::npos) {
      cmd << "( " << *li << " )";
    } else {
      cmd << *li;
    }
399
400
  }
  if (cmdLines.size() > 1) {
401
    cmd << "\"";
402
  }
403
#else
404
405
  {
    if (li != cmdLines.begin()) {
406
407
      cmd << " && ";
    }
408
409
    cmd << *li;
  }
410
#endif
411
412
413
  return cmd.str();
}

414
void cmLocalNinjaGenerator::AppendCustomCommandLines(
415
  cmCustomCommandGenerator const& ccg, std::vector<std::string>& cmdLines)
416
417
{
  if (ccg.GetNumberOfCommands() > 0) {
418
    std::string wd = ccg.GetWorkingDirectory();
419
    if (wd.empty()) {
420
      wd = this->GetCurrentBinaryDirectory();
421
    }
422

423
    std::ostringstream cdCmd;
424
#ifdef _WIN32
425
    std::string cdStr = "cd /D ";
426
#else
427
    std::string cdStr = "cd ";
428
#endif
429
430
    cdCmd << cdStr
          << this->ConvertToOutputFormat(wd, cmOutputConverter::SHELL);
431
432
    cmdLines.push_back(cdCmd.str());
  }
433

434
  std::string launcher = this->MakeCustomLauncher(ccg);
435

436
  for (unsigned i = 0; i != ccg.GetNumberOfCommands(); ++i) {
437
    cmdLines.push_back(launcher +
438
439
                       this->ConvertToOutputFormat(ccg.GetCommand(i),
                                                   cmOutputConverter::SHELL));
440

441
442
443
444
445
    std::string& cmd = cmdLines.back();
    ccg.AppendArguments(i, cmd);
  }
}

446
447
void cmLocalNinjaGenerator::WriteCustomCommandBuildStatement(
  cmCustomCommand const* cc, const cmNinjaDeps& orderOnlyDeps)
448
{
449
  if (this->GetGlobalNinjaGenerator()->SeenCustomCommand(cc)) {
450
    return;
451
  }
452

453
  cmCustomCommandGenerator ccg(*cc, this->GetConfigName(), this);
454

455
456
457
  const std::vector<std::string>& outputs = ccg.GetOutputs();
  const std::vector<std::string>& byproducts = ccg.GetByproducts();
  cmNinjaDeps ninjaOutputs(outputs.size() + byproducts.size()), ninjaDeps;
458

459
460
  bool symbolic = false;
  for (std::vector<std::string>::const_iterator o = outputs.begin();
461
462
       !symbolic && o != outputs.end(); ++o) {
    if (cmSourceFile* sf = this->Makefile->GetSource(*o)) {
463
464
      symbolic = sf->GetPropertyAsBool("SYMBOLIC");
    }
465
  }
466

467
#if 0
468
#  error TODO: Once CC in an ExternalProject target must provide the \
469
470
471
    file of each imported target that has an add_dependencies pointing \
    at us.  How to know which ExternalProject step actually provides it?
#endif
472
  std::transform(outputs.begin(), outputs.end(), ninjaOutputs.begin(),
473
                 this->GetGlobalNinjaGenerator()->MapToNinjaPath());
474
  std::transform(byproducts.begin(), byproducts.end(),
475
476
                 ninjaOutputs.begin() + outputs.size(),
                 this->GetGlobalNinjaGenerator()->MapToNinjaPath());
477
  this->AppendCustomCommandDeps(ccg, ninjaDeps);
478

479
480
  for (std::string const& ninjaOutput : ninjaOutputs) {
    this->GetGlobalNinjaGenerator()->SeenCustomCommandOutput(ninjaOutput);
481
  }
482
483

  std::vector<std::string> cmdLines;
484
  this->AppendCustomCommandLines(ccg, cmdLines);
485
486

  if (cmdLines.empty()) {
487
488
    this->GetGlobalNinjaGenerator()->WritePhonyBuild(
      this->GetBuildFileStream(),
489
490
      "Phony custom command for " + ninjaOutputs[0], ninjaOutputs, ninjaDeps,
      cmNinjaDeps(), orderOnlyDeps, cmNinjaVars());
491
  } else {
492
493
494
495
496
497
    std::string customStep = cmSystemTools::GetFilenameName(ninjaOutputs[0]);
    // Hash full path to make unique.
    customStep += '-';
    cmCryptoHash hash(cmCryptoHash::AlgoSHA256);
    customStep += hash.HashString(ninjaOutputs[0]).substr(0, 7);

498
    this->GetGlobalNinjaGenerator()->WriteCustomCommandBuild(
499
500
501
      this->BuildCommandLine(cmdLines, customStep),
      this->ConstructComment(ccg), "Custom command for " + ninjaOutputs[0],
      cc->GetDepfile(), cc->GetUsesTerminal(),
502
      /*restat*/ !symbolic || !byproducts.empty(), ninjaOutputs, ninjaDeps,
503
504
505
506
      orderOnlyDeps);
  }
}

507
void cmLocalNinjaGenerator::AddCustomCommandTarget(cmCustomCommand const* cc,
508
                                                   cmGeneratorTarget* target)
509
{
510
  CustomCommandTargetMap::value_type v(cc, std::set<cmGeneratorTarget*>());
511
512
513
  std::pair<CustomCommandTargetMap::iterator, bool> ins =
    this->CustomCommandTargets.insert(v);
  if (ins.second) {
514
    this->CustomCommands.push_back(cc);
515
  }
516
  ins.first->second.insert(target);
517
518
519
520
}

void cmLocalNinjaGenerator::WriteCustomCommandBuildStatements()
{
521
522
523
  for (cmCustomCommand const* customCommand : this->CustomCommands) {
    CustomCommandTargetMap::iterator i =
      this->CustomCommandTargets.find(customCommand);
524
525
    assert(i != this->CustomCommandTargets.end());

526
527
528
529
530
531
532
533
534
    // A custom command may appear on multiple targets.  However, some build
    // systems exist where the target dependencies on some of the targets are
    // overspecified, leading to a dependency cycle.  If we assume all target
    // dependencies are a superset of the true target dependencies for this
    // custom command, we can take the set intersection of all target
    // dependencies to obtain a correct dependency list.
    //
    // FIXME: This won't work in certain obscure scenarios involving indirect
    // dependencies.
535
    std::set<cmGeneratorTarget*>::iterator j = i->second.begin();
536
537
    assert(j != i->second.end());
    std::vector<std::string> ccTargetDeps;
538
539
    this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(*j,
                                                                ccTargetDeps);
540
541
542
543
544
    std::sort(ccTargetDeps.begin(), ccTargetDeps.end());
    ++j;

    for (; j != i->second.end(); ++j) {
      std::vector<std::string> jDeps, depsIntersection;
545
      this->GetGlobalNinjaGenerator()->AppendTargetDependsClosure(*j, jDeps);
546
547
548
549
550
551
552
553
554
555
      std::sort(jDeps.begin(), jDeps.end());
      std::set_intersection(ccTargetDeps.begin(), ccTargetDeps.end(),
                            jDeps.begin(), jDeps.end(),
                            std::back_inserter(depsIntersection));
      ccTargetDeps = depsIntersection;
    }

    this->WriteCustomCommandBuildStatement(i->first, ccTargetDeps);
  }
}
556
557

std::string cmLocalNinjaGenerator::MakeCustomLauncher(
558
  cmCustomCommandGenerator const& ccg)
559
{
560
561
  const char* property_value =
    this->Makefile->GetProperty("RULE_LAUNCH_CUSTOM");
562

563
  if (!property_value || !*property_value) {
564
565
566
    return std::string();
  }

567
  // Expand rule variables referenced in the given launcher command.
568
  cmRulePlaceholderExpander::RuleVariables vars;
569

570
  std::string output;
571
  const std::vector<std::string>& outputs = ccg.GetOutputs();
572
  if (!outputs.empty()) {
573
    output = outputs[0];
574
    if (ccg.GetWorkingDirectory().empty()) {
575
576
      output =
        this->ConvertToRelativePath(this->GetCurrentBinaryDirectory(), output);
577
    }
578
    output = this->ConvertToOutputFormat(output, cmOutputConverter::SHELL);
579
580
581
  }
  vars.Output = output.c_str();

582
  std::unique_ptr<cmRulePlaceholderExpander> rulePlaceholderExpander(
583
584
    this->CreateRulePlaceholderExpander());

585
  std::string launcher = property_value;
586
  rulePlaceholderExpander->ExpandRuleVariables(this, launcher, vars);
587
  if (!launcher.empty()) {
588
589
590
591
592
    launcher += " ";
  }

  return launcher;
}