6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2009, 2010, 2011, MinGW Project
10 * Implements the SetRequirements() method for the pkgActionItem class,
11 * together with additional components of the pkgSpecs class which are
12 * required specifically to support it.
15 * This is free software. Permission is granted to copy, modify and
16 * redistribute this software, under the provisions of the GNU General
17 * Public License, Version 3, (or, at your option, any later version),
18 * as published by the Free Software Foundation; see the file COPYING
19 * for licensing details.
21 * Note, in particular, that this software is provided "as is", in the
22 * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
23 * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
24 * PARTICULAR PURPOSE. Under no circumstances will the author, or the
25 * MinGW Project, accept liability for any damages, however caused,
26 * arising from the use of this software.
39 * Class Implementation: pkgSpecs; SetProperty and GetTarName methods
44 int buflen( pkginfo_t specs )
46 /* Local helper function, to determine the size of the buffer used
47 * to store the data content of all pkgSpecs fields; (it is also the
48 * length of the C string representing the associated package tarname,
49 * INCLUDING its NUL terminator).
51 int index = PACKAGE_TAG_COUNT;
52 while( (index-- > 0) && (specs[index] == NULL) )
54 * Locate the last populated data field in the content buffer...
56 /* ...then compute the total buffer length, from the start of
57 * the first field, up to the NUL terminator of the last.
59 return specs[index] ? specs[index] - *specs + strlen( specs[index] ) + 1 : 0;
63 int fieldlen( const char *string )
65 /* NULL-safe local helper function to determine the length of
66 * the C string representing a pkgSpecs data field, (EXCLUDING
67 * its NUL terminator); returns zero in the case of an unassigned
68 * field, identified by a NULL reference pointer.
70 return (string == NULL) ? 0 : strlen( string );
74 char *fieldcpy( char *dest, const char *src )
76 /* Local helper function to copy the content of a single
77 * pkgSpecs data field...
79 while( (*dest++ = *src++) != '\0' )
81 * ...copying byte by byte, including the terminating NUL,
82 * to the destination buffer...
84 /* ...and ultimately, return the next available location
85 * within the destination buffer.
91 char *bufcpy( int index, int end, char *buf, pkginfo_t specs )
93 /* Local helper function to copy a specified field range from
94 * its original location within the pkgSpecs content buffer, to
95 * an alternate location specified by "buf".
97 do { if( specs[index] != NULL )
99 * Copy each non-empty data field...
101 buf = fieldcpy( buf, specs[index] );
103 /* ...until all specified fields in the source range
106 } while( ++index < end );
108 /* Finally, return the location at which any further data should
109 * be appended to the data just copied.
114 const char *pkgSpecs::SetProperty( int index, const char *repl )
116 /* A private method to modify the content of any single data field
117 * within a pkgSpecs data buffer; it provides the core implementation
118 * for each of the public inline field manipulator methods.
122 /* We begin by computing the current size of the content buffer,
123 * and the size it will become after making the change.
125 int oldlen = buflen( specs );
126 int newlen = oldlen + fieldlen( repl ) - fieldlen( specs[index] );
128 if( (repl != NULL) && (specs[index] == NULL) )
130 * When inserting a non-empty value into a previously empty field,
131 * we need to allow an additional byte for an extra field separator.
135 if( newlen > oldlen )
137 /* The buffer size must increase; thus, there is insufficient space
138 * at the location of the field to be modified, to accomodate its new
139 * value, without corrupting the content of following fields. To avoid
140 * this, we use a temporary buffer in which we construct an image of
141 * the modified content...
143 char newbuf[ newlen ];
145 * ...first copying the content of all fields which precede that
146 * which is to be modified...
148 p = bufcpy( 0, index, newbuf, specs );
150 * ...appending the new value to be assigned to the modified field...
152 p = fieldcpy( p, repl );
154 * ...and then the content of any following unmodified fields.
156 bufcpy( index + 1, PACKAGE_TAG_COUNT, p, specs );
158 /* Having created this temporary image of the content buffer, as
159 * it is to become, adjust the allocation size of the original buffer,
160 * and copy its modified content into place.
162 if( (p = (char *)(content = realloc( content, newlen ))) != NULL )
163 memcpy( p, newbuf, newlen );
167 { /* In this case, the size of content buffer will either shrink, or
168 * it will remain unchanged; we may modify it in place, beginning at
169 * the location of the field which is to be modified, by writing its
170 * new value, if any, into place.
173 if( (repl != NULL) && (*repl != '\0') )
174 p = fieldcpy( p, repl );
176 /* If the new content of the modified field is exactly the same length
177 * as the content it has replaced, then the overall size of the content
178 * buffer does not change...
180 if( newlen == oldlen )
182 * ...so we have nothing more to do, to complete the modification...
184 return (char *)(content);
186 /* ...otherwise, there is residual data from the original content at
187 * the location now indicated by "p", and preceding the start of the
188 * content of the next populated field, (if any); clear this, moving
189 * the content of all following fields up to fill the gap...
191 bufcpy( index + 1, PACKAGE_TAG_COUNT, p, specs );
193 /* ...and adjust the allocation size to the reduced data length.
195 p = (char *)(content = realloc( content, newlen ));
198 /* Irrespective of which of the preceding paths we followed to get here,
199 * the size of the content buffer was adjusted, so...
203 /* ...provided it was not lost altogether...
205 if( (repl == NULL) || (*repl == '\0') )
207 * ...we either mark the content of the modified field as "deleted",
208 * when appropriate...
213 /* ...or we ensure that it is marked as having SOME content, in case
214 * it may have been empty beforehand; (note that this does NOT assign
215 * the correct pointer value -- any arbitrary non-NULL pointer value
216 * will suffice here, so we just adopt the physical start address of
217 * the content buffer).
221 /* Now, we walk through the content buffer, to fix up the data pointers
222 * for the content of any fields for which the start address may have
223 * changed, as a result of buffer size adjustment...
225 for( index = 0; index < PACKAGE_TAG_COUNT; index++ )
227 /* ...thus, for each non-empty field...
229 if( specs[index] != NULL )
231 /* ...assign the start address of the next available block of
232 * populated content data...
236 /* ...and advance the data pointer to the start of the next
237 * available block of content, (if any).
239 p += strlen( p ) + 1;
244 /* Finally, we return the address of the physical starting location
245 * of the (possibly relocated) pkgSpecs content buffer.
247 return (char*)(content);
250 const char *pkgSpecs::GetTarName( const char *buf )
252 /* Reconstitute the canonical tarname for the package
253 * identified by the current pkgSpecs record, returning
254 * the result in a C string buffer allocated by malloc();
255 * "buf" may be specified as NULL, otherwise it MUST point
256 * to a similarly allocated buffer, which is recycled.
258 char *dest = (char*)(realloc( (void*)(buf), buflen( specs ) ));
259 if( (buf = dest) != NULL )
261 * We have a valid buffer suitable for returning the result...
263 for( int index = PACKAGE_NAME; index < PACKAGE_TAG_COUNT; index++ )
265 /* For each field in the package specification...
267 char *src = specs[index];
268 if( (src != NULL) && (*src != '\0') )
270 /* ...for which a non-empty value is defined...
274 * ...when not the first such field, insert the
275 * appropriate field separator character...
277 *dest++ = (index < PACKAGE_FORMAT) ? '-' : '.';
279 /* ...then, noting that the release status may have an
280 * initial '$' token which was inserted by get_pkginfo(),...
282 if( (index == PACKAGE_RELEASE_STATUS) && (*src == '$') )
284 * ...and which we don't therefore want to include within
285 * this reverse transformation...
289 * ...copy the field content to the result buffer.
291 while( (*dest = *src++) != '\0' )
296 /* Return the fully reconstituted tarname, or NULL if the
297 * return buffer allocation failed.
306 * Class Implementation: pkgActionItem; SetRequirements method
312 /* Definition of the inheritance flags identifying those elements
313 * of the package and subsystem version fields, within a requirements
314 * specification, which are to be matched by a "%" wildcard.
316 * These flags apply to any ONE version number specification, which
317 * may represent EITHER the package version OR the subsystem version
318 * within a requirements specification; it takes the form:
320 * major.minor.patch-datestamp-index
322 * and the flags are interpreted to indicate...
324 INHERIT_NONE = 0, /* no "%" wildcard match requested */
325 INHERIT_VERSION, /* "major.minor.patch" matches a "%" wildcard */
326 INHERIT_BUILD, /* "datestamp-index" matches a "%" wildcard */
327 INHERIT_ALL /* "%" matches the entire version string */
331 bool inherited_requirement( const char *field )
333 /* Local helper function to identify a template field which is
334 * specified exactly as "%", and therefore must inherit its value
335 * from the referring package specification.
337 return ((field != NULL) && (field[0] == '%') && (field[1] == '\0'));
341 enum inherit_mode inherited( const char *ver, const char *bld )
343 /* Local helper function to assign "%" wildcard inheritance flags
344 * to a specified version number, where:
346 * "ver" represents the "major.minor.patch" part of the version
347 * "bld" represents the optional "datestamp-index" extension
349 enum inherit_mode retval = INHERIT_NONE;
351 /* Noting that the "%" wildcard must represent the respective part
352 * of the version specification in its entirety...
354 if( inherited_requirement( ver ) )
356 * ...flag a match on the "major.minor.patch" specification.
358 retval = INHERIT_VERSION;
360 /* Considering the optional "datestamp-index" specification...
364 /* ...when it isn't specified at all, then it is implicitly
365 * governed by a match on the "major.minor.patch" element...
367 if( retval == INHERIT_VERSION )
369 * ...thus promoting this to become a match on the entire
370 * "major.minor.patch-datestamp-index" specification.
372 retval = INHERIT_ALL;
374 else if( (bld[0] == '%') && (bld[1] == '\0') )
376 * ...otherwise we may, (less usefully, perhaps and therefore
377 * not to be encouraged), require a "%" wildcard match on the
378 * "datestamp-index" element alone, (with some other criterion
379 * applied to the "major.minor.patch" element -- perhaps even
380 * redundantly "%-%", which is equivalent to the simpler form
381 * of "%" alone, to match the entire version specification).
383 retval = (enum inherit_mode)(retval | INHERIT_BUILD);
385 /* The inheritance state is now fully specified; pass it back
392 const char *requirement( const char *wanted, pkgSpecs *dep )
394 /* Local helper function to generate "min_wanted" and "max_wanted"
395 * dependency specifications, for assignment within a pkgActionItem;
396 * propagation of version number fields inherited from the dependant,
397 * as specified by the "%" wildcard, is appropriately enforced. The
398 * resultant specification is ALWAYS returned on the heap, in memory
399 * dynamically allocated by malloc().
401 pkgSpecs id( wanted );
403 /* Evaluate inheritance of the PACKAGE version number specification...
405 enum inherit_mode clone;
406 clone = inherited( id.GetPackageVersion(), id.GetPackageBuild() );
407 if( (clone & INHERIT_VERSION) > INHERIT_NONE )
409 /* ...propagating its "major.minor.patch" element as required.
411 id.SetPackageVersion( dep->GetPackageVersion() );
413 /* We also consider any release status specification to represent
414 * an extension to the normal package version specification, so...
416 if( id.GetReleaseStatus() == NULL )
418 /* ...when this isn't explicitly overridden in the requirement
419 * specification, we propagate it, together with any associated
420 * release reference index, from the reference specification.
422 id.SetReleaseStatus( dep->GetReleaseStatus() );
423 id.SetReleaseIndex( dep->GetReleaseIndex() );
426 if( (clone & INHERIT_BUILD) > INHERIT_NONE )
428 * Likewise, for any associated "datestamp-index" element.
430 id.SetPackageBuild( dep->GetPackageBuild() );
432 /* Similarly, for the SUBSYSTEM version number specification...
434 clone = inherited( id.GetSubSystemVersion(), id.GetSubSystemBuild() );
435 if( (clone & INHERIT_VERSION) > INHERIT_NONE )
437 * ...propagate its "major.minor.patch" element as required...
439 id.SetSubSystemVersion( dep->GetSubSystemVersion() );
441 if( (clone & INHERIT_BUILD) > INHERIT_NONE )
443 * ...and similarly, its "datestamp-index" element.
445 id.SetSubSystemBuild( dep->GetSubSystemBuild() );
447 /* To establish association between source and binary package names,
448 * we may also need to propagate archive and compression type fields...
450 if( inherited_requirement( id.GetPackageFormat()) )
452 id.SetPackageFormat( dep->GetPackageFormat() );
453 if( id.GetCompressionType() == NULL )
454 id.SetCompressionType( dep->GetCompressionType() );
456 else if( inherited_requirement( id.GetCompressionType()) )
457 id.SetCompressionType( dep->GetCompressionType() );
459 /* Finally, reconstitute the canonical tarname representation of the
460 * specification, from its decomposed representation; (as a side effect,
461 * this automatically places the return string on the heap).
463 return id.GetTarName();
466 const char * pkgActionItem::SetRequirements( pkgXmlNode *req, pkgSpecs *dep )
468 /* Establish the selection criteria, for association of any
469 * particular package release with an action item.
471 flags &= ACTION_MASK;
473 /* First check for a strict equality requirement...
475 if( (min_wanted = req->GetPropVal( eq_key, NULL )) != NULL )
477 * ...and if specified, set the selection range such that only one
478 * specific release can be matched; evaluate any specified dependency
479 * relationship, to ensure that inherited version numbers are correctly
480 * propagated, and assign the resultant dynamically allocated tarname
481 * specifications to the respective constraints.
483 return max_wanted = min_wanted = requirement( min_wanted, dep );
486 { /* Check for either an inclusive, or a strictly exclusive,
487 * minimum requirement (release "greater" than) specification,
488 * setting the minimum release selector...
490 if( ((min_wanted = req->GetPropVal( ge_key, NULL )) == NULL)
491 && ((min_wanted = req->GetPropVal( gt_key, NULL )) != NULL) )
493 * ...and its selection mode flag accordingly.
495 flags |= STRICTLY_GT;
497 /* Similarly, check for an inclusive, or a strictly exclusive,
498 * maximum requirement (release "less" than) specification,
499 * setting the maximum release selector...
501 if( ((max_wanted = req->GetPropVal( le_key, NULL )) == NULL)
502 && ((max_wanted = req->GetPropVal( lt_key, NULL )) != NULL) )
504 * ...and its selection mode flag accordingly.
506 flags |= STRICTLY_LT;
509 /* Check the minimum required version specification...
511 if( min_wanted != NULL )
513 * ...ensuring that inherited version numbers are propagated, as
514 * required from the dependant, and assign the canonical tarname
515 * representation of the final specification on the heap.
517 min_wanted = requirement( min_wanted, dep );
521 if( max_wanted != NULL )
523 * ...likewise for the maximum required version specification.
525 max_wanted = requirement( max_wanted, dep );
527 /* Return a canonical representation of the requirements spec.
529 return (min_wanted == NULL) ? max_wanted : min_wanted;
532 EXTERN_C const char *pkgAssociateName( const char *map, const char *from )
534 /* Public function for derivation of the name of an associate package,
535 * such as a source package, from the name of the primary package, based
536 * on the relationship specified by the template passed via "map".
538 pkgSpecs ref( from );
539 return requirement( map, &ref );
542 /* $RCSfile$: end of file */