Print this page
make: translate using gettext, rather than the unmaintainable catgets

Split Close
Expand all
Collapse all
          --- old/usr/src/cmd/make/bin/parallel.cc
          +++ new/usr/src/cmd/make/bin/parallel.cc
↓ open down ↓ 38 lines elided ↑ open up ↑
  39   39  #include <mksh/dosys.h>         /* redirect_io() */
  40   40  #include <mksh/macro.h>         /* expand_value() */
  41   41  #include <mksh/misc.h>          /* getmem() */
  42   42  #include <sys/signal.h>
  43   43  #include <sys/stat.h>
  44   44  #include <sys/types.h>
  45   45  #include <sys/utsname.h>
  46   46  #include <sys/wait.h>
  47   47  #include <unistd.h>
  48   48  #include <netdb.h>
       49 +#include <libintl.h>
  49   50  
  50   51  
  51   52  
  52   53  /*
  53   54   * Defined macros
  54   55   */
  55   56  #define MAXRULES                100
  56   57  
  57   58  /*
  58   59   * This const should be in avo_dms/include/AvoDmakeCommand.h
↓ open down ↓ 65 lines elided ↑ open up ↑
 124  125          Cmd_line                rule;
 125  126          Boolean                 silent_flag;
 126  127          Name                    target = line->body.line.target;
 127  128          Boolean                 wrote_state_file = false;
 128  129  
 129  130          if ((pmake_max_jobs == 0) &&
 130  131              (dmake_mode_type == parallel_mode)) {
 131  132                  if (local_host[0] == '\0') {
 132  133                          (void) gethostname(local_host, MAXNAMELEN);
 133  134                  }
 134      -                MBSTOWCS(wcs_buffer, NOCATGETS("DMAKE_MAX_JOBS"));
      135 +                MBSTOWCS(wcs_buffer, "DMAKE_MAX_JOBS");
 135  136                  dmake_name = GETNAME(wcs_buffer, FIND_LENGTH);
 136  137                  if (((prop = get_prop(dmake_name->prop, macro_prop)) != NULL) &&
 137  138                      ((dmake_value = prop->body.macro.value) != NULL)) {
 138  139                          pmake_max_jobs = atoi(dmake_value->string_mb);
 139  140                          if (pmake_max_jobs <= 0) {
 140      -                                warning(catgets(catd, 1, 308, "DMAKE_MAX_JOBS cannot be less than or equal to zero."));
 141      -                                warning(catgets(catd, 1, 309, "setting DMAKE_MAX_JOBS to %d."), PMAKE_DEF_MAX_JOBS);
      141 +                                warning(gettext("DMAKE_MAX_JOBS cannot be less than or equal to zero."));
      142 +                                warning(gettext("setting DMAKE_MAX_JOBS to %d."), PMAKE_DEF_MAX_JOBS);
 142  143                                  pmake_max_jobs = PMAKE_DEF_MAX_JOBS;
 143  144                          }
 144  145                  } else {
 145  146                          /*
 146  147                           * For backwards compatibility w/ PMake 1.x, when
 147  148                           * DMake 2.x is being run in parallel mode, DMake
 148  149                           * should parse the PMake startup file
 149  150                           * $(HOME)/.make.machines to get the pmake_max_jobs.
 150  151                           */
 151      -                        MBSTOWCS(wcs_buffer, NOCATGETS("PMAKE_MACHINESFILE"));
      152 +                        MBSTOWCS(wcs_buffer, "PMAKE_MACHINESFILE");
 152  153                          dmake_name = GETNAME(wcs_buffer, FIND_LENGTH);
 153  154                          if (((prop = get_prop(dmake_name->prop, macro_prop)) != NULL) &&
 154  155                              ((dmake_value = prop->body.macro.value) != NULL)) {
 155  156                                  make_machines_name = dmake_value;
 156  157                          } else {
 157  158                                  make_machines_name = NULL;
 158  159                          }
 159  160                          if ((pmake_max_jobs = read_make_machines(make_machines_name)) <= 0) {
 160  161                                  pmake_max_jobs = PMAKE_DEF_MAX_JOBS;
 161  162                          }
