diff --git a/Makefile b/Makefile index 7d621589547d490929155900b0d0933edf1c1b15..99c1f340f4a2116f8a6852882ae77660fd3bdeaf 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ else endif endif -version := $(shell cat "version.lisp-expr") +version := $(shell cat "full-version.lisp-expr") #$(info $$version is [${version}]) version := $(patsubst "%",%,$(version)) #$(info $$version is [${version}]) @@ -119,8 +119,8 @@ load: build/asdf.lisp install: archive bump: bump-version - git commit -a -m "Bump version to $$(eval a=$$(cat version.lisp-expr) ; echo $$a)" - temp=$$(cat version.lisp-expr); temp="$${temp%\"}"; temp="$${temp#\"}"; git tag $$temp + git commit -a -m "Bump version to $$(eval a=$$(cat full-version.lisp-expr) ; echo $$a)" + temp=$$(cat full-version.lisp-expr); temp="$${temp%\"}"; temp="$${temp#\"}"; git tag $$temp bump-version: build/asdf.lisp ./bin/bump-version ${v} @@ -143,7 +143,7 @@ archive: build/asdf.lisp rm -r build/$(UIOPDIR) $(eval ASDFDIR := "asdf-$(version)") mkdir -p build/$(ASDFDIR) # asdf-defsystem tarball - cp -pHux build/asdf.lisp asdf.asd version.lisp-expr header.lisp README.md ${defsystem_lisp} build/$(ASDFDIR) + cp -pHux build/asdf.lisp asdf.asd full-version.lisp-expr version.lisp-expr header.lisp README.md ${defsystem_lisp} build/$(ASDFDIR) tar zcf "build/asdf-defsystem-${version}.tar.gz" -C build $(ASDFDIR) rm -r build/$(ASDFDIR) git archive --worktree-attributes --prefix="asdf-$(version)/" --format=tar -o "build/asdf-${version}.tar" ${version} #asdf-all tarball diff --git a/README.md b/README.md index 69312fb562b372134c0cc5a7d9dca94d807dbe3f..0704f0fb34427ee3b090c53c0c7f7d803c089810 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,10 @@ How do I navigate this source tree? the lisp scripting variants of the build system. * [version.lisp-expr](version.lisp-expr) - * The current version. Bumped up every time the code changes, using: + [full-version.lisp-expr](full-version.lisp-expr) + * The current version. The `full-version.lisp-expr` file contains + pre-release information, `version.lisp-expr` does not. Bumped up every + time the code changes, using: make bump diff --git a/asdf.asd b/asdf.asd index e61aa0f40d4905c95a96bfe933e81792a62facf5..65233094a02869dcfffdefa9e2a487c289873360 100644 --- a/asdf.asd +++ b/asdf.asd @@ -23,7 +23,7 @@ ;; and compulsory to sort them in defsystem-depends-on order. #+asdf3 (defsystem "asdf/prelude" - :version (:read-file-form "version.lisp-expr") + :version #+asdf3.4 (:read-file-form "full-version.lisp-expr") #-asdf3.4 (:read-file-form "version.lisp-expr") :around-compile call-without-redefinition-warnings ;; we need be the same as uiop :encoding :utf-8 :components ((:file "header"))) @@ -51,7 +51,7 @@ :bug-tracker "https://launchpad.net/asdf/" :mailto "asdf-devel@common-lisp.net" :source-control (:git "git://common-lisp.net/projects/asdf/asdf.git") - :version (:read-file-form "version.lisp-expr") + :version #+asdf3.4 (:read-file-form "full-version.lisp-expr") #-asdf3.4 (:read-file-form "version.lisp-expr") :build-operation monolithic-concatenate-source-op :build-pathname "build/asdf" ;; our target :around-compile call-without-redefinition-warnings ;; we need be the same as uiop @@ -93,7 +93,10 @@ :licence "MIT" :description "Another System Definition Facility" :long-description "ASDF builds Common Lisp software organized into defined systems." - :version "3.3.5.7" ;; to be automatically updated by make bump-version + ;; Having two :VERSION arguments makes the bump-version script a bit easier + ;; to write for a non-Perl expert. + #+asdf3.4 :version #+asdf3.4 "3.4.0-alpha.1" ;; to be automatically updated by make bump-version + #-asdf3.4 :version #-asdf3.4 "3.4.0" ;; to be automatically updated by make bump-version :depends-on () :components ((:module "build" :components ((:file "asdf")))) . #-asdf3 () #+asdf3 diff --git a/bin/bump-version b/bin/bump-version index f619c430a58113951b1891c7318e540afc6de1db..df030ab0de530d23d08257b34b59558a0aa4c098 100755 --- a/bin/bump-version +++ b/bin/bump-version @@ -4,6 +4,7 @@ use FindBin; use Getopt::Long; our $old; our $new; +our $new_no_pre_release; our $usage = 0; &GetOptions("help"=>\$usage, @@ -18,16 +19,18 @@ if ($usage) { } our $asdf_dir = $FindBin::RealBin . "/../"; -our $file = $asdf_dir . "version.lisp-expr"; +our $file = $asdf_dir . "full-version.lisp-expr"; our @transform_ref = ( - [ "version.lisp-expr", "\"", "\"" ], - [ "uiop/version.lisp", "(defparameter *uiop-version* \"", "\")" ], - [ "asdf.asd", " :version \"", "\" ;; to be automatically updated by make bump-version" ], - [ "header.lisp", "This is ASDF ", ": Another System Definition Facility." ], - [ "upgrade.lisp", "(asdf-version \"", "\")" ], - [ "doc/asdf.texinfo", "Manual for Version ", "" ], ); + [ "version.lisp-expr", "\"", "\"", 1], + [ "full-version.lisp-expr", "\"", "\"", 0], + [ "uiop/version.lisp", "(defparameter *uiop-version* \"", "\")", 0 ], + [ "asdf.asd", " #+asdf3.4 :version #+asdf3.4 \"", "\" ;; to be automatically updated by make bump-version", 0 ], + [ "asdf.asd", " #-asdf3.4 :version #-asdf3.4 \"", "\" ;; to be automatically updated by make bump-version", 1 ], + [ "header.lisp", "This is ASDF ", ": Another System Definition Facility.", 0 ], + [ "upgrade.lisp", "(asdf-version \"", "\")", 0 ], + [ "doc/asdf.texinfo", "Manual for Version ", "", 0 ], ); if ($#ARGV == 1) { $old = $ARGV[0]; @@ -40,6 +43,9 @@ if ($#ARGV == 1) { $new = bump_asdf_version($old); } +$new_no_pre_release = $new; +$new_no_pre_release =~ s/-.*//g; + print STDERR "Bumping from $old to $new\n"; transform_files(); @@ -65,14 +71,20 @@ sub transform_files { foreach my $entryptr (@transform_ref) { my @entry = @{$entryptr}; my $file = $entry[0]; + my $strip_pre_release = $entry[3]; print STDERR "Modifying file $file\n"; print STDERR "Prefix is $entry[1], suffix is $entry[2]\n"; - my $regex = "(" . quotemeta($entry[1]) . ")" . "((\\d+\\.)+\\d+)" . "(" . quotemeta($entry[2]) .")"; + my $regex = "(" . quotemeta($entry[1]) . ")" . "((\\d+\\.)+\\d+(\\-[a-zA-Z0-9_.-]+)?)" . "(" . quotemeta($entry[2]) .")"; my $filename = $asdf_dir . $file; my $data = read_text($filename); - my $count = ($data =~ s/$regex/$1$new$4/g); + my $count; + if ($strip_pre_release) { + $count = ($data =~ s/$regex/$1$new_no_pre_release$5/g); + } else { + $count = ($data =~ s/$regex/$1$new$5/g); + } if ($count == 0) { - die "Unable to replace $regex with $1$new$4"; + die "Unable to replace $regex with $1$new$5"; } # print STDERR "Writing $data to $filename\n"; write_text($filename, $data); diff --git a/bundle.lisp b/bundle.lisp index 2d8d73075f5b17ccaafa054481beb32c1d70419e..430dceacc8b768a8af17f530f00153dd4f074ff6 100644 --- a/bundle.lisp +++ b/bundle.lisp @@ -444,7 +444,7 @@ or of opaque libraries shipped along the source code.")) (library (second inputs)) (asd (output-file o s)) (name (if (and fasl asd) (pathname-name asd) (return-from perform))) - (version (component-version s)) + (version (component-version* s)) (dependencies (if (operation-monolithic-p o) ;; We want only dependencies, and we use basic-load-op rather than load-op so that @@ -481,9 +481,9 @@ which is probably not what you want; you probably need to tweak your output tran (let ((*package* (find-package :asdf-user))) (pprint `(defsystem ,name :class prebuilt-system - :version ,version :depends-on ,depends-on :components ((:compiled-file ,(pathname-name fasl))) + ,@(when version `(:version ,(version-string version))) ,@(when library `(:lib ,(file-namestring library)))) s) (terpri s))))) diff --git a/component.lisp b/component.lisp index 601f593047d6700e57ee94e13c57ec32309665c0..80870d765bb47ac2fdf9fc80758328ac69a52928 100644 --- a/component.lisp +++ b/component.lisp @@ -18,7 +18,7 @@ #:component-in-order-to #:component-sideway-dependencies #:component-if-feature #:around-compile-hook #:component-description #:component-long-description - #:component-version #:version-satisfies + #:component-version #:component-version* #:version-satisfies #:component-inline-methods ;; backward-compatibility only. DO NOT USE! #:component-operation-times ;; For internal use only. ;; portable ASDF encoding and implementation-specific external-format @@ -65,11 +65,20 @@ Use asdf-encodings to support more encodings.")) (:documentation "Check whether a COMPONENT satisfies the constraint of being at least as recent as the specified VERSION, which must be a string of dot-separated natural numbers, or NIL.")) (defgeneric component-version (component) - (:documentation "Return the version of a COMPONENT, which must be a string of dot-separated -natural numbers, or NIL.")) + (:documentation "Return the version of a COMPONENT, which must be a string +of dot-separated natural numbers, or NIL. If the component is a pre-release, +returns the version which it is a pre-release for. + +This function is kept for backward compatibility. New code should prefer to use +COMPONENT-VERSION*.")) + (defgeneric component-version* (component) + (:documentation "Return the version of a COMPONENT. The returned version is +version object suitable for use with VERSION< and friends (or NIL).")) (defgeneric (setf component-version) (new-version component) - (:documentation "Updates the version of a COMPONENT, which must be a string of dot-separated -natural numbers, or NIL.")) + (:documentation "Updates the version of a COMPONENT.")) + (defgeneric component-version-class (component) + (:documentation "Return the class COMPONENT uses to represent its +versions.")) (defgeneric component-parent (component) (:documentation "The parent of a child COMPONENT, or NIL for top-level components (a.k.a. systems)")) @@ -92,10 +101,8 @@ or NIL for top-level components (a.k.a. systems)")) (defclass component () ((name :accessor component-name :initarg :name :type string :documentation "Component name: designator for a string composed of portable pathname characters") - ;; We might want to constrain version with - ;; :type (and string (satisfies parse-version)) - ;; but we cannot until we fix all systems that don't use it correctly! - (version :accessor component-version :initarg :version :initform nil) + (version :initarg :version :initform nil) + (version-class :reader component-version-class :initarg :version-class :initform 'uiop:default-version) (description :accessor component-description :initarg :description :initform nil) (long-description :accessor component-long-description :initarg :long-description :initform nil) (sideway-dependencies :accessor component-sideway-dependencies :initform nil) @@ -162,7 +169,38 @@ The return value is a list of component NAMES; a list of strings." (defmethod component-system ((component component)) (if-let (system (component-parent component)) (component-system system) - component))) + component)) + + (defmethod component-version ((component component)) + ;; The VERSION slot of COMPONENT may be NIL, unbound, or a + ;; VERSION-OBJECT. It should not be a string (due to the + ;; *post-upgrade-cleanup-hook*). + (if-let (raw-version (and (slot-boundp component 'version) + (slot-value component 'version))) + (progn + ;; This ensures that all pre-release info is stripped. + (when (version-pre-release-p raw-version) + (setf raw-version (version-pre-release-for raw-version))) + ;; Ensure that it parses as a series of dot-separated integers. + (ignore-errors (version-string (make-version (version-string raw-version))))))) + (defmethod component-version* ((component component)) + (if-let (raw-version (and (slot-boundp component 'version) + (slot-value component 'version))) + raw-version)) + + (defmethod (setf component-version) ((value string) (component component)) + "Set the VERSION slot of component." + (setf (slot-value component 'version) + (make-version value :class (component-version-class component))) + ;; It's tempting to return (VALUES VALUE VALUE-AS-VERSION) here, but that + ;; does not seem to be allowed under 5.1.2.9 of the spec. + value) + (defmethod (setf component-version) ((value null) (component component)) + (setf (slot-value component 'version) nil) + value) + (defmethod (setf component-version) ((value version-object) (component component)) + (setf (slot-value component 'version) value) + value)) ;;;; Component hierarchy within a system @@ -313,14 +351,18 @@ this compilation, or check its results, etc.")) (defmethod version-satisfies :around ((c t) (version null)) t) (defmethod version-satisfies ((c component) version) - (unless (and version (slot-boundp c 'version) (component-version c)) - (when version - (warn "Requested version ~S but ~S has no version" version c)) - (return-from version-satisfies nil)) - (version-satisfies (component-version c) version)) + (let ((component-version (component-version* c))) + (unless (and version component-version) + (when version + (warn "Requested version ~S but ~S has no version" version c)) + (return-from version-satisfies nil)) + (version-satisfies component-version version))) + + (defmethod version-satisfies ((cver version-object) version-constraint) + (version-constraint-satisfied-p cver version-constraint)) (defmethod version-satisfies ((cver string) version) - (version<= version cver))) + (version-satisfies (make-version cver) version))) ;;; all sub-components (of a given type) diff --git a/doc/asdf.texinfo b/doc/asdf.texinfo index b9dc5496ba48d425c068361aab81da0bbb3b9c66..91acd6179fa5d68b6c52fc53129838193652da41 100644 --- a/doc/asdf.texinfo +++ b/doc/asdf.texinfo @@ -65,7 +65,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @titlepage @title ASDF: Another System Definition Facility -@subtitle Manual for Version 3.3.5.7 +@subtitle Manual for Version 3.4.0-alpha.1 @c The following two commands start the copyright page. @page @vskip 0pt plus 1filll @@ -82,7 +82,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @node Top, Introduction, (dir), (dir) @top ASDF: Another System Definition Facility @ifnottex -Manual for Version 3.3.5.7 +Manual for Version 3.4.0-alpha.1 @end ifnottex @@ -96,6 +96,7 @@ Manual for Version 3.3.5.7 * Using ASDF:: * Defining systems with defsystem:: * The object model of ASDF:: +* Version specifiers and ASDF:: * Controlling where ASDF searches for systems:: * Controlling where ASDF saves compiled files:: * Error handling:: @@ -146,9 +147,15 @@ The Object model of ASDF * Operations:: * Components:: * Dependencies:: -* Functions:: * Parsing system definitions:: +Version specifiers and ASDF + +* Introduction to Versions:: +* The Many Hats of a Programmer:: +* Version Specifier Parsing:: +* Version Constraints:: + Operations * Predefined operations of ASDF:: @@ -1194,9 +1201,11 @@ especially if you intend your software to be eventually included in Quicklisp. @c FIXME: this is way too detailed for the first example! @c move it! @item -Make sure you know how the @code{:version} numbers will be parsed! -Only period-separated non-negative integers are accepted at present. -@xref{Version specifiers}. +Make sure you know how the @code{:version} numbers will be parsed! By +default, ASDF accepts a string containing period-separated +non-negative integers and pre-release information separated by a +hyphen. However, this may be changed through the use of a different +system or version class. @xref{Version specifiers}. @item This file contains a single form, the @code{defsystem} declaration. @@ -1230,6 +1239,7 @@ slightly convoluted example: (defsystem "foo" :version (:read-file-form "variables" :at (3 2)) + :compatible-versions (:>= "1") :components ((:file "package") (:file "variables" :depends-on ("package")) @@ -1408,6 +1418,13 @@ We recommend you either prefix use of UIOP functions with the package prefix @co or make sure your system @code{:depends-on ((:version "asdf" "3.1.2"))} or has a @code{#-asdf3.1 (error "MY-SYSTEM requires ASDF 3.1.2")}. +@item +Starting with ASDF 3.4, you can specify how backwards compatible you +expect your system to be using the @code{:compatible-versions} +argument. In this example, this version of @code{foo} should satisfy any +system that expects @code{foo} to be between versions 1 and the current +version. + @item Finally, we elided most metadata, but showed how you can have ASDF automatically extract the system's version from a source file. In this case, the 3rd subform of the 4th form @@ -1478,6 +1495,9 @@ Presumably, the 4th form looks like @code{(defparameter *foo-version* "5.6.7")}. | :source-control @refrule{source-control} | :version @refrule{version-specifier} | :entry-point @var{object} # @pxref{Entry point} + # These are only available since ASDF 3.4 + | :compatible-versions @refrule{version-constraint} + | :version-class @var{class-name} # @pxref{Version class} @defrule{source-control} ( @var{keyword} @var{string} ) @@ -1514,7 +1534,7 @@ Presumably, the 4th form looks like @code{(defparameter *foo-version* "5.6.7")}. # in :in-order-to @defrule{dependency-def} @refrule{simple-component-name} | ( :feature @refrule{feature-expression} @refrule{dependency-def} ) # @pxref{Feature dependencies} - | ( :version @refrule{simple-component-name} @refrule{version-specifier} ) + | ( :version @refrule{simple-component-name} @refrule{version-constraint} ) | ( :require @var{module-name} ) # "dependency" is used in :in-order-to, as opposed to "dependency-def" @@ -1533,6 +1553,16 @@ Presumably, the 4th form looks like @code{(defparameter *foo-version* "5.6.7")}. @defrule{line-specifier} :at @var{integer} # base zero @defrule{form-specifier} :at [ @var{integer} | ( @var{integer}+ ) ] +@defrule{version-constraint} @refrule{compound-version-constraint} + | @refrule{compatibility-version-constraint} + | @refrule{simple-version-constraint} +@defrule{compound-version-constraint} ( :and @refrule{version-constraint} ) + | ( :or @refrule{version-constraint} ) +@defrule{compatibility-version-constraint} ( :compatible @var{string} ) | @var{string} +@defrule{simple-version-constraint} ( :> @var{string} ) | ( :>= @var{string} ) + | ( :< @var{string} ) | ( :<= @var{string} ) + | ( := @var{string} ) | ( :/= @var{string} ) + @defrule{method-form} ( @refrule{operation-name} @refrule{qual} @var{lambda-list} @Arest{} @var{body} ) @defrule{qual} @refrule{method-qualifier}? @defrule{method-qualifier} :before | :after | :around @@ -1767,19 +1797,59 @@ fulfills whatever constraints are required from that component type on the other hand, you can circumvent the file type that would otherwise be forced upon you if you were specifying a string. +@subsection Version class +@cindex version class +@cindex :version-class +@anchor{Version class} + +A version class name will be looked up the same way as a component +type (see above), except that only subclasses of +@code{uiop:version-object} are allowed. Typically, one will not need +to specify a version class name, unless they want to customize their +version specifier. + @subsection Version specifiers @cindex version specifiers @cindex :version @anchor{Version specifiers} -Version specifiers are strings to be parsed as period-separated lists of integers. -I.e., in the example, @code{"0.2.1"} is to be interpreted, -roughly speaking, as @code{(0 2 1)}. -In particular, version @code{"0.2.1"} is interpreted the same as @code{"0.0002.1"}, -though the latter is not canonical and may lead to a warning being issued. -Also, @code{"1.3"} and @code{"1.4"} are both strictly @code{uiop:version<} to @code{"1.30"}, -quite unlike what would have happened -had the version strings been interpreted as decimal fractions. +A version specifier is a string that can be parsed to make an object +that is a subclass of @code{uiop:version-object}. The default version +class, @code{uiop:default-version} accepts strings that are parsed in +two segments. + +The first segment denotes the ``core'' version number. It is required +and must consist of any number of non-negative integers, separated by +@code{#\.} characters. + +The second segment is optional and contains pre-release +information. If present, the pre-release segment must be separated +from the first by a @code{#\-} character. This segment consists of a +``category'' (@code{alpha}, @code{beta}, or @code{rc}), optionally +followed by one or two non-negative integers, separated from the +catagory and easch other by a @code{#\.} character. + +Examples of valid specifiers include: + +@enumerate + +@item +@code{1.2.0} - The released 1.2.0. + +@item +@code{1.3.0-alpha.4} - The fourth alpha release of 1.3.0. + +@item +@code{1.3.0-rc} - A release candidate of 1.3.0. + +@item +@code{1.3.0} - The released 1.3.0. + +@end enumerate + +@code{uiop:version<} is used to order versions. The above list of +versions is ordered as @code{uiop:version<} would order them. For more +details on the default ordering rules, @pxref{Version specifiers and ASDF}. Instead of a string representing the version, the @code{:version} argument can be an expression that is resolved to @@ -1799,13 +1869,86 @@ subforms can also be specified, with e.g. @code{(1 2 2)} specifying ``the third subform (index 2) of the third subform (index 2) of the second form (index 1)'' in the file (mind the off-by-one error in the English language). -System definers are encouraged to use version identifiers of the form +System implementors are encouraged to use version identifiers of the form @var{x}.@var{y}.@var{z} for major version, minor version and patch level, where significant API incompatibilities are signaled by an increased major number. @xref{Common attributes of components}. +@subsection Version constraints +@cindex version constraints +@cindex :compatible-versions +@anchor{Version constraints} + +ASDF allows systems to specify constraints on the allowed version of +their dependencies. Additionally, systems can specify which of their +own previous versions they are API compatible with. Both of these are +specified using version constraints. Be aware that ASDF simply +evaluates these constraints and signals a continuable error if they +are not met. It is up to you or your package management system to +ensure that compatible versions of systems are installed and +discoverable by ASDF. + +We @emph{strongly} recommend reading the chapter on version specifiers +and ASDF before using version constraints in your +systems. @xref{Version specifiers and ASDF}. + +A simple version constraint consists of an operator followed by a +version specifier (string). The allowed operators are @code{:>=}, +@code{:>}, @code{:<=}, @code{:<}, @code{:=}, and @code{:/=}. + +ASDF also includes a ``compatibility'' constraint. This constraint is +specified using either the @code{:compatible} operator or a raw +version specifier string. A compatibility specifies a minimum version +(similar to the @code{:>=} operator, but additionally respects the +depended on system's @code{:compatible-versions} argument. + +Last, the previous constraints can be composed together as a compound +version constraint, using the @code{:and} or @code{:or} operators. + +As an example, consider the three following examples of system +@code{foo}. + +@lisp +(defsystem "foo" + :version "1.3.0" + ...) + +(defsystem "foo" + :version "2.0.0" + ...) + +(defsystem "foo" + :version "3.0.0" + :compatible-versions (:>= "3") + ...) +@end lisp + + +The first two satisfy the dependency constraint +@code{(:version "foo" (:and "1.2" (:/= "1.4.0")))}, but the third does +not. Note that the third does not satisfy the constraint because +@code{foo}'s author believes the API has changed enough in version +@code{3.0.0} that any system written for a previous version will not +work as-is. + +However, Common Lisp allows for a great deal of introspection at run +and compile time. As such, it is very possible that your system can +handle any version of @code{foo}, even if @code{foo}'s author believes +them to be incompatible under normal circumstances. If that is the +case, simply use the @code{:or} operator. All three definitions of +@code{foo} satisfy this constraint: +@code{(:version "foo" (:and (:or "1.2" "3.0") (:/= "1.4.0")))}. + +While the version constraint language allows you to disallow versions +of a dependency that are not yet released, it is extremely bad form to +do so. When specifying a constraint on a dependency, you should avoid +the @code{:<} and @code{:<=} operators. Instead you should use +@code{:/=} to knock out known bad versions and otherwise leave it up +to the author of your dependency to declare known incompatibilities. + + @subsection Require @cindex :require dependencies @@ -2196,7 +2339,7 @@ where the @file{.asd} file resides. @codequoteundirected off -@node The object model of ASDF, Controlling where ASDF searches for systems, Defining systems with defsystem, Top +@node The object model of ASDF, Version specifiers and ASDF, Defining systems with defsystem, Top @comment node-name, next, previous, up @chapter The Object model of ASDF @tindex component @@ -2261,7 +2404,6 @@ customize the behaviour of existing @emph{functions}. * Operations:: * Components:: * Dependencies:: -* Functions:: * Parsing system definitions:: @end menu @@ -3319,7 +3461,7 @@ The new component type is used in a @code{defsystem} form in this way: ) @end lisp -@node Dependencies, Functions, Components, The object model of ASDF +@node Dependencies, Parsing system definitions, Components, The object model of ASDF @section Dependencies @c FIXME: Moved this material here, but it isn't very comfortable @c here.... Also needs to be revised to be coherent. @@ -3354,43 +3496,7 @@ remain at the system level, and are not propagated along the hierarchy, but instead do something global on the system. -@node Functions, Parsing system definitions, Dependencies, The object model of ASDF -@comment node-name, next, previous, up -@section Functions - -@c FIXME: this does not belong here.... -@defun version-satisfies @var{version} @var{version-spec} -Does @var{version} satisfy the @var{version-spec}. A generic function. -ASDF provides built-in methods for @var{version} being a @code{component} or @code{string}. -@var{version-spec} should be a string. -If it's a component, its version is extracted as a string before further processing. - -A version string satisfies the version-spec if after parsing, -the former is no older than the latter. -Therefore @code{"1.9.1"}, @code{"1.9.2"} and @code{"1.10"} all satisfy @code{"1.9.1"}, -but @code{"1.8.4"} or @code{"1.9"} do not. -For more information about how @code{version-satisfies} parses and interprets -version strings and specifications, -@pxref{Version specifiers} and -@ref{Common attributes of components}. - -Note that in versions of ASDF prior to 3.0.1, -including the entire ASDF 1 and ASDF 2 series, -@code{version-satisfies} would also require that the version and the version-spec -have the same major version number (the first integer in the list); -if the major version differed, the version would be considered as not matching the spec. -But that feature was not documented, therefore presumably not relied upon, -whereas it was a nuisance to several users. -Starting with ASDF 3.0.1, -@code{version-satisfies} does not treat the major version number specially, -and returns T simply if the first argument designates a version that isn't older -than the one specified as a second argument. -If needs be, the @code{(:version ...)} syntax for specifying dependencies -could be in the future extended to specify an exclusive upper bound for compatible versions -as well as an inclusive lower bound. -@end defun - -@node Parsing system definitions, , Functions, The object model of ASDF +@node Parsing system definitions, , Dependencies, The object model of ASDF @section Parsing system definitions @cindex Parsing system definitions @cindex Extending ASDF's defsystem parser @@ -3445,8 +3551,401 @@ for) new component types. @end deffn +@node Version specifiers and ASDF, Controlling where ASDF searches for systems, The object model of ASDF, Top +@comment node-name, next, previous, up +@chapter Version specifiers and ASDF + +@menu +* Introduction to Versions:: +* The Many Hats of a Programmer:: +* Version Specifier Parsing:: +* Version Constraints:: +@end menu + +@node Introduction to Versions, The Many Hats of a Programmer, Version specifiers and ASDF, Version specifiers and ASDF +@section Introduction to Versions + +Version specifiers are an imprecise, yet concise and widely used way +to summarize what the features and API are of a library. Like any good +build system, ASDF allows systems to provide their version, depend on +specific versions of their dependencies, and advertise when API +breakage occurs. However, unlike many extant build systems, ASDF +allows its version grammar to be extended and chooses slightly +different defaults that are more in line with how the Common Lisp +community operates (especially when paired with the stability of the +language!). + +In this chapter, we describe the different considerations that a +programmer must keep in mind when they are versioning their systems, +using versioned systems, or assembling a complete development (or +production, etc.) environment. We then continue by describing ASDF's +API for specifying versions and the API for specifying version +constraints. + +@node The Many Hats of a Programmer, Version Specifier Parsing, Introduction to Versions, Version specifiers and ASDF +@section The Many Hats of a Programmer + +@menu +* The Implementor Hat:: +* The Consumer Hat:: +* The Integrator Hat:: +@end menu + +There are (at least) three hats that a programmer must wear when +thinking about versions and version constraints: implementor, +consumer, and integrator. In each role, the programmer has different +considerations they must keep in mind. + +@node The Implementor Hat, The Consumer Hat, The Many Hats of a Programmer, The Many Hats of a Programmer +@subsection The Implementor Hat + +When a programmer is writing a system for others to use, they must +wear their implementor hat. The goal of an implementor is to concisely +communicate a feature (and bug!) set to those that depend on their +system. Additionally, an implementor must communicate how backward +compatible each version is. + +In order to communicate a feature set, we encourage all implementors +to provide version numbers for each of their systems. Additionally, +implementors should maintain a change log that a consumer can use to +see what features were added, changed, or removed in each release. + +In order to communicate how backward compatible each release is, we +encourage implementors to both communicate a general policy to their +users and use the @code{:compatible-versions} argument to +@code{defsystem} to declaratively state when the last ``breaking'' +change was. + +A good implementor always has backward compatibility on their mind and +seeks to maintain it. As such, we recommend that implementors adopt +one of the following policies, with the policies listed from most to +least preferable. + +@enumerate + +@item +Once your system has been released for others to use, never break +backward compatibility. One way to accomplish this in Common Lisp is +to version your package names. For example, release v1.0 of system +@code{foo} could provide the @code{foo-v1} package. If @code{foo}'s +author then realizes that exported functions are not particularly easy +to use, they can release @code{foo} v2 that provides both the +@code{foo-v1} @emph{and} @code{foo-v2} packages. Where @code{foo-v1} +exports the same API as it did in @code{foo} v1.0, but all of its +functions have been rewritten to call functionality exported by the +@code{foo-v2} package. + +@item +If an update would break an API, create an entirely new system (and +potentially an entirely new project) for the new version. The old +system can remain frozen and usable while other systems move over to +your new system name. + +@item +Introduce the new API and have it coexist with the old API for a +period of time. Document the old API as deprecated. Work with all of +your consumers to move over to your new API. After a reasonable amount +of time, remove the old API, increment the version number, and update +your system's @code{:compatible-versions} constraint. + +@end enumerate + +@node The Consumer Hat, The Integrator Hat, The Implementor Hat, The Many Hats of a Programmer +@subsection The Consumer Hat + +When a programmer is declaring a dependency on another system, they +must wear their consumer hat. The goal of a consumer is to communicate +what feature set and API they need from their dependency. + +Typically, this is communicated by declaring a minimum version of the +dependency that is required. However, a consumer may also specify +upper bounds or state that specific versions of a dependency are not +to be allowed. + +A good consumer must try and ensure that they do not artificially +constrain which versions of their dependencies are allowed. Therefore, +we recommend that consumers only use the ``compatibility'' +constraint. This both declares a minimum version and tells ASDF to +respect the dependency's @code{:compatible-versions} argument. + +If a consumer's dependency does introduce a breaking change but the +consumer uses reader macros or other introspection capabilities to +remain compatible with releases before and after the breaking change, +the consumer may use the @code{:or} operator to combine compatibility +constraints. For example, the constraint +@code{(:version "foo" (:or "1" "2"))} states that the consumer can use +version 1 or 2 of @code{foo}, even if v2 introduced a breaking change. + +We @emph{strongly} recommend that consumers do not use the @code{:<}, +@code{:<=}, or @code{:/=} operators when specifying a version +constraint. They should only be used if either you have observed that +the disallowed versions break the consumer @emph{or} you do not trust +your dependency's implementor to use @code{:compatible-versions} +correctly (in which case, we recommend that you do the community a +favor and engage with the implementor to remedy the issue or fork the +project if necessary). + +@node The Integrator Hat, The Consumer Hat, Version Specifier Parsing, The Many Hats of a Programmer +@subsection The Integrator Hat + +The last hat a programmer typically wears is that of an integrator. An +integrator is responsible for assembling and maintaining a complete +environment. This includes not only the direct dependencies of a +system (or systems), but their transitive dependencies as well. + +The most frequent environment that a programmer assembles is the +development and testing environment for one of their systems. However, +this also includes things such as production and quality assurance +environments typically used by large scale application development +processes. + +Typically, an integrator is tasked with creating an environment that +both works (e.g., there are no conflicts between different systems in +the environment) and is reproducible. + +Most of our guidance for implementors and consumers is designed to +make the lives of integrators easier. If implementors make few +breaking changes and consumers state only the bare minimum needed from +their dependencies, then integrators have more flexibility to assemble +a working environment. + +The only tool that ASDF provides which is aimed specifically at +integrators is that all errors resulting from version constraint +violations are continuable. So if you are forced to work with a set of +systems that does not adhere to ASDF's recommendations, you can tell +ASDF to ignore the constraint violation and continue operating +anyways. + +We recommend that integrators look into the features their chosen +Common Lisp dependency manager provides for assembling a working, +coherent, reproducible environment. + +@node Version Specifier Parsing, Version Constraints, Introduction to Versions, Version specifiers and ASDF +@section Version Specifier Parsing + +ASDF's default version specifier parsing methods are designed to parse +a majority of version strings seen in the wild. However, it is fully +extensible so that system implementors may use whichever scheme they +want. + +All version specifiers must parse to an object that is a subclass of +@code{uiop:version-object}. The default version class is +@code{uiop:default-version}. A system implementor can choose a +non-default class using the @code{:version-class} argument to +@code{defsystem}. + +The value of @code{:version-class} must be a symbol or string naming +the class. If it is a keyword, ASDF attempts to find the class in the +@code{asdf} package. If it is a symbol, it must be the name of the +class. If it is a string, it will be @code{read} after the +@code{:defsystem-depends-on} list have been loaded and must result in +a symbol naming the class to use. + +A custom version class must implement the following interface: + +@deffn {Initarg} :version-string + +The class must accept the @code{:version-string} initarg. This will +be the string the user specified in the @code{defsystem}. + +If the @code{:version-string} is invalid, the class's initialization +methods must signal a @code{uiop:version-string-invalid-error} +condition. ASDF will provide restarts for the user to either enter +another string or treat the version as being unspecified. + +@end deffn + +@deffn {Generic Function} uiop:version-pre-release-p @var{version} + +Returns non-NIL if @code{version} is a pre-release. + +@end deffn + +@deffn {Generic Function} uiop:version-pre-release-for @var{version} + +If @code{uiop:version-pre-release-p} is non-NIL, this must return an +instance of @code{uiop:version-object} that represents the version for +which @code{version} is a pre-release. + +If the returned version is @code{final-version}, +@code{(uiop:version-pre-release-p final-version)} must be NIL and +@code{(uiop:version< version final-version)} must be non-NIL. + +@end deffn + +@deffn {Generic Function} uiop:version< @var{version-1} @var{version-2} + +Returns non-NIL if @code{version-1} is ordered before +@code{version-2}. + +UIOP provides default methods that parse strings as +@code{uiop:default-version} using @code{uiop:make-version} and then +call @code{uiop:version<} again. + +@end deffn + +@deffn {Generic Function} uiop:version-string @var{version} + +Returns a string that represents @code{version}. To be used primarily +for display to a user. The returned string must also be suitable for +the @code{:version-string} initarg. + +@end deffn + +@deffn {Generic Function} asdf:component-version @var{component} +Returns the version of @code{component}. + +This function is kept for backward compatibility. New code should prefer +@code{component-version*}. + +The version is a string of dot-separated natural numbers, or NIL. If the +component is a pre-release, returns the version which it is a +pre-release for. + +@end deffn + +@deffn {Generic Function} asdf:component-version* @var{component} +Returns the version of @code{component}. + +The returned version is version object suitable for use with +@code{uiop:version<} and friends (or NIL). + +@end deffn + +ASDF provides two version classes, @code{semantic-version} and +@code{default-version}. + +@code{semantic-version} implements the grammar and ordering rules from +@url{https://semver.org/spec/v2.0.0.html,Semver v2.0.0}, with the +modification that the core version segment is not limited to exactly +three integers. + +@code{default-version} implements the same grammar and ordering rules +as @code{semantic-version}, but limits the pre-release segment to be +contain only @code{alpha}, @code{beta}, or @code{rc}, optionally +followed by a @code{#\.} character and a non-negative +integer. Currently, @code{default-version} is a subclass of +@code{semantic-version} but this may change in the future. + +@node Version Constraints, Controlling where ASDF searches for systems, Version Specifier Parsing, Version specifiers and ASDF +@section Version Constraints + +Version constraints are used for two purposes. The first is to declare +dependencies as part of the @code{:depends-on} or +@code{:defsystem-depends-on} argument. The second is to declare how +backwards compatible a system is using the @code{:compatible-versions} +argument. + +If a version constraint is not satisfied, a +@code{missing-dependency-of-version} error is signaled. This error is +continuable, so you may choose to ignore it and use the dependency +anyways. + +A simple version constraint is a list of two elements. The first is an +operator and the second is a version designator. + +A compound version constraint is a list of two or more elements. The +first is an operator and the remaining elements are version +constraints. + +Last, there is a compatibility version constraint. This is allowed +only for declaring dependencies and is not legal for use in the +@code{:compatible-versions} argument to @code{defsystem}. A +compatibility constraint is either a list of two elements or a version +designator. If a list, the first element must be @code{:compatible} +and the second must be a version designator. + +The allowed simple constraint operators are @code{:>=}, @code{:>}, +@code{:<=}, @code{:<}, @code{:=}, and @code{:/=}. @code{uiop:version<} +is used to compare versions under the hood. + +For example, assume there is a system @code{foo}, with version +@code{V1}. If system @code{bar} has the dependency +@code{(:version "foo" (:>= VS2)}, where @code{VS2} is some +string. First, @code{VS2} will be parsed as a version object @code{V2} +using @code{foo}'s version class. If parsing fails, the constraint is +not satisfied. Then, if @code{(not (uiop:version< V1 V2))} is true, +the constraint is satisfied. If it is false, the constraint is not +satisfied. + +Compound constraints require that either all sub-constraints are +satisfied (@code{:and}), or at least one of them is (@code{:or}). + +A compatibility constraint is satisfied if the dependency's version is +@code{:>=} the requested version @emph{and} the requested version +satisfies the dependency's @code{:compatible-versions} constraint (if +it is non-NIL). For example, consider @code{foo} is at version +@code{3.0.0} with no @code{:compatible-versions} constraint. That +version of foo will satisfy the constraint @code{(:version "foo" "1.4")}. + +However, if @code{foo} has the @code{:compatible-versions} constraint +@code{(:>= "2")}, it will no longer satisfy the constraint +@code{(:version "foo" "1.4")}. That is because the requested version +(@code{1.4}) is not at least @code{2}. + +Additionally, note that for any simple or compatibility constraint, if +the requested version does not contain pre-release information, then +pre-release information is also stripped from the dependency's version +when comparing. This means that @code{foo} version @code{1.2-alpha.1}, +@emph{does} satisfy the constraint +@code{(:version "foo" "1.2")}. However, it will @emph{not} satisfy +@code{(:version "foo" "1.2-rc.1")}. + +This behavior may be surprising to users of version strings from other +languages where the default version constraints (such as Node's +@code{^1.4.1} or Python's @code{~=1.4}) enforce that major versions +must match and pre-release versions do not satisfy the constraints. +However, we believe that our choice to eschew these traditions from +other language's build systems better fits how the CL community +operates. + +Common Lisp developers tend to take backward compatibility very +seriously. Additionally, the Common Lisp ecosystem provides plenty of +tools to help system implementors maintain backward compatibility +without excessive verbosity or pain. For instance, a system may +provide multiple, versioned packages and recommend that consumers use +package local nicknames to access a particular versioned package. + +Additionally, the default version constraints in other language +ecosystems often require that a system consumer mark a wide swath of +dependency versions as incompatible before those version have even +been released! As no one can accurately predict the future, and given +Common Lisp developers' predilection for backwards compatibility, it +is very likely that a system consumer would declare future systems as +incompatible, even when they're not. + +In light of this, ASDF's approach makes no assumptions about the +meaning of the ``major'' version number. Additionally, as a dependency +implementor is the only one that truly knows if a backwards +compatibility breaking change is going to be released, ASDF's approach +lets them communicate that to their consumers. + +We allow pre-release versions to satisfy the constraints largely out +of separation of concerns. ASDF is not responsible for fetching +dependencies and we feel the decision on whether or not to use +pre-release versions is best made there. If you choose to make +pre-releases visible to ASDF, presumably you know that things could +break horribly as the code could change drastically before it is fully +released. + +The primary entry point to the version constraint checking logic is +@code{uiop:version-constraint-satisfied-p}. + +@defun version-constraint-satisfied-p @var{version} @var{version-constraint} &key @var{strip-pre-release-p} @var{compatible-versions} + +Returns non-NIL if @var{version} satisfies the @var{version-constraint}. + +If @var{strip-pre-release-p} is provided and non-NIL, then any +pre-release information will be unconditionally stripped from +@var{version}. If it is provided and NIL, then no pre-release +information is ever stripped from @var{version}. + +If @var{compatible-versions} is non-NIL, it must be a version +constraint that is used during checking of compatibility constraints. + +@end defun -@node Controlling where ASDF searches for systems, Controlling where ASDF saves compiled files, The object model of ASDF, Top +@node Controlling where ASDF searches for systems, Controlling where ASDF saves compiled files, Version specifiers and ASDF, Top @comment node-name, next, previous, up @chapter Controlling where ASDF searches for systems diff --git a/doc/exported-functions b/doc/exported-functions index 2013d3d8e1a85b99c1d3184b311c1c78868bdf3d..84f10fca14efc4f87f24a4f45d87b850780ef2b4 100644 --- a/doc/exported-functions +++ b/doc/exported-functions @@ -28,6 +28,7 @@ COMPONENT-RELATIVE-PATHNAME COMPONENT-SIDEWAY-DEPENDENCIES COMPONENT-SYSTEM COMPONENT-VERSION +COMPONENT-VERSION* COMPUTE-SOURCE-REGISTRY DEFSYSTEM DISABLE-DEFERRED-WARNINGS-CHECK @@ -108,6 +109,8 @@ SYSTEM-SOURCE-DIRECTORY SYSTEM-SOURCE-FILE SYSTEM-SOURCE-REGISTRY SYSTEM-SOURCE-REGISTRY-DIRECTORY +SYSTEM-VERSION +SYSTEM-VERSION* SYSTEM-WEAKLY-DEPENDS-ON TEST-SYSTEM TRAVERSE diff --git a/find-component.lisp b/find-component.lisp index 53fa65d30f8957d1434b0ea54e188969a53e151c..ffbcd9f4b91afc718255bd26dbc88859b80267ee 100644 --- a/find-component.lisp +++ b/find-component.lisp @@ -114,10 +114,11 @@ in the context of COMPONENT")) :requires name)) (when version (unless (version-satisfies comp version) - (error 'missing-dependency-of-version - :required-by component - :version version - :requires name))) + (cerror "Continue anyways" + 'missing-dependency-of-version + :required-by component + :version version + :requires name))) comp)) (retry () :report (lambda (s) diff --git a/find-system.lisp b/find-system.lisp index 0980a2ccdd6fab1a957925ad3355ae612d9b0799..dc88c77d4e5d48cf9c8ef8d72a3626907d6f1f7f 100644 --- a/find-system.lisp +++ b/find-system.lisp @@ -149,7 +149,8 @@ Do NOT try to load a .asd file directly with CL:LOAD. Always use ASDF:LOAD-ASD." (null pathname) (let* ((asdfp (equal name "asdf")) ;; otherwise, it's uiop (version-pathname - (subpathname pathname "version" :type (if asdfp "lisp-expr" "lisp"))) + (subpathname pathname (if asdfp "full-version" "version") + :type (if asdfp "lisp-expr" "lisp"))) (version (and (probe-file* version-pathname :truename nil) (read-file-form version-pathname :at (if asdfp '(0) '(2 2 2))))) (old-version (asdf-version))) diff --git a/footer.lisp b/footer.lisp index dbb96f8ceb17797458bca9b989597f05f2a3cf38..714a795348176137a2791221786562fe9cc6e7c1 100644 --- a/footer.lisp +++ b/footer.lisp @@ -30,12 +30,12 @@ ;;;; data from the file system that we want to preserve instead of blasting ;;;; away and replacing with a blank preloaded system. (with-upgradability () - (unless (equal (system-version (registered-system "asdf")) (asdf-version)) + (unless (version= (system-version* (registered-system "asdf")) (asdf-version)) (clear-system "asdf")) ;; 3.1.2 is the last version where asdf-package-system was a separate system. - (when (version< "3.1.2" (system-version (registered-system "asdf-package-system"))) + (when (version< "3.1.2" (system-version* (registered-system "asdf-package-system"))) (clear-system "asdf-package-system")) - (unless (equal (system-version (registered-system "uiop")) *uiop-version*) + (unless (version= (system-version* (registered-system "uiop")) *uiop-version*) (clear-system "uiop"))) ;;;; Hook ASDF into the implementation's REQUIRE and other entry points. @@ -83,7 +83,7 @@ (setf excl:*warn-on-nested-reader-conditionals* uiop/common-lisp::*acl-warn-save*)) ;; Advertise the features we provide. - (dolist (f '(:asdf :asdf2 :asdf3 :asdf3.1 :asdf3.2 :asdf3.3)) (pushnew f *features*)) + (dolist (f '(:asdf :asdf2 :asdf3 :asdf3.1 :asdf3.2 :asdf3.3 :asdf3.4)) (pushnew f *features*)) ;; Provide both lowercase and uppercase, to satisfy more people, especially LispWorks users. (provide "asdf") (provide "ASDF") diff --git a/full-version.lisp-expr b/full-version.lisp-expr new file mode 100644 index 0000000000000000000000000000000000000000..8428a7d533543a8de0fb7abd093a97e2a5f50eb3 --- /dev/null +++ b/full-version.lisp-expr @@ -0,0 +1,2 @@ +"3.4.0-alpha.1" + diff --git a/gitlab-pipelines/standard-pipeline.yml b/gitlab-pipelines/standard-pipeline.yml index 3e3aadb73da77b385b0d9ae9074b64239860952a..c5f1f9db7641cacfd2db87fd0b628b9ef138f025 100644 --- a/gitlab-pipelines/standard-pipeline.yml +++ b/gitlab-pipelines/standard-pipeline.yml @@ -111,22 +111,25 @@ Upgrade test: extends: .Upgrade test template parallel: matrix: - - l: [abcl, allegro, ccl, clasp, clisp, cmucl, ecl, sbcl] + - l: [allegro, ccl, clasp, clisp, ecl, sbcl] IMAGE_TAG: latest - l: allegro IMAGE_TAG: latest variant: modern -# No more tests are known to fail. But leave commented out in case it needs to -# be resurrected. - -# Upgrade test known failure: -# extends: .Upgrade test template -# parallel: -# matrix: -# - l: [] -# IMAGE_TAG: latest -# allow_failure: true +Upgrade test known failure: + extends: .Upgrade test template + parallel: + matrix: + # CMUCL fails due to CLOS bugs. 3.4 adds a new slot to SYSTEM, which + # causes "Error in function KERNEL:CLASS-TYPEP: Class is currently + # invalid: #" + # + # ABCL fails due to compiler macros being called for generic functions + # locally declared NOTINLINE. See https://abcl.org/trac/ticket/487 + - l: [cmucl, abcl] + IMAGE_TAG: latest + allow_failure: true .REQUIRE upgrade test template: extends: .Upgrade test template @@ -141,21 +144,25 @@ REQUIRE upgrade test: ASDF_UPGRADE_TEST_TAGS: REQUIRE parallel: matrix: - - l: [abcl, allegro, ccl, clisp, cmucl, ecl, sbcl] + - l: [allegro, ccl, clisp, ecl, sbcl] IMAGE_TAG: latest - l: allegro IMAGE_TAG: latest variant: modern -# No more tests are known to fail. But leave commented out in case it needs to -# be resurrected. - REQUIRE upgrade test known failure: extends: .REQUIRE upgrade test template variables: ASDF_UPGRADE_TEST_TAGS: REQUIRE parallel: matrix: + # CMUCL fails due to CLOS bugs. 3.4 adds a new slot to SYSTEM, which + # causes "Error in function KERNEL:CLASS-TYPEP: Class is currently + # invalid: #" + - l: [cmucl] + # ABCL fails due to compiler macros being called for generic functions + # locally declared NOTINLINE. See https://abcl.org/trac/ticket/487 + - l: [abcl] - l: [clasp] IMAGE_TAG: latest allow_failure: true diff --git a/header.lisp b/header.lisp index 3105444142af3e8b61d5b1c24feecba07f218dd2..b98e8e9666da5271291d01f96fa5dcdc999b1755 100644 --- a/header.lisp +++ b/header.lisp @@ -1,5 +1,5 @@ ;;; -*- mode: Lisp; Base: 10 ; Syntax: ANSI-Common-Lisp ; Package: CL-USER ; buffer-read-only: t; -*- -;;; This is ASDF 3.3.5.7: Another System Definition Facility. +;;; This is ASDF 3.4.0-alpha.1: Another System Definition Facility. ;;; ;;; Feedback, bug reports, and patches are all welcome: ;;; please mail to . diff --git a/interface.lisp b/interface.lisp index f2d322b1c368d7fad87bf47cd79189ba353647d3..dbe598035d76861eaef1e13a33172b5dea8cf981 100644 --- a/interface.lisp +++ b/interface.lisp @@ -68,6 +68,7 @@ #:component-relative-pathname #:component-name #:component-version + #:component-version* #:component-parent #:component-system #:component-encoding @@ -79,6 +80,7 @@ #:system-license #:system-licence #:system-version + #:system-version* #:system-source-file #:system-source-directory #:system-relative-pathname diff --git a/operate.lisp b/operate.lisp index ccd66561a4cc1c403f58a57ba7b975041d5fef18..d54f0e07a6b8936ff375c449365e9068c3b9bf8d 100644 --- a/operate.lisp +++ b/operate.lisp @@ -92,7 +92,7 @@ But do NOT depend on it, for this is deprecated behavior.")) (defmethod operate :before ((operation operation) (component component) &key version) (unless (version-satisfies component version) - (error 'missing-component-of-version :requires component :version version)) + (cerror "Continue anyways" 'missing-component-of-version :requires component :version version)) (record-dependency nil operation component)) (defmethod operate ((operation operation) (component component) diff --git a/parse-defsystem.lisp b/parse-defsystem.lisp index 52ac0ecd8e4add528a1db42f7c85a98f49cca590..7a2d81d74a4f5f60b7fceb33c42452e7866b540b 100644 --- a/parse-defsystem.lisp +++ b/parse-defsystem.lisp @@ -145,44 +145,29 @@ Please only define ~S and secondary systems with a name starting with ~S (e.g. ~ (pushnew pathname (cdr cell) :test 'pathname-equal) (values))) - ;; Given a form used as :version specification, in the context of a system definition - ;; in a file at PATHNAME, for given COMPONENT with given PARENT, normalize the form - ;; to an acceptable ASDF-format version. - (fmakunbound 'normalize-version) ;; signature changed between 2.27 and 2.31 - (defun normalize-version (form &key pathname component parent) - (labels ((invalid (&optional (continuation "using NIL instead")) - (warn (compatfmt "~@") - form component parent pathname continuation)) - (invalid-parse (control &rest args) - (unless (if-let (target (find-component parent component)) (builtin-system-p target)) - (apply 'warn control args) - (invalid)))) - (if-let (v (typecase form - ((or string null) form) - (real - (invalid "Substituting a string") - (format nil "~D" form)) ;; 1.0 becomes "1.0" - (cons - (case (first form) - ((:read-file-form) - (destructuring-bind (subpath &key (at 0)) (rest form) - (let ((path (subpathname pathname subpath))) - (record-additional-system-input-file path component parent) - (safe-read-file-form path - :at at :package :asdf-user)))) - ((:read-file-line) - (destructuring-bind (subpath &key (at 0)) (rest form) - (let ((path (subpathname pathname subpath))) - (record-additional-system-input-file path component parent) - (safe-read-file-line (subpathname pathname subpath) - :at at)))) - (otherwise - (invalid)))) - (t - (invalid)))) - (if-let (pv (parse-version v #'invalid-parse)) - (unparse-version pv) - (invalid)))))) + (defun parse-version-pointer (form &key pathname) + "Given a form used as a :version specification, in the context of a system +definition in a file at PATHNAME, attempt to read the form as part of ASDF's +mini-DSL for reading version information from another file. If FORM is not a +member of the DSL, the FORM is returned as is. A second value is returned +specifying the pathname that was used to derive the return value, if any." + (if (listp form) + (case (first form) + ((:read-file-form) + (destructuring-bind (subpath &key (at 0)) (rest form) + (let ((path (subpathname pathname subpath))) + (values (safe-read-file-form path + :at at :package :asdf-user) + path)))) + ((:read-file-line) + (destructuring-bind (subpath &key (at 0)) (rest form) + (let ((path (subpathname pathname subpath))) + (values (safe-read-file-line (subpathname pathname subpath) + :at at) + path)))) + (otherwise + form)) + form))) ;;; "inline methods" @@ -306,7 +291,7 @@ children.")) ;; remove-plist-keys form. important to keep them in sync components pathname perform explain output-files operation-done-p weakly-depends-on depends-on serial - do-first if-component-dep-fails version + do-first if-component-dep-fails version version-class ;; list ends &allow-other-keys) options (declare (ignore perform explain output-files operation-done-p builtin-system-p)) @@ -318,14 +303,19 @@ children.")) (class-for-type parent type)))) (error 'duplicate-names :name name)) (when do-first (error "DO-FIRST is not supported anymore as of ASDF 3")) + (unless (null version-class) + (setf version-class (coerce-class version-class :package :asdf/interface + :super 'version-object))) (let* ((name (coerce-name name)) (args `(:name ,name :pathname ,pathname + ,@(unless (null version-class) + `(:version-class ,version-class)) ,@(when parent `(:parent ,parent)) ,@(remove-plist-keys '(:components :pathname :if-component-dep-fails :version :perform :explain :output-files :operation-done-p - :weakly-depends-on :depends-on :serial) + :weakly-depends-on :depends-on :serial :version-class) rest))) (component (find-component parent name)) (class (class-for-type parent type))) @@ -345,10 +335,18 @@ children.")) (let ((sysfile (system-source-file (component-system component)))) ;; requires the previous (when (and (typep component 'system) (not bspp)) (setf (builtin-system-p component) (lisp-implementation-pathname-p sysfile))) - (setf version (normalize-version version :component name :parent parent :pathname sysfile))) + (multiple-value-bind (version-form additional-input-file) + (parse-version-pointer version :pathname sysfile) + (setf version version-form) + (when additional-input-file + (record-additional-system-input-file additional-input-file component parent)))) ;; Don't use the accessor: kluge to avoid upgrade issue on CCL 1.8. ;; A better fix is required. - (setf (slot-value component 'version) version) + (setf (slot-value component 'version) + (if (null version) + nil + (apply #'make-version version + (unless (null version-class) (list :version-class version-class))))) (when (typep component 'parent-component) (setf (component-children component) (compute-component-children component components serial)) (compute-children-by-name component)) diff --git a/system-registry.lisp b/system-registry.lisp index 71e286fd47def0d99b87c1a4ccbdc28fa9be475f..bfc95d024ece83683ae23590e59c6950c243b20a 100644 --- a/system-registry.lisp +++ b/system-registry.lisp @@ -100,7 +100,7 @@ If VERSION is the default T, and a system was already loaded, then its version w (let ((name (coerce-name system-name))) (when (eql version t) (if-let (system (registered-system name)) - (setf (getf keys :version) (component-version system)))) + (setf (getf keys :version) (version-string (component-version* system))))) (setf (gethash name *preloaded-systems*) keys) (ensure-preloaded-system-registered system-name))) diff --git a/system.lisp b/system.lisp index b05888405f3476a7b9b408b1b90696ab67673b7f..0659a031c921dd7c727be17b640fb82eee77d137 100644 --- a/system.lisp +++ b/system.lisp @@ -9,7 +9,7 @@ #:system-source-file #:system-source-directory #:system-relative-pathname #:system-description #:system-long-description #:system-author #:system-maintainer #:system-licence #:system-license - #:system-version + #:system-version #:system-version* #:system-compatible-versions #:definition-dependency-list #:definition-dependency-set #:system-defsystem-depends-on #:system-depends-on #:system-weakly-depends-on #:component-build-pathname #:build-pathname @@ -104,7 +104,10 @@ a SYSTEM is redefined and its class is modified.")) :initform nil) ;; these two are specially set in parse-component-form, so have no :INITARGs. (depends-on :reader system-depends-on :initform nil) - (weakly-depends-on :reader system-weakly-depends-on :initform nil)) + (weakly-depends-on :reader system-weakly-depends-on :initform nil) + ;; This slot added in ASDF 3.4 + (compatible-versions :reader system-compatible-versions :initarg :compatible-versions + :initform nil)) (:documentation "SYSTEM is the base class for top-level components that users may request ASDF to build.")) @@ -172,10 +175,10 @@ NB: The onus is unhappily on the user to avoid clashes." ;;; System virtual slot readers, recursing to the primary system if needed. (with-upgradability () - (defvar *system-virtual-slots* '(long-name description long-description - author maintainer mailto - homepage source-control - licence version bug-tracker) + (defparameter *system-virtual-slots* '(long-name description long-description + author maintainer mailto + homepage source-control + licence bug-tracker) "The list of system virtual slot names.") (defun system-virtual-slot-value (system slot-name) "Return SYSTEM's virtual SLOT-NAME value. @@ -197,7 +200,27 @@ the primary one." *system-virtual-slots*))) (define-system-virtual-slot-readers) (defun system-license (system) - (system-virtual-slot-value system 'licence))) + (system-virtual-slot-value system 'licence)) + ;; ASDF4: Move this back into *system-virtual-slots*. We can't return the + ;; slot directly as we want to mimic the result of COMPONENT-VERSION. + (defun system-version (system) + "Return the version of SYSTEM, which must be a string of dot-separated +natural numbers, or NIL. If the system is a pre-release, returns the version +which it is a pre-release for. + +This function is kept for backward compatibility. New code should prefer to use +SYSTEM-VERSION*." + (if-let (direct-version (component-version system)) + direct-version + (unless (primary-system-p system) + (component-version (find-system (primary-system-name system)))))) + (defun system-version* (system) + "Return the version of SYSTEM. The returned version is version object +suitable for use with VERSION< and friends (or NIL)." + (if-let (direct-version (component-version* system)) + direct-version + (unless (primary-system-p system) + (component-version* (find-system (primary-system-name system))))))) ;;;; Pathnames @@ -253,3 +276,13 @@ return the absolute pathname of a corresponding file under that system's source (defmethod component-build-pathname ((c component)) nil)) +;;;; version-satisfies +(with-upgradability () + (defmethod version-satisfies ((c system) version) + (let ((component-version (component-version* c))) + (unless (and version component-version) + (when version + (warn "Requested version ~S but ~S has no version" version c)) + (return-from version-satisfies nil)) + (version-constraint-satisfied-p component-version version + :compatible-versions (system-compatible-versions c))))) diff --git a/test/script-support.lisp b/test/script-support.lisp index 2588573aa557af16c952953d031937f0da0baa56..abc4017780d22e72cd5878272998a3d727627124 100644 --- a/test/script-support.lisp +++ b/test/script-support.lisp @@ -761,10 +761,10 @@ is bound, write a message and exit on an error. If (format t "Now loading new asdf via method ~A~%" new-method) (acall (list new-method :asdf-test)) (format t "Testing it~%") - (format t "UIOP: ~S ~S~%" (asymval :*uiop-version*) (acall :system-version (acall :find-system :uiop))) - (format t "ASDF: ~S ~S~%" (get-asdf-version) (acall :system-version (acall :find-system :asdf))) - (assert (equal (asymval :*uiop-version*) (acall :system-version (acall :find-system :uiop)))) - (assert (equal (get-asdf-version) (acall :system-version (acall :find-system :asdf)))) + (format t "UIOP: ~S ~S~%" (asymval :*uiop-version*) (acall :version-string (acall :system-version* (acall :find-system :uiop)))) + (format t "ASDF: ~S ~S~%" (get-asdf-version) (acall :version-string (acall :system-version* (acall :find-system :asdf)))) + (assert (equal (asymval :*uiop-version*) (acall :version-string (acall :system-version* (acall :find-system :uiop))))) + (assert (equal (get-asdf-version) (acall :version-string (acall :system-version* (acall :find-system :asdf))))) (register-directory *test-directory*) (load-test-system :test-asdf/upgrade) (assert (symbol-value '*properly-upgraded*)))) diff --git a/test/test-operation-classes.script b/test/test-operation-classes.script index 94095b66e6933bddb149c95a3cf287a7cc183100..1fcbb18f52d5701fea3d9b2f5179ec811681b4b4 100644 --- a/test/test-operation-classes.script +++ b/test/test-operation-classes.script @@ -58,25 +58,6 @@ (assert (make-operation 'my-good-operation)) - -;; This test exercises the backward-compatibility mechanism of operation, -;; whereby traditional unqualified operations are implicitly downward and sideward -(defclass trivial-operation (operation) ()) - -(assert-equal - (loop :for (o . c) :in (traverse 'trivial-operation '(:test-asdf/test-module-depend "quux")) - :collect (cons (type-of o) (component-find-path c))) - '((trivial-operation "test-asdf/test-module-depend" "file1") - (trivial-operation "test-asdf/test-module-depend" "quux" "file2") - (trivial-operation "test-asdf/test-module-depend" "quux" "file3mod" "file3") - (trivial-operation "test-asdf/test-module-depend" "quux" "file3mod") - (trivial-operation "test-asdf/test-module-depend" "quux"))) - -(operate 'trivial-operation 'test-asdf/test-module-depend) -;;; this test intended to catch a bug in operate :around method in operate.lisp, -;;; thanks to Jan Moringen [2014/08/10:rpg] -(operate (make-operation 'trivial-operation) 'test-asdf/test-module-depend) - ;;; operations should only be made by MAKE-OPERATION, and now we enforce this. (signals system-definition-error (make-instance 'load-op)) diff --git a/test/test-version.script b/test/test-version.script index 295e108a72ec1c599b591cb58742ab1739747b3a..513709dd03377559d56824efb69819139875c114 100644 --- a/test/test-version.script +++ b/test/test-version.script @@ -1,7 +1,22 @@ ;;; -*- Lisp -*- +(DBG "Check version parsing and precedence operations") +(assert (version< "1.0.0" "2.0.0")) +(assert (version< "2.0.0" "2.1.0")) +(assert (version< "2.1.0" "2.1.1")) + +(assert (version< "1.0.0-alpha" "1.0.0")) + +(assert (version< "1.0.0-alpha" "1.0.0-alpha.1")) +(assert (version< "1.0.0-alpha.1" (make-version "1.0.0-alpha.beta" :class 'semantic-version))) +(assert (version< (make-version "1.0.0-alpha.beta" :class 'semantic-version) "1.0.0-beta")) +(assert (version< "1.0.0-beta" "1.0.0-beta.2")) +(assert (version< "1.0.0-beta.2" "1.0.0-beta.11")) +(assert (version< "1.0.0-beta.11" "1.0.0-rc.1")) +(assert (version< "1.0.0-rc.1" "1.0.0")) + (DBG "Check that there is an ASDF version that correctly parses to a non-empty list") -(assert (consp (parse-version (asdf-version) 'error))) +(assert (not (null (make-version (asdf-version))))) (DBG "Check that ASDF is newer than 1.234") (assert-compare (version<= "1.234" (asdf-version))) (DBG "Check that ASDF is not a compatible replacement for 1.234") @@ -11,7 +26,15 @@ (defparameter *asdf* (find-system :asdf)) (assert-equal nil (system-source-directory *asdf*)) (DBG "Check that the fallback system bears the current asdf version") -(assert-equal (asdf-version) (component-version *asdf*)) +;; Ensure that COMPONENT-VERSION returns only the non-pre-release version string. +(assert-equal (version-string + (let ((version (make-version (asdf-version)))) + (if (version-pre-release-p version) + (version-pre-release-for version) + version))) + (component-version *asdf*)) +;; Ensure pre-release info (if any) isn't lost. +(assert-equal (asdf-version) (version-string (component-version* *asdf*))) (def-test-system :unversioned-system :pathname #.*test-directory*) @@ -29,6 +52,11 @@ :pathname #.*test-directory* :version "1.2") +(def-test-system :versioned-system-4 + :pathname #.*test-directory* + :version "2.1" + :compatible-versions (:>= "2.0")) + (def-test-system :versioned-system-file-form :defsystem-depends-on ((:version :test-asdf/2 "2.1")) :pathname #.*test-directory* @@ -45,7 +73,14 @@ (vtest :versioned-system-1 "1.0") (vtest :versioned-system-2 "1.0") +(vtest :versioned-system-2 '(:and "1.0" (:< "2"))) +(vtest :versioned-system-3 '(:= "1.2")) +(vtest :versioned-system-3 '(:<= "1.2")) +(vtest :versioned-system-3 '(:< "1.3")) +(vtest :versioned-system-3 '(:> "1.1")) (vtest :versioned-system-3 "2.0" nil) +(vtest :versioned-system-4 "2.0") +(vtest :versioned-system-4 '(:or "1.0" "2.0")) (vtest :versioned-system-file-form "1.0") (vtest :versioned-system-file-line "1.0") ;; version UNmatching @@ -54,7 +89,10 @@ (vtest :versioned-system-2 "1.1" t) (vtest :versioned-system-2 "1.1.1" nil) (vtest :versioned-system-2 "1.2" nil) +(vtest :versioned-system-2 '(:and "1.2" (:< "2")) nil) (vtest :versioned-system-3 "1.2") (vtest :versioned-system-3 "1.1") (vtest :versioned-system-3 "1.1.1") - +(vtest :versioned-system-3 '(:/= "1.2") nil) +(vtest :versioned-system-4 "1.2" nil) +(vtest :versioned-system-4 "2.3" nil) diff --git a/tools/release.lisp b/tools/release.lisp index 8303440e09e80665b3d24672dc7a75fb47875c9a..ea901f9bf264d36acb0690c7bf09e021185b709d 100644 --- a/tools/release.lisp +++ b/tools/release.lisp @@ -77,7 +77,7 @@ (defun asdf-defsystem-files () "list files in asdf/defsystem" (list* "build/asdf.lisp" ;; for bootstrap purposes - "asdf.asd" "version.lisp-expr" "header.lisp" + "asdf.asd" "full-version.lisp-expr" "header.lisp" (system-source-files "asdf/defsystem"))) (defun asdf-defsystem-name () (format nil "asdf-defsystem-~A" (version-from-file))) @@ -197,7 +197,7 @@ (break) ;; for each function, offer to do it or not (?) (with-asdf-dir () (let ((log (newlogfile "release" "all")) - (releasep (= (length (parse-version new-version)) 3))) + (releasep (not (uiop:version-pre-release-p (make-version new-version))))) (when releasep (let ((debian-version (debian-version-from-file))) (unless (equal new-version (parse-debian-version debian-version)) diff --git a/tools/version.lisp b/tools/version.lisp index 31128127e6b3bd3b8fafc1d13310c22def9ffb79..4f59ead1602bcc84a9f39dceadd0015e412db7a1 100644 --- a/tools/version.lisp +++ b/tools/version.lisp @@ -10,8 +10,9 @@ (defun version-from-file (&optional commit) (if commit - (nth-value 2 (git `(show (,commit":version.lisp-expr")) :output :form)) - (safe-read-file-form (pn "version.lisp-expr")))) + (or (ignore-errors (nth-value 2 (git `(show (,commit":full-version.lisp-expr")) :output :form))) + (nth-value 2 (git `(show (,commit":version.lisp-expr")) :output :form))) + (safe-read-file-form (pn "full-version.lisp-expr")))) (defun debian-version-from-file (&optional commit) (match (if commit @@ -40,7 +41,8 @@ ;;; Bumping the version of ASDF (defparameter *versioned-files* - '(("version.lisp-expr" "\"" "\"") + '(("full-version.lisp-expr" "\"" "\"") + ("full-version.lisp-expr" "\"" "\"" t) ("uiop/version.lisp" "(defparameter *uiop-version* \"" "\")") ("asdf.asd" " :version \"" "\" ;; to be automatically updated by make bump-version") ("header.lisp" "This is ASDF " ": Another System Definition Facility.") @@ -52,18 +54,12 @@ (defparameter *new-version* :default) (defun compute-next-version (v) - (let ((pv (parse-version v 'error))) - (assert (first pv)) - (assert (second pv)) - (unless (third pv) (appendf pv (list 0))) - (unless (fourth pv) (appendf pv (list 0))) - (incf (car (last pv))) - (unparse-version pv))) + (version-string (version-next v))) (defun versions-from-args (&optional v1 v2) (labels ((check (old new) - (parse-version old 'error) - (parse-version new 'error) + (make-version old) + (make-version new) (values old new))) (cond ((and v1 v2) (check v1 v2)) @@ -93,9 +89,12 @@ (format t "done.~%")))) (success)) -(defun version-transformer (new-version file prefix suffix &optional dont-warn) +(defun version-transformer (new-version file prefix suffix &key dont-warn strip-pre-release) + (let ((--position (position #\- new-version))) + (when (and strip-pre-release (not (null --position))) + (setf new-version (subseq new-version 0 --position)))) (let* ((qprefix (cl-ppcre:quote-meta-chars prefix)) - (versionrx "([0-9]+(\\.[0-9]+)+)") + (versionrx "([0-9]+(\\.[0-9]+)+(-[a-zA-Z0-9_.-]+)?)") (qsuffix (cl-ppcre:quote-meta-chars suffix)) (regex (strcat "(" qprefix ")(" versionrx ")(" qsuffix ")")) (replacement @@ -107,8 +106,9 @@ (warn "Missing version in ~A" (file-namestring file))) (values new-text foundp))))) -(defun transform-file (new-version file prefix suffix) - (maybe-replace-file (pn file) (version-transformer new-version file prefix suffix))) +(defun transform-file (new-version file prefix suffix &optional strip-pre-release) + (maybe-replace-file (pn file) (version-transformer new-version file prefix suffix + :strip-pre-release strip-pre-release))) (defun transform-files (new-version) (loop :for f :in *versioned-files* :do (apply 'transform-file new-version f)) @@ -118,7 +118,7 @@ (let ((lines (read-file-lines (pn file)))) (dolist (l lines (progn (warn "Couldn't find a match in ~A" file) nil)) (multiple-value-bind (new-text foundp) - (funcall (version-transformer new-version file prefix suffix t) l) + (funcall (version-transformer new-version file prefix suffix :dont-warn t) l) (when foundp (format t "Found a match:~% ==> ~A~%Replacing with~% ==> ~A~%~%" l new-text) diff --git a/uiop/backward-driver.lisp b/uiop/backward-driver.lisp index 4a67992b94918c294ec6b32ff7beff2eed9ae389..0e9462993cf82bfd0b8cf95ff34016b9f5baff4c 100644 --- a/uiop/backward-driver.lisp +++ b/uiop/backward-driver.lisp @@ -63,6 +63,7 @@ If major versions differ, it's not compatible. If they are equal, then any later version is compatible, with later being determined by a lexicographical comparison of minor numbers. DEPRECATED." + (declare (notinline parse-version)) (let ((x (parse-version provided-version nil)) (y (parse-version required-version nil))) (and x y (= (car x) (car y)) (lexicographic<= '< (cdr y) (cdr x))))))) diff --git a/uiop/run-program.lisp b/uiop/run-program.lisp index ff0f87dc58a952580d856b78264e96cc2418ae4a..efec38a0e8be5341e1f9f9922489c26e6553d0d3 100644 --- a/uiop/run-program.lisp +++ b/uiop/run-program.lisp @@ -424,15 +424,16 @@ or whether it's already taken care of by the implementation's underlying run-pro 'run-program :stream)) #+(or abcl allegro clozure cmucl ecl (and lispworks os-unix) mkcl sbcl scl) (let (#+(or abcl ecl mkcl) - (version (parse-version - #-abcl - (lisp-implementation-version) - #+abcl - (second (split-string (implementation-identifier) :separator '(#\-)))))) + (version (ignore-errors + (make-version + #-abcl + (lisp-implementation-version) + #+abcl + (second (split-string (implementation-identifier) :separator '(#\-))))))) (nest - #+abcl (unless (lexicographic< '< version '(1 4 0))) - #+ecl (unless (lexicographic<= '< version '(16 0 0))) - #+mkcl (unless (lexicographic<= '< version '(1 1 9))) + #+abcl (unless (version< version "1.4.0")) + #+ecl (unless (version<= version "16.0.0")) + #+mkcl (unless (version<= version "1.1.9")) (return-from %system (wait-process (apply 'launch-program (%normalize-system-command command) keys))))) @@ -570,8 +571,8 @@ or an indication of failure via the EXIT-CODE of the process" #-(or allegro clisp clozure sbcl) (os-cond ((os-windows-p) t)))) #+(or clasp clisp cormanlisp gcl (and lispworks os-windows) mcl xcl) t ;; A race condition in ECL <= 16.0.0 prevents using ext:run-program - #+ecl #.(if-let (ver (parse-version (lisp-implementation-version))) - (lexicographic<= '< ver '(16 0 0))) + #+ecl #.(if-let (ver (ignore-errors (make-version (lisp-implementation-version)))) + (version<= ver "16.0.0")) #+(and lispworks os-unix) (%interactivep input output error-output)) '%use-system '%use-launch-program) command keys))) diff --git a/uiop/stream.lisp b/uiop/stream.lisp index b0dd6cfdaf985bb950d978251db109065ce50c9b..4e3ac67706fda7f6b6e9492c039d5db9dfd8ebe1 100644 --- a/uiop/stream.lisp +++ b/uiop/stream.lisp @@ -199,7 +199,10 @@ If OUTPUT is a PATHNAME, open the file and write to it, passing ELEMENT-TYPE to Otherwise, signal an error." (etypecase output (null - (with-output-to-string (stream nil :element-type element-type) (funcall function stream))) + ;; SBCL emits an annoying note here because it can't stack allocate the + ;; initial buffer if the element-type isn't known at compile time. + (locally (declare #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note)) + (with-output-to-string (stream nil :element-type element-type) (funcall function stream)))) ((eql t) (funcall function *standard-output*)) (stream @@ -390,8 +393,12 @@ Otherwise, using WRITE-SEQUENCE using a buffer of size BUFFER-SIZE." "Read the contents of the INPUT stream as a string" (let ((string (with-open-stream (input input) - (with-output-to-string (output nil :element-type element-type) - (copy-stream-to-stream input output :element-type element-type))))) + ;; SBCL emits an annoying note here because it can't stack + ;; allocate the initial buffer if the element-type isn't known at + ;; compile time. + (locally (declare #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note)) + (with-output-to-string (output nil :element-type element-type) + (copy-stream-to-stream input output :element-type element-type)))))) (if stripped (stripln string) string))) (defun slurp-stream-lines (input &key count) diff --git a/uiop/uiop.asd b/uiop/uiop.asd index 72fffb0d35600cf4223eb2c6cffdec708e90b13e..62fdd6cbac82c7740e881dbdcc53b9db929d53f5 100644 --- a/uiop/uiop.asd +++ b/uiop/uiop.asd @@ -49,4 +49,4 @@ you already have a matching UIOP loaded." :around-compile call-without-redefinition-warnings . #-asdf3.1 () #+asdf3.1 (:class package-system - :version (:read-file-form "version.lisp" :at (2 2 2))))) + :version #-asdf3.4 (:read-file-form "version.lisp" :at (2 2 2)) #+asdf3.4 (:read-file-form "version.lisp" :at (2 3 2))))) diff --git a/uiop/version.lisp b/uiop/version.lisp index c34c22fb094a112519ecb59e5f9b12357642fcc5..7a5cc9ab9b8d030d86ff37d28d382d8a40860d2b 100644 --- a/uiop/version.lisp +++ b/uiop/version.lisp @@ -1,8 +1,13 @@ (uiop/package:define-package :uiop/version - (:recycle :uiop/version :uiop/utility :asdf) + (:recycle :uiop/version :uiop/utility :asdf) (:use :uiop/common-lisp :uiop/package :uiop/utility) (:export #:*uiop-version* + #:make-version + #:version-object #:version-string-invalid-error #:version-pre-release-p #:version-pre-release-for + #:version-next #:version-string + #:semantic-version #:default-version + #:version-constraint-satisfied-p #:parse-version #:unparse-version #:version< #:version<= #:version= ;; version support, moved from uiop/utility #:next-version #:deprecated-function-condition #:deprecated-function-name ;; deprecation control @@ -12,63 +17,403 @@ (in-package :uiop/version) (with-upgradability () - (defparameter *uiop-version* "3.3.5.7") + (defparameter *fake-uiop-version* "3.4.0" + "ASDF has some logic in FIND-SYSTEM to ensure that an old version of UIOP +isn't returned. To do so, it looks at form '(2 2 2) in this file. Because old +versions of ASDF can't parse version strings with pre-release information, we +make sure this is always set to the non-pre-release version.") + (defparameter *uiop-version* "3.4.0-alpha.1")) - (defun unparse-version (version-list) - "From a parsed version (a list of natural numbers), compute the version string" - (format nil "~{~D~^.~}" version-list)) +;; Base version-object class and API. +(with-upgradability () + ;; Introduced in version 3.4. This can't be called VERSION because ASDF + ;; already exports a VERSION (a slot name for COMPONENT, why???). So let's + ;; not deal with changing VERSION's home package and all the messiness that + ;; would entail. + (defclass version-object () + () + (:documentation + "The base class of version specifiers.")) - (defun parse-version (version-string &optional on-error) - "Parse a VERSION-STRING as a series of natural numbers separated by dots. -Return a (non-null) list of integers if the string is valid; -otherwise return NIL. + (define-condition version-string-invalid-error (error) + ((%version-string + :initarg :version-string + :reader %version-string) + (reason + :initarg :reason + :reader %reason)) + (:report + (lambda (condition stream) + (format stream "The version string ~S is valid.~@[~%~A~]" + (%version-string condition) (%reason condition))))) -When invalid, ON-ERROR is called as per CALL-FUNCTION before to return NIL, -with format arguments explaining why the version is invalid. -ON-ERROR is also called if the version is not canonical -in that it doesn't print back to itself, but the list is returned anyway." - (block nil - (unless (stringp version-string) - (call-function on-error "~S: ~S is not a string" 'parse-version version-string) - (return)) - (unless (loop :for prev = nil :then c :for c :across version-string - :always (or (digit-char-p c) - (and (eql c #\.) prev (not (eql prev #\.)))) - :finally (return (and c (digit-char-p c)))) - (call-function on-error "~S: ~S doesn't follow asdf version numbering convention" - 'parse-version version-string) - (return)) - (let* ((version-list - (mapcar #'parse-integer (split-string version-string :separator "."))) - (normalized-version (unparse-version version-list))) - (unless (equal version-string normalized-version) - (call-function on-error "~S: ~S contains leading zeros" 'parse-version version-string)) - version-list))) - - (defun next-version (version) - "When VERSION is not nil, it is a string, then parse it as a version, compute the next version -and return it as a string." - (when version - (let ((version-list (parse-version version))) - (incf (car (last version-list))) - (unparse-version version-list)))) + (defun make-version (version-string &key (class 'default-version)) + (when (not (null version-string)) + (restart-case + (make-instance class :version-string version-string) + (continue () + :report "Return NIL as the version object." + nil) + (use-value (new-version-string) + :report "Retry parsing, with a new version string." + :interactive (lambda () + (format *query-io* "Enter a new version string: ") + (list (read-line *query-io*))) + (make-version new-version-string :class class))))) - (defun version< (version1 version2) - "Given two version strings, return T if the second is strictly newer" - (let ((v1 (parse-version version1 nil)) - (v2 (parse-version version2 nil))) - (lexicographic< '< v1 v2))) + (defun ensure-version (maybe-version &key (class 'default-version)) + (if (stringp maybe-version) + (make-version maybe-version :class class) + maybe-version)) + (defgeneric version-next (version) + (:documentation "Returns the next version after VERSION. The returned +version must not be a pre-release.") + (:method ((version null)) + nil) + (:method ((version string)) + (version-next (make-version version)))) + (defgeneric version-pre-release-p (version) + (:documentation "Returns non-NIL if VERSION is a pre-release.") + (:method ((version string)) + (version-pre-release-p (make-version version)))) + (defgeneric version-pre-release-for (version) + (:documentation "If VERSION is a pre-release, returns an version specifier +stating for which version it is a pre-release.") + (:method ((version string)) + (version-pre-release-for (make-version version)))) + (when (and (fboundp 'version<) + (not (typep (symbol-function 'version<) 'generic-function))) + ;; ASDF 3.4: Turned from function to generic function. Only conditionally + ;; making it unbound because this is an extension point for the user and we + ;; don't want to gratuitously remove their methods. + (fmakunbound 'version<)) + (defgeneric version< (version1 version2) + (:documentation "Returns non-NIL if VERSION2 is strictly newer than VERSION1.") + (:method ((version1 string) version2) + (version< (make-version version1) version2)) + (:method (version1 (version2 string)) + (version< version1 (make-version version2))) + (:method (version1 (version2 null)) + "This method doesn't make a lot of sense, but it it here to maintain +backward compatibility with the behavior of VERSION< prior to 3.4." + (declare (ignore version1)) + nil) + (:method ((version1 null) version2) + "This method doesn't make a lot of sense, but it it here to maintain +backward compatibility with the behavior of VERSION< prior to 3.4." + (declare (ignore version2)) + t) + (:method ((version1 null) (version2 null)) + "This method doesn't make a lot of sense, but it it here to maintain +backward compatibility with the behavior of VERSION< prior to 3.4." + nil)) + (defgeneric version-string (version) + (:documentation "Return a string that represents VERSION.") + (:method ((version string)) + ;; Round trip instead of returning VERSION, in case VERSION is invalid. + (version-string (make-version version)))) (defun version<= (version1 version2) "Given two version strings, return T if the second is newer or the same" - (not (version< version2 version1)))) - + (not (version< version2 version1))) (defun version= (version1 version2) "Given two version strings, return T if the first is newer or the same and the second is also newer or the same." (and (version<= version1 version2) (version<= version2 version1))) + (defmethod print-object ((version version-object) stream) + (print-unreadable-object (version stream :type t :identity t) + (format stream "~A" (version-string version))))) + +;; Semantic version class +(with-upgradability () + (defparameter *semver-valid-chars* + '(#\a #\b #\c #\d #\e #\f #\g #\h #\i #\j #\k #\l #\m + #\n #\o #\p #\q #\r #\s #\t #\u #\v #\w #\x #\y #\z + #\A #\B #\C #\D #\E #\F #\G #\H #\I #\J #\K #\L #\M + #\N #\O #\P #\Q #\R #\S #\T #\U #\V #\W #\X #\Y #\Z + #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 + #\-) + "List of all characters valid in pre-release and build metadata segments.") + + (defclass semantic-version (version-object) + (core-segment + pre-release-segment + build-metadata-segment) + (:documentation + "Implements semantic version parsing and ordering as specified by + https://semver.org/spec/v2.0.0.html")) + + (defun split-semver-string (version-string) + "Given a semver string, split it into its three basic components: the core +segment, the pre-release segment, and the build segment. Returns the three +components in a list. If the component is missing from the string, it returns +NIL." + (let ((pre-release-separator-position (position #\- version-string)) + (build-separator-position (position #\+ version-string))) + ;; If the pre-release and build separators both exist and the former comes + ;; after the latter, treat the former as not existing. + (when (and pre-release-separator-position + build-separator-position + (> pre-release-separator-position build-separator-position)) + (setf pre-release-separator-position nil)) + (list + ;; The core segment + (subseq version-string 0 (or pre-release-separator-position + build-separator-position)) + ;; The pre-release segment + (when pre-release-separator-position + (subseq version-string (1+ pre-release-separator-position) + build-separator-position)) + ;; The build segment + (when build-separator-position + (subseq version-string (1+ build-separator-position)))))) + + (defun parse-semver-dot-separated-segment (segment-string identifier-valid-test transform on-error) + "Given a string consisting of any number of identifiers separated by +#\\. characters, return a list of the identifiers. + +Each identifier is checked with IDENTIFIER-VALID-TEST to ensure it is valid. + +Then if the identifier is valid, TRANSFORM is called to canonicalize the +identifier. + +If there are any invalid segments, ON-ERROR is called with a string describing +the problem." + (let ((raw-identifiers (split-string segment-string :separator '(#\.)))) + (dolist (raw-identifier raw-identifiers) + (when (emptyp raw-identifier) + (call-function on-error (format nil "Invalid identifier ~S" raw-identifier))) + (unless (call-function identifier-valid-test raw-identifier) + (call-function on-error (format nil "Invalid identifier ~S" raw-identifier)))) + (mapcar transform raw-identifiers))) + + (defun valid-core-segment-identifier-p (string &key (on-leading-zero :warn)) + "An identifier is valid for use in the core segment if it is a non-negative +integer. + +ON-LEADING-ZERO specifies how to handle leading zeros in identifier. If :WARN, +a warning is signaled and the identifier is considered valid. If :IGNORE, the +identifier is considered valid. If INVALID, the identifier is not considered +valid." + (check-type on-leading-zero (member :warn :invalid :ignore)) + (and (every #'digit-char-p string) + (or (= 1 (length string)) + (not (eql (aref string 0) #\0)) + (ecase on-leading-zero + (:warn + (warn "Identifier ~S contains a leading zero." string) + t) + (:ignore + t) + (:invalid + nil))))) + + (defun valid-pre-release-segment-identifier-p (string &key (on-leading-zero :warn)) + "An identifier is valid for use in the pre-release segment if it is a +non-negative integer or consists entirely of alphanumeric and #\\- characters. + +See VALID-CORE-SEGMENT-IDENTIFIER-P for a discussion of ON-LEADING-ZERO." + (check-type on-leading-zero (member :warn :invalid :ignore)) + (and (every (lambda (c) (member c *semver-valid-chars*)) string) + (or (notevery #'digit-char-p string) + (valid-core-segment-identifier-p string :on-leading-zero on-leading-zero)))) + + (defun valid-build-segment-identifier-p (string) + "An identifier is valid for use in the build segment if it consists +entirely of alphanumeris and #\\- characters." + (every (lambda (c) (member c *semver-valid-chars*)) string)) + + (defun transform-pre-release-segment-identifier (string) + "If STRING consistes only of numeric characters, parse it as an integer and +return it. Otherwise, return STRING." + (let ((as-integer (ignore-errors (parse-integer string)))) + (if (and as-integer (not (minusp as-integer))) + as-integer + string))) + + (defun parse-semver-string (version-string) + "Given a string nominally representing a version string in the semantic +version grammar, return three VALUES: the list of core segment identifiers, the +list of pre-release segment identifiers, and the list of build metadata +identifiers. + +If VERSION-STRING contains leading zeros in a numeric identifier, a warning is +signaled. + +If VERSION-STRING is otherwise invalid, a VERSION-STRING-INVALID-ERROR is signaled." + (destructuring-bind (core-segment-string pre-release-segment-string build-segment-string) + (split-semver-string version-string) + (flet ((invalid (&optional reason) + (error 'version-string-invalid-error :version-string version-string + :reason reason))) + (when (null core-segment-string) + (invalid "There is no core segment.")) + (when (and (stringp pre-release-segment-string) + (emptyp pre-release-segment-string)) + (invalid "There is a #\\- character, but the pre-release segment is empty.")) + (when (and (stringp build-segment-string) + (emptyp build-segment-string)) + (invalid "There is a #\\+ character, but the build segment is empty.")) + + (let ((core-segment + (parse-semver-dot-separated-segment core-segment-string 'valid-core-segment-identifier-p #'parse-integer #'invalid)) + (pre-release-segment + (parse-semver-dot-separated-segment pre-release-segment-string 'valid-pre-release-segment-identifier-p 'transform-pre-release-segment-identifier #'invalid)) + (build-metadata + (parse-semver-dot-separated-segment build-segment-string 'valid-build-segment-identifier-p #'identity #'invalid))) + (values core-segment pre-release-segment build-metadata))))) + + (defmethod initialize-instance :after ((version semantic-version) &key version-string) + (with-slots (core-segment pre-release-segment build-metadata-segment) version + (multiple-value-setq + (core-segment pre-release-segment build-metadata-segment) + (parse-semver-string version-string)))) + + (defun semver-pre-release-segment-< (identifier1 identifier2) + (cond + ((and (integerp identifier1) (integerp identifier2)) + (< identifier1 identifier2)) + ((integerp identifier1) + t) + ((integerp identifier2) + nil) + (t + (string< identifier1 identifier2)))) + (defun semver-pre-release-< (pre-release-segment-1 pre-release-segment-2) + (or (and pre-release-segment-1 (null pre-release-segment-2)) + (lexicographic< 'semver-pre-release-segment-< pre-release-segment-1 pre-release-segment-2))) + + (defmethod version-next ((version semantic-version)) + (with-slots (core-segment) version + (let ((new-core-segment (copy-list core-segment))) + (incf (car (last new-core-segment))) + (make-version (format nil "~{~D~^.~}" new-core-segment) :class (class-of version))))) + (defmethod version-pre-release-p ((version semantic-version)) + (with-slots (pre-release-segment) version + (not (null pre-release-segment)))) + (defmethod version-pre-release-for ((version semantic-version)) + (with-slots (core-segment) version + (make-version (format nil "~{~D~^.~}" core-segment) :class (class-of version)))) + (defmethod version< ((version1 semantic-version) (version2 semantic-version)) + (with-slots ((core-segment-1 core-segment) (pre-release-segment-1 pre-release-segment)) version1 + (with-slots ((core-segment-2 core-segment) (pre-release-segment-2 pre-release-segment)) version2 + (or (lexicographic< #'< core-segment-1 core-segment-2) + (and (equal core-segment-1 core-segment-2) + (semver-pre-release-< pre-release-segment-1 pre-release-segment-2)))))) + (defmethod version-string ((version semantic-version)) + (with-slots (core-segment pre-release-segment build-metadata-segment) version + (format nil "~{~D~^.~}~@[-~{~A~^.~}~]~@[+~{~A~^.~}~]" + core-segment pre-release-segment build-metadata-segment)))) + +;; ASDF's (slightly) opinionated default version. +(with-upgradability () + (defclass default-version (semantic-version) + () + (:documentation + "A version specifier that parses and orders identically to +SEMANTIC-VERSION. However, no build metadata is allowed and the pre-release +segment can consist of at most three identifiers. The first must be \"alpha\", +\"beta\", or \"rc\". The second and third (if they exist) must be integers.")) + + (defmethod initialize-instance :after ((version default-version) &key version-string) + (with-slots (pre-release-segment build-metadata-segment) version + (flet ((invalid (&optional reason) + (error 'version-string-invalid-error :version-string version-string + :reason reason))) + (unless (null build-metadata-segment) + (invalid "The build metadata segment must not exist.")) + (unless (or (null pre-release-segment) + (and (<= (length pre-release-segment) 3) + (or (equal "alpha" (first pre-release-segment)) + (equal "beta" (first pre-release-segment)) + (equal "rc" (first pre-release-segment))) + (every #'integerp (rest pre-release-segment)))) + (invalid "The pre-release segment must be absent or consist of \"alpha\", \"beta\", or \"rc\", optionally followed by up to two integers.")))))) + +(with-upgradability () + (defun simple-version-constraint-p (constraint) + (and (listp constraint) + (and (= (length constraint) 2)) + (member (first constraint) '(:>= :> :<= :< := :/=)) + (or (stringp (second constraint)) + (typep (second constraint) 'version-object)))) + (defun simple-version-constraint-satisfied-p (operator version requested + &key (strip-pre-release-p (not (version-pre-release-p requested)))) + (let ((version (if (and strip-pre-release-p (version-pre-release-p version)) + (version-pre-release-for version) + version))) + (ecase operator + (:>= + (not (version< version requested))) + (:> + (version< requested version)) + (:<= + (not (version< requested version))) + (:< + (version< version requested)) + (:= + (version= version requested)) + (:/= + (not (version= version requested)))))) + + (defun compound-version-constraint-p (constraint) + (and (listp constraint) + (member (first constraint) '(:and :or)))) + (defun compound-version-constraint-satisfied-p (operator version subcs + &rest args + &key strip-pre-release-p compatible-versions) + (declare (ignore strip-pre-release-p compatible-versions)) + (ecase operator + (:and + (every (lambda (subc) (apply 'version-constraint-satisfied-p version subc args)) + subcs)) + (:or + (some (lambda (subc) (apply 'version-constraint-satisfied-p version subc args)) + subcs)))) + + (defun compatible-version-constraint-p (constraint) + (or (and (listp constraint) + (and (= (length constraint) 2)) + (eql (first constraint) :compatible) + (or (stringp (second constraint)) + (typep (second constraint) 'version-object))) + (stringp constraint) + (typep constraint 'version-object))) + (defun compatible-version-constraint-satisfied-p (version requested + &rest args + &key compatible-versions + (strip-pre-release-p (not (version-pre-release-p requested)))) + (let ((version (if (and strip-pre-release-p (version-pre-release-p version)) + (version-pre-release-for version) + version))) + (and (not (version< version requested)) + (or (null compatible-versions) + (apply 'version-constraint-satisfied-p requested compatible-versions + (remove-plist-key :compatible-versions args)))))) + + (defun version-constraint-satisfied-p (version constraint + &rest args + &key strip-pre-release-p compatible-versions) + (declare (ignore strip-pre-release-p compatible-versions)) + (let ((version (ensure-version version))) + (cond + ((simple-version-constraint-p constraint) + (apply #'simple-version-constraint-satisfied-p + (first constraint) version + (ensure-version (second constraint) + :class (class-of version)) + (remove-plist-key :compatible-versions args))) + ((compatible-version-constraint-p constraint) + (let ((requested (ensure-version (if (listp constraint) (second constraint) constraint) + :class (class-of version)))) + (apply #'compatible-version-constraint-satisfied-p version requested args))) + ((compound-version-constraint-p constraint) + (apply #'compound-version-constraint-satisfied-p + (first constraint) version (rest constraint) + args)) + (t t))))) (with-upgradability () (define-condition deprecated-function-condition (condition) @@ -114,19 +459,22 @@ the second is also newer or the same." ((:error) (cerror "USE FUNCTION ANYWAY" 'deprecated-function-error :name name)))) (defun version-deprecation (version &key (style-warning nil) - (warning (next-version style-warning)) - (error (next-version warning)) - (delete (next-version error))) + (warning (version-next style-warning)) + (error (version-next warning)) + (delete (version-next error))) "Given a VERSION string, and the starting versions for notifying the programmer of various levels of deprecation, return the current level of deprecation as per WITH-DEPRECATION that is the highest level that has a declared version older than the specified version. Each start version for a level of deprecation can be specified by a keyword argument, or -if left unspecified, will be the NEXT-VERSION of the immediate lower level of deprecation." - (cond - ((and delete (version<= delete version)) :delete) - ((and error (version<= error version)) :error) - ((and warning (version<= warning version)) :warning) - ((and style-warning (version<= style-warning version)) :style-warning))) +if left unspecified, will be the VERSION-NEXT of the immediate lower level of deprecation." + (let ((version (if (version-pre-release-p version) + (version-pre-release-for version) + version))) + (cond + ((and delete (version<= delete version)) :delete) + ((and error (version<= error version)) :error) + ((and warning (version<= warning version)) :warning) + ((and style-warning (version<= style-warning version)) :style-warning)))) (defmacro with-deprecation ((level) &body definitions) "Given a deprecation LEVEL (a form to be EVAL'ed at macro-expansion time), instrument the @@ -185,3 +533,54 @@ from instrumentation by enclosing it in a PROGN." form))) (t form)))))))) + +;; All of these were deprecated in 3.4 +(with-upgradability () + (with-deprecation ((version-deprecation *uiop-version* :style-warning "3.4" :delete "4.0")) + (defun unparse-version (version-list) + "DEPRECATED. Use VERSION-STRING instead. + +From a parsed version (a list of natural numbers), compute the version string" + (format nil "~{~D~^.~}" version-list)) + + (defun parse-version (version-string &optional on-error) + "DEPRECATED. Use MAKE-VERSION instead. + +Parse a VERSION-STRING as a series of natural numbers separated by dots. +Return a (non-null) list of integers if the string is valid; +otherwise return NIL. + +When invalid, ON-ERROR is called as per CALL-FUNCTION before to return NIL, +with format arguments explaining why the version is invalid. +ON-ERROR is also called if the version is not canonical +in that it doesn't print back to itself, but the list is returned anyway." + (declare (notinline unparse-version)) + (block nil + (unless (stringp version-string) + (call-function on-error "~S: ~S is not a string" 'parse-version version-string) + (return)) + (unless (loop :for prev = nil :then c :for c :across version-string + :always (or (digit-char-p c) + (and (eql c #\.) prev (not (eql prev #\.)))) + :finally (return (and c (digit-char-p c)))) + (call-function on-error "~S: ~S doesn't follow asdf version numbering convention" + 'parse-version version-string) + (return)) + (let* ((version-list + (mapcar #'parse-integer (split-string version-string :separator "."))) + (normalized-version (unparse-version version-list))) + (unless (equal version-string normalized-version) + (call-function on-error "~S: ~S contains leading zeros" 'parse-version version-string)) + version-list))) + + (defun next-version (version) + "DEPRECATED. Use VERSION-NEXT instead. + +When VERSION is not nil, it is a string, then parse it as a version, compute the next version +and return it as a string." + (declare (notinline parse-version unparse-version next-version)) + (when version + (let ((version-list (parse-version version))) + (incf (car (last version-list))) + (unparse-version version-list))) + (next-version version)))) diff --git a/upgrade.lisp b/upgrade.lisp index 81f0316149e4840fcbb7cf455bd11316303df2e8..aacfa90941c85de016d2084b33991deac5c11ca7 100644 --- a/upgrade.lisp +++ b/upgrade.lisp @@ -94,10 +94,10 @@ previously-loaded version of ASDF." ;; Relying on its automation, the version is now redundantly present on top of asdf.lisp. ;; "3.4" would be the general branch for major version 3, minor version 4. ;; "3.4.5" would be an official release in the 3.4 branch. - ;; "3.4.5.67" would be a development version in the official branch, on top of 3.4.5. - ;; "3.4.5.0.8" would be your eighth local modification of official release 3.4.5 - ;; "3.4.5.67.8" would be your eighth local modification of development version 3.4.5.67 - (asdf-version "3.3.5.7") + ;; "3.4.6-alpha.1" would be a development version in the official branch, on top of 3.4.5. + ;; "3.4.5.8" would be your eighth local modification of official release 3.4.5 + ;; "3.4.6-alpha.1.8" would be your eighth local modification of development version 3.4.6-alpha.1 + (asdf-version "3.4.0-alpha.1") (existing-version (asdf-version))) (setf *asdf-version* asdf-version) (when (and existing-version (not (equal asdf-version existing-version))) diff --git a/version.lisp-expr b/version.lisp-expr index 015c0944363e419c77cb77422e859d0eb5459d51..d8e9dc0c08114e981d1d9ee7dc5597d40b8832a5 100644 --- a/version.lisp-expr +++ b/version.lisp-expr @@ -1,2 +1,5 @@ -"3.3.5.7" +;; This must contain the version without pre-release info, as versions of ASDF +;; earlier than 3.4 use this file to determine if they should self-upgrade and +;; they are unable to parse pre-release info. +"3.4.0"