diff -r make-3.82/ChangeLog make-3.82-submit/ChangeLog
0a1,4
> 2011-03-02  Benjamin Robinson  <icaretaker@gmail.com>
> 
> 	* function.c: Added $(trimpath names...) and $(relpath names...) public APIs.
> 
diff -r make-3.82/function.c make-3.82-submit/function.c
1880a1881,2101
> /* Will return the path to a file or directory which
>    does not contain superfluous `.', `..' components nor any
>    repeated path separators ('/'). */
> 
> static char *
> trimpath (const char *name, char *apath)
> {
>   char *dest;
>   const char *start, *end, *fixed, *apath_limit;
>   char abspath = 0;
> 
>   if (name[0] == '\0' || apath == NULL) {
>     return NULL;
>   }
> 
>   apath_limit = apath + GET_PATH_MAX;
> 
>   fixed = dest = apath;
> 
>   /* A unix style absolute path */
>   if (name[0] == '/') {
>     ++fixed;
>     abspath = 1;
>   }
>   /* A Windows style absolute path */
>   if (name[1] == ':' && name[2] == '/') {
>     fixed += 3;
>     abspath = 1;
>   }
> 
>   for (start = end = name; *start != '\0'; start = end) {
>     unsigned long len;
> 
>     /* Take only the first path-separator. */
>     if (*start == '/') {
>       ++start;
>       *dest++ = '/';
>     }
> 
>     /* Skip sequence of multiple path-separators. */
>     while (*start == '/') {
>       ++start;
>     }
> 
>     /* Find end of path component. */
>     for (end = start; *end != '\0' && *end != '/'; ++end) {
>       ;
>     }
> 
>     len = end - start;
> 
>     if (len == 0) {
>       break;
>     } else if (len == 1 && start[0] == '.') {
>       /* Find next component */
>       while (*end == '/' && *end != '\0') {
>         ++end;
>       }
>     } else if (len == 2 && start[0] == '.' && start[1] == '.') {
>       if ( dest > fixed) {
>         /* Find next component */
>         while (*end == '/' && *end != '\0') {
>           ++end;
>         }
>         /* Remove previous component */
>         while (dest > fixed) {
>           --dest;
>           if (dest[-1] == '/') {
>             break;
>           }
>         }
>       } else if (abspath == 0) {
>         /* Add ../ to the path */
>         if (dest + len >= apath_limit) {
>           return NULL;
>         }
> 
>         dest = memcpy (dest, start, len);
>         dest += len;
>         *dest = '\0';
> 
>         fixed += 3;
>       } else {
>         /* Invalid path, return NULL */
>         return NULL;
>       }
>     } else {
>       if (dest + len >= apath_limit) {
>         return NULL;
>       }
> 
>       dest = memcpy (dest, start, len);
>       dest += len;
>       *dest = '\0';
>     }
>   }
> 
>   /* Strip the trailing separator if any. */
>   if (dest > apath && dest[-1] == '/') {
>     /* Unless name is an absolute path resulting in only '/' */
>     if (!(name[0] == '/' && dest == apath + 1)) {
>       --dest;
>     }
>   }
> 
>   /* If the resulting path is empty, return a '.' */
>   if (dest == apath) {
>     *dest++ = '.';
>   }
> 
>   /* Terminate the string. */
>   *dest = '\0';
> 
>   return apath;
> }
> 
> /* Will return the relative path to a file or directory which
>    does not contain superfluous `.', `..' components nor any
>    repeated path separators ('/'). */
> 
> static char *
> relpath (const char *name, char *apath)
> {
>   const char *apath_limit;
>   char abspath = 0;
>   char *dest = apath;
>   *dest = '\0';
> 
>   if (name[0] == '\0' || apath == NULL) {
>     return NULL;
>   }
> 
>   apath_limit = apath + GET_PATH_MAX;
> 
>   /* It is unlikely we would make it until here but just to make sure. */
>   if (!starting_directory) {
>     return NULL;
>   }
> 
>   /* A unix style absolute path */
>   if (name[0] == '/') {
>     abspath = 1;
>     if (starting_directory[1] == ':' && starting_directory[2] == '/') {
>       /* A Windows system should not get passed a unix style absolute path */
>       return NULL;
>     }
>   }
>   /* A Windows style absolute path */
>   if (name[1] == ':' && name[2] == '/') {
>     if (name[0] != starting_directory[0]) {
>       /* Cannot convert a path on a different drive letter to relative */
>       return NULL;
>     }
>     abspath = 1;
>   }
> 
>   if (abspath == 0) {
>     /* Path is already relative.  Just trim it. */
>     return trimpath(name, apath);
>   }
> 
>   unsigned long srclen = 0;
>   PATH_VAR (tname);
>   char *srcname = tname;
>   char *srcdir = starting_directory;
> 
>   /* Trim the absolute path "name" before converting it to a relative path */
>   if (trimpath(name, tname) == NULL) {
>     return NULL;
>   }
>   if (strlen(tname) != 1 && tname[0] != '/') {
>     strcat(tname, "/");
>   }
> 
>   /* Skip common characters in both paths */
>   while (*srcname == *srcdir && *srcname  != '\0' && *srcdir  != '\0') {
>     ++srcname;
>     ++srcdir;
>   }
>   /* Now rewind to last common / */
>   while (*srcname != '/' && *srcdir != '/' && srcdir != starting_directory) {
>     --srcname;
>     --srcdir;
>   }
> 
>   /* Add ../ for each remaining / in srcdir */
>   while (*srcdir != '\0') {
>     if (*srcdir++ == '/') {
>       if (dest + 3 >= apath_limit) {
>         return NULL;
>       }
>       strcat(dest, "../");
>       dest += 3;
>     }
>   }
> 
>   /* Add remaining srcname */
>   ++srcname;
>   srclen = strlen(srcname);
>   if (dest + srclen >= apath_limit) {
>     return NULL;
>   }
>   strcat(dest, srcname);
>   dest += srclen;
> 
>   /* Strip the trailing separator if any. */
>   if (dest > apath && dest[-1] == '/') {
>     --dest;
>   }
> 
>   /* If the resulting path is empty, return a '.' */
>   if (dest == apath) {
>     *dest++ = '.';
>   }
> 
>   /* Terminate the string. */
>   *dest = '\0';
> 
>   return apath;
> }
> 
2079a2301,2368
> static char *
> func_trimpath (char *o, char **argv, const char *funcname UNUSED)
> {
>   /* Expand the argument.  */
>   const char *p = argv[0];
>   const char *path = NULL;
>   int doneany = 0;
>   unsigned int len = 0;
>   PATH_VAR (in);
>   PATH_VAR (out);
> 
>   while ((path = find_next_token (&p, &len)) != 0)
>     {
>       if (len < GET_PATH_MAX)
>         {
>           strncpy (in, path, len);
>           in[len] = '\0';
> 
>           if (trimpath (in, out))
>             {
>               o = variable_buffer_output (o, out, strlen (out));
>               o = variable_buffer_output (o, " ", 1);
>               doneany = 1;
>             }
>         }
>     }
> 
>   /* Kill last space.  */
>   if (doneany)
>     --o;
> 
>   return o;
> }
> 
> static char *
> func_relpath (char *o, char **argv, const char *funcname UNUSED)
> {
>   /* Expand the argument.  */
>   const char *p = argv[0];
>   const char *path = NULL;
>   int doneany = 0;
>   unsigned int len = 0;
>   PATH_VAR (in);
>   PATH_VAR (out);
> 
>   while ((path = find_next_token (&p, &len)) != 0)
>     {
>       if (len < GET_PATH_MAX)
>         {
>           strncpy (in, path, len);
>           in[len] = '\0';
> 
>           if (relpath (in, out))
>             {
>               o = variable_buffer_output (o, out, strlen (out));
>               o = variable_buffer_output (o, " ", 1);
>               doneany = 1;
>             }
>         }
>     }
> 
>   /* Kill last space.  */
>   if (doneany)
>     --o;
> 
>   return o;
> }
> 
2114a2404
>   { STRING_SIZE_TUPLE("relpath"),       0,  1,  1,  func_relpath},
2117a2408
>   { STRING_SIZE_TUPLE("trimpath"),      0,  1,  1,  func_trimpath},
Only in make-3.82-submit/tests/scripts/functions: relpath
Only in make-3.82-submit/tests/scripts/functions: trimpath