↓ open down ↓ 153 lines elided ↑ open up ↑
 315  316  
 316  317  static char     m2_file[MAXPATHLEN];
 317  318  static int      m2_shm_id = -1;
 318  319  static sem_t*   m2_shm_sem = 0;
 319  320  
 320  321  static int
 321  322  m2_init() {
 322  323          char    *var;
 323  324          key_t   key;
 324  325  
 325      -        if ((var = getenv(NOCATGETS("__DMAKE_M2_FILE__"))) == 0) {
      326 +        if ((var = getenv("__DMAKE_M2_FILE__")) == 0) {
 326  327                  /* compose tmp file name */
 327      -                sprintf(m2_file, NOCATGETS("%s/dmake.m2.%d.XXXXXX"), tmpdir, getpid());
      328 +                sprintf(m2_file, "%s/dmake.m2.%d.XXXXXX", tmpdir, getpid());
 328  329  
 329  330                  /* create tmp file */
 330  331                  int fd = mkstemp(m2_file);
 331  332                  if (fd < 0) {
 332  333                          return -1;
 333  334                  } else {
 334  335                          close(fd);
 335  336                  }
 336  337          } else {
 337  338                  /* using existing semaphore */
↓ open down ↓ 21 lines elided ↑ open up ↑
 359  360                  if (sem_init(m2_shm_sem, 1, pmake_max_jobs)) {
 360  361                          return -1;
 361  362                  }
 362  363  
 363  364                  /* alloc memory for env variable */
 364  365                  if ((var = (char*) malloc(MAXPATHLEN)) == 0) {
 365  366                          return -1;
 366  367                  }
 367  368  
 368  369                  /* put key to env */
 369      -                sprintf(var, NOCATGETS("__DMAKE_M2_FILE__=%s"), m2_file);
      370 +                sprintf(var, "__DMAKE_M2_FILE__=%s", m2_file);
 370  371                  if (putenv(var)) {
 371  372                          return -1;
 372  373                  }
 373  374          }
 374  375          return 0;
 375  376  }
 376  377  
 377  378  static void
 378  379  m2_fini() {
 379  380          if (m2_shm_id >= 0) {
↓ open down ↓ 94 lines elided ↑ open up ↑
 474  475   *  Static variables:
 475  476   *      job_adjust_mode Current job adjust mode
 476  477   */
 477  478  static void
 478  479  job_adjust_error() {
 479  480          if (job_adjust_mode != ADJUST_NONE) {
 480  481                  /* cleanup internals */
 481  482                  job_adjust_fini();
 482  483  
 483  484                  /* warning message for the user */
 484      -                warning(catgets(catd, 1, 339, "Encountered max jobs auto adjustment error - disabling auto adjustment."));
      485 +                warning(gettext("Encountered max jobs auto adjustment error - disabling auto adjustment."));
 485  486  
 486  487                  /* switch off job adjustment for the children */
 487      -                putenv(strdup(NOCATGETS("DMAKE_ADJUST_MAX_JOBS=NO")));
      488 +                putenv(strdup("DMAKE_ADJUST_MAX_JOBS=NO"));
 488  489  
 489  490                  /* and for this dmake */
 490  491                  job_adjust_mode = ADJUST_NONE;
 491  492          }
 492  493  }
 493  494  
 494  495  /*
 495  496   *  void job_adjust_init()
 496  497   *
 497  498   *  Description:
↓ open down ↓ 12 lines elided ↑ open up ↑
 510  511   *  Static variables:
 511  512   *      job_adjust_mode Current job adjust mode
 512  513   */
 513  514  static void
 514  515  job_adjust_init() {
 515  516          if (job_adjust_mode == ADJUST_UNKNOWN) {
 516  517                  /* default mode */
 517  518                  job_adjust_mode = ADJUST_M1;
 518  519  
 519  520                  /* determine adjust mode */
 520      -                if (char *var = getenv(NOCATGETS("DMAKE_ADJUST_MAX_JOBS"))) {
 521      -                        if (strcasecmp(var, NOCATGETS("NO")) == 0) {
      521 +                if (char *var = getenv("DMAKE_ADJUST_MAX_JOBS")) {
      522 +                        if (strcasecmp(var, "NO") == 0) {
 522  523                                  job_adjust_mode = ADJUST_NONE;
 523      -                        } else if (strcasecmp(var, NOCATGETS("M2")) == 0) {
      524 +                        } else if (strcasecmp(var, "M2") == 0) {
 524  525                                  job_adjust_mode = ADJUST_M2;
 525  526                          }
 526  527                  }
 527  528  
 528  529                  /* M2 specific initialization */
 529  530                  if (job_adjust_mode == ADJUST_M2) {
 530  531                          if (m2_init()) {
 531  532                                  job_adjust_error();
 532  533                          }
 533  534                  }
↓ open down ↓ 80 lines elided ↑ open up ↑
 614  615  #endif /* TEAMWARE_MAKE_CMN && MAXJOBS_ADJUST_RFE4694000 */
 615  616          setvar_envvar();
 616  617          /*
 617  618           * Tell the user what DMake is doing.
 618  619           */
 619  620          if (!silent && output_mode != txt2_mode) {
 620  621                  /*
 621  622                   * Print local_host --> x job(s).
 622  623                   */
 623  624                  (void) fprintf(stdout,
 624      -                               catgets(catd, 1, 325, "%s --> %d %s\n"),
      625 +                               gettext("%s --> %d %s\n"),
 625  626                                 local_host,
 626  627                                 parallel_process_cnt + 1,
 627      -                               (parallel_process_cnt == 0) ? catgets(catd, 1, 124, "job") : catgets(catd, 1, 125, "jobs"));
      628 +                               (parallel_process_cnt == 0) ? gettext("job") : gettext("jobs"));
 628  629  
 629  630                  /* Print command line(s). */
 630  631                  tmp_index = 0;
 631  632                  while (commands[tmp_index] != NULL) {
 632  633                      /* No @ char. */
 633  634                      /* XXX - need to add [2] when + prefix is added */
 634  635                      if ((commands[tmp_index][0] != (int) at_char) &&
 635  636                          (commands[tmp_index][1] != (int) at_char)) {
 636  637                          tmp_index_str_ptr = commands[tmp_index];
 637  638                          if (*tmp_index_str_ptr == (int) hyphen_char) {
 638  639                                  tmp_index_str_ptr++;
 639  640                          }
 640  641                          (void) fprintf(stdout, "%s\n", tmp_index_str_ptr);
 641  642                      }
 642  643                      tmp_index++;
 643  644                  }
 644  645                  (void) fflush(stdout);
 645  646          }
 646  647  
 647  648          (void) sprintf(mbstring,
 648      -                        NOCATGETS("%s/dmake.stdout.%d.%d.XXXXXX"),
      649 +                        "%s/dmake.stdout.%d.%d.XXXXXX",
 649  650                          tmpdir,
 650  651                          getpid(),
 651  652                          file_number++);
 652  653  
 653  654          mktemp(mbstring);
 654  655  
 655  656          stdout_file = strdup(mbstring);
 656  657          stderr_file = NULL;
 657  658  
 658  659          if (!out_err_same) {
 659  660                  (void) sprintf(mbstring,
 660      -                                NOCATGETS("%s/dmake.stderr.%d.%d.XXXXXX"),
      661 +                                "%s/dmake.stderr.%d.%d.XXXXXX",
 661  662                                  tmpdir,
 662  663                                  getpid(),
 663  664                                  file_number++);
 664  665  
 665  666                  mktemp(mbstring);
 666  667  
 667  668                  stderr_file = strdup(mbstring);
 668  669          }
 669  670  
 670  671          process_running = run_rule_commands(local_host, commands);
↓ open down ↓ 256 lines elided ↑ open up ↑
 927  928                          }
 928  929                  }
 929  930          }
 930  931          /*
 931  932           * If still nothing found to build, we either have a deadlock
 932  933           * or a subtree with a dependency conflict with something waiting
 933  934           * to build.
 934  935           */
 935  936          if (quiescent) {
 936  937                  if (subtree_target == NULL) {
 937      -                        fatal(catgets(catd, 1, 126, "Internal error: deadlock detected in process_next"));
      938 +                        fatal(gettext("Internal error: deadlock detected in process_next"));
 938  939                  } else {
 939  940                          rp = *subtree_target;
 940  941                          if (debug_level > 0) {
 941      -                                warning(catgets(catd, 1, 127, "Conditional macro conflict encountered for %s between %s and %s"),
      942 +                                warning(gettext("Conditional macro conflict encountered for %s between %s and %s"),
 942  943                                          subtree_conflict2->string_mb,
 943  944                                          rp->target->string_mb,
 944  945                                          subtree_conflict->string_mb);
 945  946                          }
 946  947                          *subtree_target = (*subtree_target)->next;
 947  948                          if (rp->next == NULL) {
 948  949                                  running_tail = subtree_target;
 949  950                          }
 950  951                          recursion_level = rp->recursion_level;
 951  952                          doname_subtree(rp->target, rp->do_get, rp->implicit);
↓ open down ↓ 185 lines elided ↑ open up ↑
1137 1138                          } else {
1138 1139                                  return;
1139 1140                          }
1140 1141                  }
1141 1142                  for (rp = running_list;
1142 1143                       (rp != NULL) && (rp->pid != pid);
1143 1144                       rp = rp->next) {
1144 1145                          ;
1145 1146                  }
1146 1147                  if (rp == NULL) {
1147      -                        fatal(catgets(catd, 1, 128, "Internal error: returned child pid not in running_list"));
     1148 +                        fatal(gettext("Internal error: returned child pid not in running_list"));
1148 1149                  } else {
1149 1150                          rp->state = (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? build_ok : build_failed;
1150 1151                  }
1151 1152                  nohang = true;
1152 1153                  parallel_process_cnt--;
1153 1154  
1154 1155  #if defined (TEAMWARE_MAKE_CMN) && defined (MAXJOBS_ADJUST_RFE4694000)
1155 1156                  if (job_adjust_mode == ADJUST_M2) {
1156 1157                          if (m2_release_job()) {
1157 1158                                  job_adjust_error();
↓ open down ↓ 53 lines elided ↑ open up ↑
1211 1212                                  line2 = get_prop(rp->target->prop, line_prop);
1212 1213                          }
1213 1214  
1214 1215  
1215 1216                          /*
1216 1217                           * Check if there were any job output
1217 1218                           * from the parallel build.
1218 1219                           */
1219 1220                          if (rp->stdout_file != NULL) {
1220 1221                                  if (stat(rp->stdout_file, &out_buf) < 0) {
1221      -                                        fatal(catgets(catd, 1, 130, "stat of %s failed: %s"),
     1222 +                                        fatal(gettext("stat of %s failed: %s"),
1222 1223                                              rp->stdout_file,
1223 1224                                              errmsg(errno));
1224 1225                                  }
1225 1226  
1226 1227                                  if ((line2 != NULL) &&
1227 1228                                      (out_buf.st_size > 0)) {
1228 1229                                          cmds_length = 0;
1229 1230                                          for (rule = line2->body.line.command_used,
1230 1231                                                   silent_flag = silent;
1231 1232                                               rule != NULL;
↓ open down ↓ 6 lines elided ↑ open up ↑
1238 1239                                                  dump_out_file(rp->stdout_file, false);
1239 1240                                          }
1240 1241                                  }
1241 1242                                  (void) unlink(rp->stdout_file);
1242 1243                                  retmem_mb(rp->stdout_file);
1243 1244                                  rp->stdout_file = NULL;
1244 1245                          }
1245 1246  
1246 1247                          if (!out_err_same && (rp->stderr_file != NULL)) {
1247 1248                                  if (stat(rp->stderr_file, &out_buf) < 0) {
1248      -                                        fatal(catgets(catd, 1, 130, "stat of %s failed: %s"),
     1249 +                                        fatal(gettext("stat of %s failed: %s"),
1249 1250                                              rp->stderr_file,
1250 1251                                              errmsg(errno));
1251 1252                                  }
1252 1253                                  if ((line2 != NULL) &&
1253 1254                                      (out_buf.st_size > 0)) {
1254 1255                                          dump_out_file(rp->stderr_file, true);
1255 1256                                  }
1256 1257                                  (void) unlink(rp->stderr_file);
1257 1258                                  retmem_mb(rp->stderr_file);
1258 1259                                  rp->stderr_file = NULL;
↓ open down ↓ 5 lines elided ↑ open up ↑
1264 1265                          }
1265 1266                          rp->temp_file = NULL;
1266 1267                          if (rp->state == build_failed) {
1267 1268                                  line = get_prop(rp->target->prop, line_prop);
1268 1269                                  if (line != NULL) {
1269 1270                                          line->body.line.command_used = NULL;
1270 1271                                  }
1271 1272                                  if (continue_after_error ||
1272 1273                                      fatal_in_progress ||
1273 1274                                      !docheck) {
1274      -                                        warning(catgets(catd, 1, 256, "Command failed for target `%s'"),
     1275 +                                        warning(gettext("Command failed for target `%s'"),
1275 1276                                                  rp->command ? line2->body.line.target->string_mb : rp->target->string_mb);
1276 1277                                          build_failed_seen = true;
1277 1278                                  } else {
1278 1279                                          /*
1279 1280                                           * XXX??? - DMake needs to exit(),
1280 1281                                           * but shouldn't call fatal().
1281 1282                                           */
1282 1283  #ifdef PRINT_EXIT_STATUS
1283      -                                        warning(NOCATGETS("I'm in finish_children. rp->state == build_failed."));
     1284 +                                        warning("I'm in finish_children. rp->state == build_failed.");
1284 1285  #endif
1285 1286  
1286      -                                        fatal(catgets(catd, 1, 258, "Command failed for target `%s'"),
     1287 +                                        fatal(gettext("Command failed for target `%s'"),
1287 1288                                                  rp->command ? line2->body.line.target->string_mb : rp->target->string_mb);
1288 1289                                  }
1289 1290                          }
1290 1291                          if (!docheck) {
1291 1292                                  delete_running_struct(rp);
1292 1293                                  rp = *rp_prev;
1293 1294                                  if (rp == NULL) {
1294 1295                                          break;
1295 1296                                  } else {
1296 1297                                          goto bypass_for_loop_inc_4;
↓ open down ↓ 27 lines elided ↑ open up ↑
1324 1325   */
1325 1326  static void
1326 1327  dump_out_file(char *filename, Boolean err)
1327 1328  {
1328 1329          int             chars_read;
1329 1330          char            copybuf[BUFSIZ];
1330 1331          int             fd;
1331 1332          int             out_fd = (err ? 2 : 1);
1332 1333  
1333 1334          if ((fd = open(filename, O_RDONLY)) < 0) {
1334      -                fatal(catgets(catd, 1, 141, "open failed for output file %s: %s"),
     1335 +                fatal(gettext("open failed for output file %s: %s"),
1335 1336                        filename,
1336 1337                        errmsg(errno));
1337 1338          }
1338 1339          if (!silent && output_mode != txt2_mode) {
1339 1340                  (void) fprintf(err ? stderr : stdout,
1340 1341                                 err ?
1341      -                                catgets(catd, 1, 338, "%s --> Job errors\n") :
1342      -                                catgets(catd, 1, 259, "%s --> Job output\n"),
     1342 +                                gettext("%s --> Job errors\n") :
     1343 +                                gettext("%s --> Job output\n"),
1343 1344                                 local_host);
1344 1345                  (void) fflush(err ? stderr : stdout);
1345 1346          }
1346 1347          for (chars_read = read(fd, copybuf, BUFSIZ);
1347 1348               chars_read > 0;
1348 1349               chars_read = read(fd, copybuf, BUFSIZ)) {
1349 1350                  /*
1350 1351                   * Read buffers from the source file until end or error.
1351 1352                   */
1352 1353                  if (write(out_fd, copybuf, chars_read) < 0) {
1353      -                        fatal(catgets(catd, 1, 260, "write failed for output file %s: %s"),
     1354 +                        fatal(gettext("write failed for output file %s: %s"),
1354 1355                                filename,
1355 1356                                errmsg(errno));
1356 1357                  }
1357 1358          }
1358 1359          (void) close(fd);
1359 1360          (void) unlink(filename);
1360 1361  }
1361 1362  
1362 1363  /*
1363 1364   *      finish_doname(rp)
↓ open down ↓ 13 lines elided ↑ open up ↑
1377 1378          int             auto_count = rp->auto_count;
1378 1379          Name            *automatics = rp->automatics;
1379 1380          Doname          result = rp->state;
1380 1381          Name            target = rp->target;
1381 1382          Name            true_target = rp->true_target;
1382 1383          Property        *conditionals;
1383 1384  
1384 1385          recursion_level = rp->recursion_level;
1385 1386          if (result == build_ok) {
1386 1387                  if (true_target == NULL) {
1387      -                        (void) printf(NOCATGETS("Target = %s\n"), target->string_mb);
1388      -                        (void) printf(NOCATGETS(" State = %d\n"), result);
1389      -                        fatal(NOCATGETS("Internal error: NULL true_target in finish_doname"));
     1388 +                        (void) printf("Target = %s\n", target->string_mb);
     1389 +                        (void) printf(" State = %d\n", result);
     1390 +                        fatal("Internal error: NULL true_target in finish_doname");
1390 1391                  }
1391 1392                  /* If all went OK, set a nice timestamp */
1392 1393                  if (true_target->stat.time == file_doesnt_exist) {
1393 1394                          true_target->stat.time = file_max_time;
1394 1395                  }
1395 1396          }
1396 1397          target->state = result;
1397 1398          if (target->is_member) {
1398 1399                  Property member;
1399 1400  
↓ open down ↓ 9 lines elided ↑ open up ↑
1409 1410                  }
1410 1411          }
1411 1412          /*
1412 1413           * Check if we found any new auto dependencies when we
1413 1414           * built the target.
1414 1415           */
1415 1416          if ((result == build_ok) && check_auto_dependencies(target,
1416 1417                                                              auto_count,
1417 1418                                                              automatics)) {
1418 1419                  if (debug_level > 0) {
1419      -                        (void) printf(catgets(catd, 1, 261, "%*sTarget `%s' acquired new dependencies from build, checking all dependencies\n"),
     1420 +                        (void) printf(gettext("%*sTarget `%s' acquired new dependencies from build, checking all dependencies\n"),
1420 1421                                        recursion_level,
1421 1422                                        "",
1422 1423                                        true_target->string_mb);
1423 1424                  }
1424 1425                  target->rechecking_target = true;
1425 1426                  target->state = build_running;
1426 1427  
1427 1428                  /* [tolik, Tue Mar 25 1997]
1428 1429                   * Fix for bug 4038824:
1429 1430                   *       command line options set by conditional macros get dropped
↓ open down ↓ 347 lines elided ↑ open up ↑
1777 1778          Name            command;
1778 1779          Boolean         ignore;
1779 1780          int             length;
1780 1781          Doname          result;
1781 1782          Boolean         silent_flag;
1782 1783          wchar_t         *tmp_wcs_buffer;
1783 1784  
1784 1785          childPid = fork();
1785 1786          switch (childPid) {
1786 1787          case -1:        /* Error */
1787      -                fatal(catgets(catd, 1, 337, "Could not fork child process for dmake job: %s"),
     1788 +                fatal(gettext("Could not fork child process for dmake job: %s"),
1788 1789                        errmsg(errno));
1789 1790                  break;
1790 1791          case 0:         /* Child */
1791 1792                  /* To control the processed targets list is not the child's business */
1792 1793                  running_list = NULL;
1793 1794                  if(out_err_same) {
1794 1795                          redirect_io(stdout_file, (char*)NULL);
1795 1796                  } else {
1796 1797                          redirect_io(stdout_file, stderr_file);
1797 1798                  }
↓ open down ↓ 32 lines elided ↑ open up ↑
1830 1831                          }
1831 1832                          result = dosys(command,
1832 1833                                         ignore,
1833 1834                                         false,
1834 1835                                         false, /* bugs #4085164 & #4990057 */
1835 1836                                         /* BOOLEAN(silent_flag && ignore), */
1836 1837                                         always_exec, 
1837 1838                                         (Name) NULL);
1838 1839                          if (result == build_failed) {
1839 1840                                  if (silent_flag) {
1840      -                                        (void) printf(catgets(catd, 1, 152, "The following command caused the error:\n%s\n"), command->string_mb);
     1841 +                                        (void) printf(gettext("The following command caused the error:\n%s\n"), command->string_mb);
1841 1842                                  }
1842 1843                                  if (!ignore) {
1843 1844                                          _exit(1);
1844 1845                                  }
1845 1846                          }
1846 1847                  }
1847 1848                  _exit(0);
1848 1849                  break;
1849 1850          default:
1850 1851                  break;
↓ open down ↓ 55 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX