VirtualBox

Changeset 2456 in kBuild for trunk/src/kObjCache


Ignore:
Timestamp:
Jul 7, 2011 11:25:30 PM (13 years ago)
Author:
bird
Message:

kObjCache: Record the preprocess and compile times. Named pipe support for windows.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/kObjCache/kObjCache.c

    r2413 r2456  
    55
    66/*
    7  * Copyright (c) 2007-2010 knut st. osmundsen <[email protected]>
     7 * Copyright (c) 2007-2011 knut st. osmundsen <[email protected]>
    88 *
    99 * This file is part of kBuild.
     
    5050#  include <unistd.h>
    5151#  include <sys/wait.h>
     52#  include <sys/time.h>
    5253# endif
    5354# if defined(_MSC_VER)
     
    6465# include <unistd.h>
    6566# include <sys/wait.h>
     67# include <sys/time.h>
    6668# ifndef O_BINARY
    6769#  define O_BINARY 0
     
    235237char *xstrdup(const char *pszIn)
    236238{
    237     char *psz = strdup(pszIn);
    238     if (!psz)
    239         FatalDie("out of memory (%d)\n", (int)strlen(pszIn));
     239    char *psz;
     240    if (pszIn)
     241    {
     242        psz = strdup(pszIn);
     243        if (!psz)
     244            FatalDie("out of memory (%d)\n", (int)strlen(pszIn));
     245    }
     246    else
     247        psz = NULL;
    240248    return psz;
    241249}
     
    252260
    253261
     262
     263
     264/**
     265 * Returns a millisecond timestamp.
     266 *
     267 * @returns Millisecond timestamp.
     268 */
     269static uint32_t NowMs(void)
     270{
     271#if defined(__WIN__)
     272    return GetTickCount();
     273#else
     274    int             iSavedErrno = errno;
     275    struct timeval  tv          = {0, 0};
     276
     277    gettimeofday(&tv, NULL);
     278    errno = iSavedErrno;
     279
     280    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
     281#endif
     282}
    254283
    255284
     
    10511080    /** Whether the compiler runs in piped mode (precompiler output on stdin). */
    10521081    unsigned fPipedCompile;
     1082    /** The name of the pipe that we're feeding the precompiled output to the
     1083     *  compiler via.  This is a Windows thing. */
     1084    char *pszNmPipeCompile;
    10531085    /** Cache entry key that's used for some quick digest validation. */
    10541086    uint32_t uKey;
     
    10651097        /** The precompiler output checksums that will produce the cached object. */
    10661098        KOCSUM SumHead;
     1099        /** The number of milliseconds spent precompiling. */
     1100        uint32_t cMsCpp;
     1101
    10671102        /** The object filename (relative to the cache file). */
    10681103        char *pszObjName;
     
    10731108        /** The checksum of the compiler argument vector. */
    10741109        KOCSUM SumCompArgv;
     1110        /** The number of milliseconds spent compiling. */
     1111        uint32_t cMsCompile;
     1112        /** @todo need a list of additional output files for MSC. */
     1113
    10751114        /** The target os/arch identifier. */
    10761115        char *pszTarget;
     
    11321171static void kOCEntryDestroy(PKOCENTRY pEntry)
    11331172{
     1173    /** @todo free pEntry->pszName? */
    11341174    free(pEntry->pszDir);
    11351175    free(pEntry->pszAbsPath);
     1176    free(pEntry->pszNmPipeCompile);
    11361177
    11371178    kOCSumDeleteChain(&pEntry->New.SumHead);
     
    12131254         */
    12141255        if (    !fgets(g_szLine, sizeof(g_szLine), pFile)
    1215             ||  strcmp(g_szLine, "magic=kObjCacheEntry-v0.1.0\n"))
     1256            ||  (   strcmp(g_szLine, "magic=kObjCacheEntry-v0.1.0\n")
     1257                 && strcmp(g_szLine, "magic=kObjCacheEntry-v0.1.1\n"))
     1258           )
    12161259        {
    12171260            InfoMsg(2, "bad cache file (magic)\n");
     
    12701313                    kOCSumAdd(&pEntry->Old.SumHead, &Sum);
    12711314                }
     1315                else if (!strcmp(g_szLine, "cpp-ms"))
     1316                {
     1317                    char *pszNext;
     1318                    if ((fBad = pEntry->Old.cMsCpp != 0))
     1319                        break;
     1320                    pEntry->Old.cMsCpp = strtoul(pszVal, &pszNext, 0);
     1321                    if ((fBad = pszNext && *pszNext))
     1322                        break;
     1323                }
    12721324                else if (!strcmp(g_szLine, "cc-argc"))
    12731325                {
     
    12901342                        break;
    12911343                    if ((fBad = kOCSumInitFromString(&pEntry->Old.SumCompArgv, pszVal)))
     1344                        break;
     1345                }
     1346                else if (!strcmp(g_szLine, "cc-ms"))
     1347                {
     1348                    char *pszNext;
     1349                    if ((fBad = pEntry->Old.cMsCompile != 0))
     1350                        break;
     1351                    pEntry->Old.cMsCompile = strtoul(pszVal, &pszNext, 0);
     1352                    if ((fBad = pszNext && *pszNext))
    12921353                        break;
    12931354                }
     
    14051466        do { int cch = expr; if (cch >= KOBJCACHE_MAX_LINE_LEN) FatalDie("Line too long: %d (max %d)\nexpr: %s\n", cch, KOBJCACHE_MAX_LINE_LEN, #expr); } while (0)
    14061467
    1407     fprintf(pFile, "magic=kObjCacheEntry-v0.1.0\n");
     1468    fprintf(pFile, "magic=kObjCacheEntry-v0.1.1\n");
    14081469    CHECK_LEN(fprintf(pFile, "target=%s\n", pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget));
    14091470    CHECK_LEN(fprintf(pFile, "key=%lu\n", (unsigned long)pEntry->uKey));
    1410     CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->New.pszObjName ? pEntry->New.pszObjName : pEntry->Old.pszObjName));
    1411     CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->New.pszCppName ? pEntry->New.pszCppName : pEntry->Old.pszCppName));
    1412     CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", pEntry->New.pszCppName ? pEntry->New.cbCpp : pEntry->Old.cbCpp));
     1471    CHECK_LEN(fprintf(pFile, "obj=%s\n",        pEntry->New.pszObjName ? pEntry->New.pszObjName : pEntry->Old.pszObjName));
     1472    CHECK_LEN(fprintf(pFile, "cpp=%s\n",        pEntry->New.pszCppName ? pEntry->New.pszCppName : pEntry->Old.pszCppName));
     1473    CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n",  pEntry->New.pszCppName ? pEntry->New.cbCpp      : pEntry->Old.cbCpp));
     1474    CHECK_LEN(fprintf(pFile, "cpp-ms=%lu\n",    pEntry->New.pszCppName ? pEntry->New.cMsCpp     : pEntry->Old.cMsCpp));
     1475    CHECK_LEN(fprintf(pFile, "cc-ms=%lu\n",     pEntry->New.pszCppName ? pEntry->New.cMsCompile : pEntry->Old.cMsCompile));
    14131476
    14141477    if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
     
    15851648 * @param   fRedirPreCompStdOut     Whether the precompiler is in piped mode.
    15861649 * @param   fRedirCompileStdIn      Whether the compiler is in piped mode.
    1587  */
    1588 static void kOCEntrySetPipedMode(PKOCENTRY pEntry, int fRedirPreCompStdOut, int fRedirCompileStdIn)
     1650 * @param   pszNmPipeCompile        The name of the named pipe to use to feed
     1651 *                                  the microsoft compiler.
     1652 */
     1653static void kOCEntrySetPipedMode(PKOCENTRY pEntry, int fRedirPreCompStdOut, int fRedirCompileStdIn,
     1654                                 const char *pszNmPipeCompile)
    15891655{
    15901656    pEntry->fPipedPreComp = fRedirPreCompStdOut;
    1591     pEntry->fPipedCompile = fRedirCompileStdIn;
     1657    pEntry->fPipedCompile = fRedirCompileStdIn || pszNmPipeCompile;
     1658    pEntry->pszNmPipeCompile = xstrdup(pszNmPipeCompile);
    15921659}
    15931660
     
    15981665 *
    15991666 * @param   papszArgv       Argument vector. The cArgv element is NULL.
     1667 * @param   pcMs            The cache entry member use for time keeping.  This
     1668 *                          will be set to the current timestamp.
    16001669 * @param   cArgv           The number of arguments in the vector.
    1601  */
    1602 static void kOCEntrySpawn(PCKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
     1670 * @param   pszMsg          Which operation this is, for use in messages.
     1671 * @param   pszStdOut       Where to redirect standard out.
     1672 */
     1673static void kOCEntrySpawn(PCKOCENTRY pEntry, uint32_t *pcMs, const char * const *papszArgv, unsigned cArgv,
     1674                          const char *pszMsg, const char *pszStdOut)
    16031675{
    16041676#if defined(__OS2__) || defined(__WIN__)
     
    16231695    }
    16241696
     1697    *pcMs = NowMs();
    16251698    errno = 0;
    16261699# ifdef __WIN__
     
    16291702    rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv);
    16301703# endif
     1704    *pcMs = NowMs() - *pcMs;
    16311705    if (rc < 0)
    16321706        FatalDie("%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
     
    16431717    int iStatus;
    16441718    pid_t pidWait;
    1645     pid_t pid = fork();
     1719    pid_t pid;
     1720
     1721    *pcMs = NowMs();
     1722    pid = fork();
    16461723    if (!pid)
    16471724    {
     
    16731750    while (pidWait < 0 && errno == EINTR)
    16741751        pidWait = waitpid(pid, &iStatus, 0);
     1752    *pcMs = NowMs() - *pcMs;
    16751753    if (pidWait != pid)
    16761754        FatalDie("%s - waitpid failed rc=%d: %s\n",
     
    16901768 *
    16911769 * @param   pEntry          The cache entry.
     1770 * @param   pcMs            The cache entry member use for time keeping.  This
     1771 *                          will be set to the current timestamp.
    16921772 * @param   papszArgv       Argument vector. The cArgv element is NULL.
    16931773 * @param   cArgv           The number of arguments in the vector.
     
    16961776 * @param   pszMsg          Message to start the info/error messages with.
    16971777 */
    1698 static pid_t kOCEntrySpawnChild(PCKOCENTRY pEntry, const char * const *papszArgv, unsigned cArgv, int fdStdIn, int fdStdOut, const char *pszMsg)
     1778static pid_t kOCEntrySpawnChild(PCKOCENTRY pEntry, uint32_t *pcMs, const char * const *papszArgv, unsigned cArgv,
     1779                                int fdStdIn, int fdStdOut, const char *pszMsg)
    16991780{
    17001781    pid_t pid;
     
    17291810     * Create the child process.
    17301811     */
     1812    *pcMs = NowMs();
    17311813#if defined(__OS2__) || defined(__WIN__)
    17321814    errno = 0;
     
    17771859 *
    17781860 * @param   pEntry      The cache entry.
     1861 * @param   pcMs        The millisecond timestamp that should be convert to
     1862 *                      elapsed time.
    17791863 * @param   pid         The child to wait for.
    17801864 * @param   pszMsg      Message to start the info/error messages with.
    17811865 */
    1782 static void kOCEntryWaitChild(PCKOCENTRY pEntry, pid_t pid, const char *pszMsg)
     1866static void kOCEntryWaitChild(PCKOCENTRY pEntry, uint32_t *pcMs, pid_t pid, const char *pszMsg)
    17831867{
    17841868    int iStatus = -1;
     
    17881872#ifdef __WIN__
    17891873    pidWait = _cwait(&iStatus, pid, _WAIT_CHILD);
     1874    *pcMs = NowMs() - *pcMs;
    17901875    if (pidWait == -1)
    17911876        FatalDie("%s - waitpid failed: %s\n", pszMsg, strerror(errno));
     
    17961881    while (pidWait < 0 && errno == EINTR)
    17971882        pidWait = waitpid(pid, &iStatus, 0);
     1883    *pcMs = NowMs() - *pcMs;
    17981884    if (pidWait != pid)
    17991885        FatalDie("%s - waitpid failed rc=%d: %s\n", pidWait, strerror(errno));
     
    18131899 * @param   pFDs            Where to store the two file descriptors.
    18141900 * @param   pszMsg          The operation message for info/error messages.
    1815  */
    1816 static void kOCEntryCreatePipe(PKOCENTRY pEntry, int *pFDs, const char *pszMsg)
     1901 * @param   pszPipeName     The pipe name if it is supposed to be named. (Windows only.)
     1902 */
     1903static void kOCEntryCreatePipe(PKOCENTRY pEntry, int *pFDs, const char *pszPipeName, const char *pszMsg)
    18171904{
    18181905    pFDs[0] = pFDs[1] = -1;
    18191906#if defined(__WIN__)
    1820     if (_pipe(pFDs, 0, _O_NOINHERIT | _O_BINARY) < 0)
     1907    if (pszPipeName)
     1908    {
     1909        HANDLE hPipe = CreateNamedPipeA(pszPipeName,
     1910                                       /*PIPE_ACCESS_OUTBOUND*/ PIPE_ACCESS_DUPLEX,
     1911                                       PIPE_READMODE_BYTE | PIPE_WAIT,
     1912                                       10 /* nMaxInstances */,
     1913                                       0x10000 /* nOutBuffer */,
     1914                                       0x10000 /* nInBuffer */,
     1915                                       NMPWAIT_WAIT_FOREVER,
     1916                                       NULL /* pSecurityAttributes */);
     1917
     1918        if (hPipe == INVALID_HANDLE_VALUE)
     1919            FatalDie("%s - CreateNamedPipe(%s) failed: %d\n", pszMsg, pszPipeName, GetLastError());
     1920
     1921        pFDs[1 /* write */] = _open_osfhandle((intptr_t)hPipe, _O_WRONLY | _O_TEXT | _O_NOINHERIT);
     1922        if (pFDs[1 /* write */] == -1)
     1923            FatalDie("%s - _open_osfhandle failed: %d\n", pszMsg, strerror(errno));
     1924    }
     1925    else if (_pipe(pFDs, 0, _O_NOINHERIT | _O_BINARY) < 0)
    18211926#else
    18221927    if (pipe(pFDs) < 0)
     
    18471952    pid_t pid;
    18481953
    1849     kOCEntryCreatePipe(pEntry, fds, pszMsg);
    1850     pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, -1, fds[1 /* write */], pszMsg);
     1954    kOCEntryCreatePipe(pEntry, fds, NULL, pszMsg);
     1955    pid = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCpp, papszArgv, cArgv, -1, fds[1 /* write */], pszMsg);
    18511956
    18521957    pfnConsumer(pEntry, fds[0 /* read */]);
    18531958
    1854     kOCEntryWaitChild(pEntry, pid, pszMsg);
    1855 }
    1856 
    1857 
    1858 /**
    1859  * Spawns a child that consumes input on stdin.
     1959    kOCEntryWaitChild(pEntry, &pEntry->New.cMsCpp, pid, pszMsg);
     1960}
     1961
     1962
     1963/**
     1964 * Spawns a child that consumes input on stdin or via a named pipe.
    18601965 *
    18611966 * @param   papszArgv       Argument vector. The cArgv element is NULL.
     
    18711976    pid_t pid;
    18721977
    1873     kOCEntryCreatePipe(pEntry, fds, pszMsg);
    1874     pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, fds[0 /* read */], -1, pszMsg);
     1978    kOCEntryCreatePipe(pEntry, fds, pEntry->pszNmPipeCompile, pszMsg);
     1979    pid = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCompile, papszArgv, cArgv, fds[0 /* read */], -1, pszMsg);
     1980#ifdef __WIN__
     1981    if (pEntry->pszNmPipeCompile && !ConnectNamedPipe((HANDLE)_get_osfhandle(fds[1 /* write */]), NULL))
     1982        FatalDie("compile - ConnectNamedPipe failed: %d\n", GetLastError());
     1983#endif
    18751984
    18761985    pfnProducer(pEntry, fds[1 /* write */]);
    18771986
    1878     kOCEntryWaitChild(pEntry, pid, pszMsg);
     1987    kOCEntryWaitChild(pEntry, &pEntry->New.cMsCompile, pid, pszMsg);
    18791988}
    18801989
     
    19012010     * The producer.
    19022011     */
    1903     kOCEntryCreatePipe(pEntry, fds, pszMsg);
    1904     pidConsumer = kOCEntrySpawnChild(pEntry, papszProdArgv, cProdArgv, -1, fds[1 /* write */], pszMsg);
     2012    kOCEntryCreatePipe(pEntry, fds, NULL, pszMsg);
     2013    pidConsumer = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCpp, papszProdArgv, cProdArgv, -1, fds[1 /* write */], pszMsg);
    19052014    fdIn = fds[0 /* read */];
    19062015
     
    19082017     * The consumer.
    19092018     */
    1910     kOCEntryCreatePipe(pEntry, fds, pszMsg);
    1911     pidProducer = kOCEntrySpawnChild(pEntry, papszConsArgv, cConsArgv, fds[0 /* read */], -1, pszMsg);
     2019    kOCEntryCreatePipe(pEntry, fds, pEntry->pszNmPipeCompile, pszMsg);
     2020    pidProducer = kOCEntrySpawnChild(pEntry, &pEntry->New.cMsCompile, papszConsArgv, cConsArgv, fds[0 /* read */], -1, pszMsg);
    19122021    fdOut = fds[1 /* write */];
    19132022
     
    19202029     * Reap the children.
    19212030     */
    1922     kOCEntryWaitChild(pEntry, pidProducer, pszMsg);
    1923     kOCEntryWaitChild(pEntry, pidConsumer, pszMsg);
     2031    kOCEntryWaitChild(pEntry, &pEntry->New.cMsCpp, pidProducer, pszMsg);
     2032    kOCEntryWaitChild(pEntry, &pEntry->New.cMsCompile, pidConsumer, pszMsg);
    19242033}
    19252034
     
    20722181         */
    20732182        InfoMsg(3, "precompiling -> '%s'...\n", pEntry->New.pszCppName);
    2074         kOCEntrySpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
     2183        kOCEntrySpawn(pEntry, &pEntry->New.cMsCpp, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
    20752184        kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
    20762185        kOCEntryCalcChecksum(pEntry);
     
    21592268            if (errno == EINTR)
    21602269                continue;
     2270#ifdef __WIN__ /* HACK */
     2271            if (   errno == EINVAL
     2272                && pEntry->pszNmPipeCompile
     2273                && DisconnectNamedPipe((HANDLE)_get_osfhandle(fdOut))
     2274                && ConnectNamedPipe((HANDLE)_get_osfhandle(fdOut), NULL))
     2275            {
     2276                psz = pEntry->New.pszCppMapping;
     2277                cbLeft = (long)pEntry->New.cbCpp;
     2278            }
     2279            FatalDie("compile - write(%d,,%ld) failed: %s - _doserrno=%d\n", fdOut, cbLeft, strerror(errno), _doserrno);
     2280#else
    21612281            FatalDie("compile - write(%d,,%ld) failed: %s\n", fdOut, cbLeft, strerror(errno));
     2282#endif
    21622283        }
    21632284        psz += cbWritten;
     
    22022323            kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
    22032324        InfoMsg(3, "compiling -> '%s'...\n", pEntry->New.pszObjName);
    2204         kOCEntrySpawnConsumer(pEntry, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
    2205                               "compile", kOCEntryCompileProducer);
     2325        kOCEntrySpawnConsumer(pEntry, (const char * const *)pEntry->New.papszArgvCompile,
     2326                              pEntry->New.cArgvCompile, "compile", kOCEntryCompileProducer);
    22062327    }
    22072328    else
     
    22102331            kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
    22112332        InfoMsg(3, "compiling -> '%s'...\n", pEntry->New.pszObjName);
    2212         kOCEntrySpawn(pEntry, (const char * const *)pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "compile", NULL);
     2333        kOCEntrySpawn(pEntry, &pEntry->New.cMsCompile, (const char * const *)pEntry->New.papszArgvCompile,
     2334                      pEntry->New.cArgvCompile, "compile", NULL);
    22132335    }
    22142336}
     
    22272349static void kOCEntryTeeConsumer(PKOCENTRY pEntry, int fdIn, int fdOut)
    22282350{
     2351#ifdef __WIN__
     2352    unsigned fConnectedToCompiler = fdOut == -1 || pEntry->pszNmPipeCompile == NULL;
     2353#endif
    22292354    KOCSUMCTX Ctx;
    22302355    long cbLeft;
     
    22592384        psz[cbRead] = '\0';
    22602385        kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
     2386#ifdef __WIN__
     2387        if (   !fConnectedToCompiler
     2388            && !(fConnectedToCompiler = ConnectNamedPipe((HANDLE)_get_osfhandle(fdOut), NULL)))
     2389            FatalDie("precompile|compile - ConnectNamedPipe failed: %d\n", GetLastError());
     2390#endif
    22612391        do
    22622392        {
     
    36953825            "            <-f|--file <local-cache-file>>\n"
    36963826            "            <-t|--target <target-name>>\n"
    3697             "            [-r|--redir-stdout] [-p|--passthru]\n"
     3827            "            [-r|--redir-stdout] [-p|--passthru] [--named-pipe-compile <pipename>]\n"
    36983828            "            --kObjCache-cpp <filename> <precompiler + args>\n"
    36993829            "            --kObjCache-cc <object> <compiler + args>\n"
     
    37333863    const char *pszObjName = NULL;
    37343864    int fRedirCompileStdIn = 0;
     3865    const char *pszNmPipeCompile;
    37353866
    37363867    const char *pszTarget = NULL;
     
    38313962            pszTarget = argv[++i];
    38323963        }
     3964        else if (!strcmp(argv[i], "--named-pipe-compile"))
     3965        {
     3966            if (i + 1 >= argc)
     3967                return SyntaxError("%s requires a pipe name!\n", argv[i]);
     3968            pszNmPipeCompile = argv[++i];
     3969            fRedirCompileStdIn = 0;
     3970        }
    38333971        else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru"))
    38343972            fRedirPreCompStdOut = fRedirCompileStdIn = 1;
     
    38483986        {
    38493987            printf("kObjCache - kBuild version %d.%d.%d ($Revision$)\n"
    3850                    "Copyright (c) 2007-2009 knut st. osmundsen\n",
     3988                   "Copyright (c) 2007-2011 knut st. osmundsen\n",
    38513989                   KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH);
    38523990            return 0;
     
    39034041    kOCEntrySetTarget(pEntry, pszTarget);
    39044042    kOCEntrySetCppName(pEntry, pszPreCompName);
    3905     kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn);
     4043    kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn, pszNmPipeCompile);
    39064044
    39074045    /*
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette