Loading CONTRIBUTING.org 0 → 100644 +10 −0 Original line number Diff line number Diff line #+TITLE: Contributing Contributions are welcome! Note that I've tried to make the internal mechanisms extensible, but have not yet exported the generic functions neccessary for other projects to extend the behavior. They're not yet exported because I want to leave the option to refactor things on the table until I know I have consumers. If you'd like those GFs exported, just open an issue! After that point I'll make sure to not break anything... LICENSE 0 → 100644 +19 −0 Original line number Diff line number Diff line Copyright 2021 Eric Timmons <eric@timmons.dev> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. No newline at end of file README.org 0 → 100644 +244 −0 Original line number Diff line number Diff line #+TITLE: ASDF Package Inferred Components ASDF's Package Inferred System class is pretty great. However, it has some aspects that annoy me and this project is an attempt to clean those up, while maintaining as many of the benefits as possible. The best features of package inferred systems are: 1. Dependencies between files in the same project are automatically inferred. 2. Dependencies on external systems are declared very close to where they are used. The worst features (IMHO) are: 1. Every single *file* in your project is a potential dependency for other systems. Because every file is turned into a system, people using your code can say ~(:depends-on :your-system/a/b/c)~ and then they suddenly become intimately tied to the implementation details of your project. Sure, you can say in your README "Only depend on ~:your-system~ or ~:your-system/a~", but why not expose only the systems you want? 2. There is no easy way to extract dependencies for a project. In order to do so, you must recurse into every sub-system. That is equivalent to recursing into every file and checking it for external dependencies. 3. Some features of ASDF dependency specification are unavailable. You cannot use ~:feature~ or ~:version~ dependencies in any of your inferred systems. ~:version~ dependencies are pretty easy to hoist to the top level system, but ~:feature~ dependencies are much trickier to get right. 4. It is difficult to include intraproject dependencies on anything other than Common Lisp source files. If you have a grovel-file from CFFI, there is no way to declaratively say that source file ~a/b/c.lisp~ depends on that grovel-file being operated on first. 5. It is difficult to introspect a system to determine its components. Similar to 2, you need to recurse into every subsystem/file to do so. 6. It is *required* that every CL source file has a corresponding package. This can help with modularity on large systems, but can be annoying on smaller systems. Plus some people plain don't like having that many packages around. Most of these cons stem from the fact that the package-inferred-system class creates an entirely new system for *every* lisp file in your project. This project attempts to bring a little sanity to the inferred approach by inferring *components* of a parent system. * Notes + This currently requires a patched version of ASDF to work. * Quickstart ** One package per system This example shows how to define a system ~z~ that uses fewer than one package per file. It has four source files: ~package~, ~a~, ~b~, and ~c~. ~a~, ~b~, and ~c~ all depend on ~package~. ~c~ also depends on both ~a~ and ~b~. Additionally, the system depends on system ~x~, but let's assume all uses of ~x~ are localized to file ~b~. Without inferred components, your system definition would look like: #+begin_src common-lisp (defsystem #:z :components ((:file "package") (:file "a" :depends-on ("package")) (:file "b" :depends-on ("package")) (:file "c" :depends-on ("package" "a" "b"))) :depends-on (#:x)) #+end_src With inferred components, your system definition would look like: #+begin_src common-lisp (defsystem #:z :defsystem-depends-on (#:asdf-inferred-components) :class "aic:inferred-components-system") #+end_src File ~a.lisp~ would start with: #+begin_src common-lisp (aic:define-component :depends-on (#:./package)) #+end_src File ~b.lisp~ would start with: #+begin_src common-lisp (aic:define-component :depends-on (#:./package #:x)) #+end_src File ~c.lisp~ would start with: #+begin_src common-lisp (aic:define-component :depends-on (#:./package #:./a #:./b)) #+end_src ** One package per file This example shows the same as above, except each file also defines its own package. As such, we will also drop the ~package~ file. The system defintion would look like: #+begin_src common-lisp (defsystem #:z :defsystem-depends-on (#:asdf-inferred-components) :class "aic:inferred-components-system") #+end_src File ~a.lisp~ would start with: #+begin_src common-lisp (aic:define-package #:z/a (:use #:cl)) #+end_src File ~b.lisp~ would start with: #+begin_src common-lisp (aic:define-package #:z/b (:use #:cl #:x)) #+end_src File ~c.lisp~ would start with: #+begin_src common-lisp (aic:define-package #:z/c (:use #:cl #:./a #:./b)) #+end_src * How it works When the system object is being constructed, we descend into every folder under the system's pathname. A module is created for every directory, with the same name as the directory. Then, for every directory, we examine the files contained within it and create components for them. If the type of a file is recognized, then the file is examined for a component defining form. If the defining form exists, the options for the component (such as ~:depends-on~ and ~:if-feature~) are extracted and used to define the actual component object. This overrides all the options provided explicitly through ~defsystem~'s ~:components~ list (if any). If no component defining form is found, then the options for the file that are provided explicitly (via ~defsystem~'s ~:components~ list) are used. If the type of the file is not recognized and there is no explicit definition of the file, a component of type ~:static-file~ is created with no options. * Recognized File Types This package recognizes the current file types: + ~lisp~, ~cl~, ~lsp~ :: Common Lisp source files + ~asd~ :: ASD definitions Any ASD definitions are ignored and *not* added as components. This helps handle the common case where the ~.asd~ file is in the same directory as the project's source files. Common Lisp source files are created using the ~:file~ component type. * Component Defining Forms ** Common Lisp source files Component defining forms must be the first form in a file. Two forms are currently recognized: + ~(aic:define-component (&rest options &key depends-on component-type &allow-other-keys))~ :: This is a macro that expands to ~(progn)~ (i.e., it does nothing when the file is compiled or loaded). If ~component-type~ is provided, it must be something usable as the first form in the component definition. If not provided, it defaults to ~:file~. If ~depends-on~ is provided, it must be a list of dependency definitions. The provided list is processed before being handed off to ASDF's built in machinery. If the component name of any dependency starts with a ~.~ character, it is turned into an intrasystem dependency. To do so, the component name is parsed as a pathname relative to the file containing the defining form. Otherwise, the dependency is assumed to refer to a system. Every other option must be a keyword argument valid for explicitly defining a component. + ~(aic:define-package (package-name &rest options))~ :: This is a macro that is roughly equivalent to a ~aic:define-component~ form and a ~uiop:define-package~ form. The option ~(:component-def &rest plist)~ is used to define the options for creating the component. Every pair in ~plist~ is treated the same as if it were provided to ~aic:define-component~. There may be only one ~:component-def~ option. There may be any number of options of the form ~(:feature feature-expression &rest package-options)~. Every option in ~package-options~ is treated as described below, but its effects only happen if ~feature-expression~ evaluates to true (see ~uiop:featurep~). The remaining options are the package defining options. These options behave the same as if they were passed to ~uiop:define-package~. However, anywhere a package name is expected this macro also accepts a relative package name or a system name/package name pair. For example, if ~package-name~ is ~a/b/c~, the parent system is named ~a~, and ~options~ contains a form ~(:use #:../d/e)~, the package will use the package ~a/d/e~ and depend on the component ~("d" "e")~ in system ~a~. If ~options~ contains a form ~(:use (#:cl-z #:z))~, the package will use the package ~z~ and depend on the system ~cl-z~. If the first item in system/package pair is nil, no dependencies are extracted. While it would be possible to leave dependencies on other systems on each component (e.g., file ~a.lisp~ depends on system ~z~), doing so would likely break many tools and other features of ASDF. See [[https://gitlab.common-lisp.net/asdf/asdf/-/issues/68#note_7941]]. Therefore, any dependencies on systems are hoisted up to the parent system. * Future Work 1. Provide ability to specify component defining forms in file comments. This would be useful if someone wanted to use this project, but also make sure someone could load their system (by hand?) without having ASDF or AIC present. This could also lay the ground work for extending support beyond Common Lisp source files. 2. Recognize files for CFFI's groveller. It's used enough that we should have first class support for it. 3. Handle symlinks. I haven't tried it, but I wouldn't be surprised if things break horribly if there are symlinks within the source folders. But handling those isn't portable across OSes or implementations, so you're not using them, right? 4. Add option to disable hoisting of external dependencies to the parent system. asdf-inferred-components.asd 0 → 100644 +25 −0 Original line number Diff line number Diff line ;;;; ASDF-Inferred-Components System Definition ;;;; ;;;; This file is part of the asdf-inferred-components project. See README.org ;;;; and LICENSE for more information. (defsystem #:asdf-inferred-components :version (:read-file-form "version.lisp-expr") :license "MIT" :pathname "src" :in-order-to ((test-op (load-op "asdf-inferred-components/test"))) :perform (test-op (o c) (uiop:symbol-call :parachute :test :asdf-inferred-components-test)) :components ((:file "package") (:file "file-wrappers" :depends-on ("package")) (:file "inferred-component-system" :depends-on ("package" "file-wrappers")) (:file "define-component" :depends-on ("package" "inferred-component-system")) (:file "define-package" :depends-on ("package" "inferred-component-system" "define-component")))) (defsystem #:asdf-inferred-components/test :version (:read-file-form "version.lisp-expr") :license "MIT" :pathname "test" :components ((:file "package") (:file "aic-test-1") (:file "aic-test-2")) :depends-on (#:alexandria #:parachute)) clpmfile 0 → 100644 +8 −0 Original line number Diff line number Diff line ;;; -*- mode: common-lisp -*- (:api-version "0.3") (:source "quicklisp" :type :ql-clpi :url "https://quicklisp.common-lisp-project-index.org/") (:asd "asdf-inferred-components.asd") Loading
CONTRIBUTING.org 0 → 100644 +10 −0 Original line number Diff line number Diff line #+TITLE: Contributing Contributions are welcome! Note that I've tried to make the internal mechanisms extensible, but have not yet exported the generic functions neccessary for other projects to extend the behavior. They're not yet exported because I want to leave the option to refactor things on the table until I know I have consumers. If you'd like those GFs exported, just open an issue! After that point I'll make sure to not break anything...
LICENSE 0 → 100644 +19 −0 Original line number Diff line number Diff line Copyright 2021 Eric Timmons <eric@timmons.dev> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. No newline at end of file
README.org 0 → 100644 +244 −0 Original line number Diff line number Diff line #+TITLE: ASDF Package Inferred Components ASDF's Package Inferred System class is pretty great. However, it has some aspects that annoy me and this project is an attempt to clean those up, while maintaining as many of the benefits as possible. The best features of package inferred systems are: 1. Dependencies between files in the same project are automatically inferred. 2. Dependencies on external systems are declared very close to where they are used. The worst features (IMHO) are: 1. Every single *file* in your project is a potential dependency for other systems. Because every file is turned into a system, people using your code can say ~(:depends-on :your-system/a/b/c)~ and then they suddenly become intimately tied to the implementation details of your project. Sure, you can say in your README "Only depend on ~:your-system~ or ~:your-system/a~", but why not expose only the systems you want? 2. There is no easy way to extract dependencies for a project. In order to do so, you must recurse into every sub-system. That is equivalent to recursing into every file and checking it for external dependencies. 3. Some features of ASDF dependency specification are unavailable. You cannot use ~:feature~ or ~:version~ dependencies in any of your inferred systems. ~:version~ dependencies are pretty easy to hoist to the top level system, but ~:feature~ dependencies are much trickier to get right. 4. It is difficult to include intraproject dependencies on anything other than Common Lisp source files. If you have a grovel-file from CFFI, there is no way to declaratively say that source file ~a/b/c.lisp~ depends on that grovel-file being operated on first. 5. It is difficult to introspect a system to determine its components. Similar to 2, you need to recurse into every subsystem/file to do so. 6. It is *required* that every CL source file has a corresponding package. This can help with modularity on large systems, but can be annoying on smaller systems. Plus some people plain don't like having that many packages around. Most of these cons stem from the fact that the package-inferred-system class creates an entirely new system for *every* lisp file in your project. This project attempts to bring a little sanity to the inferred approach by inferring *components* of a parent system. * Notes + This currently requires a patched version of ASDF to work. * Quickstart ** One package per system This example shows how to define a system ~z~ that uses fewer than one package per file. It has four source files: ~package~, ~a~, ~b~, and ~c~. ~a~, ~b~, and ~c~ all depend on ~package~. ~c~ also depends on both ~a~ and ~b~. Additionally, the system depends on system ~x~, but let's assume all uses of ~x~ are localized to file ~b~. Without inferred components, your system definition would look like: #+begin_src common-lisp (defsystem #:z :components ((:file "package") (:file "a" :depends-on ("package")) (:file "b" :depends-on ("package")) (:file "c" :depends-on ("package" "a" "b"))) :depends-on (#:x)) #+end_src With inferred components, your system definition would look like: #+begin_src common-lisp (defsystem #:z :defsystem-depends-on (#:asdf-inferred-components) :class "aic:inferred-components-system") #+end_src File ~a.lisp~ would start with: #+begin_src common-lisp (aic:define-component :depends-on (#:./package)) #+end_src File ~b.lisp~ would start with: #+begin_src common-lisp (aic:define-component :depends-on (#:./package #:x)) #+end_src File ~c.lisp~ would start with: #+begin_src common-lisp (aic:define-component :depends-on (#:./package #:./a #:./b)) #+end_src ** One package per file This example shows the same as above, except each file also defines its own package. As such, we will also drop the ~package~ file. The system defintion would look like: #+begin_src common-lisp (defsystem #:z :defsystem-depends-on (#:asdf-inferred-components) :class "aic:inferred-components-system") #+end_src File ~a.lisp~ would start with: #+begin_src common-lisp (aic:define-package #:z/a (:use #:cl)) #+end_src File ~b.lisp~ would start with: #+begin_src common-lisp (aic:define-package #:z/b (:use #:cl #:x)) #+end_src File ~c.lisp~ would start with: #+begin_src common-lisp (aic:define-package #:z/c (:use #:cl #:./a #:./b)) #+end_src * How it works When the system object is being constructed, we descend into every folder under the system's pathname. A module is created for every directory, with the same name as the directory. Then, for every directory, we examine the files contained within it and create components for them. If the type of a file is recognized, then the file is examined for a component defining form. If the defining form exists, the options for the component (such as ~:depends-on~ and ~:if-feature~) are extracted and used to define the actual component object. This overrides all the options provided explicitly through ~defsystem~'s ~:components~ list (if any). If no component defining form is found, then the options for the file that are provided explicitly (via ~defsystem~'s ~:components~ list) are used. If the type of the file is not recognized and there is no explicit definition of the file, a component of type ~:static-file~ is created with no options. * Recognized File Types This package recognizes the current file types: + ~lisp~, ~cl~, ~lsp~ :: Common Lisp source files + ~asd~ :: ASD definitions Any ASD definitions are ignored and *not* added as components. This helps handle the common case where the ~.asd~ file is in the same directory as the project's source files. Common Lisp source files are created using the ~:file~ component type. * Component Defining Forms ** Common Lisp source files Component defining forms must be the first form in a file. Two forms are currently recognized: + ~(aic:define-component (&rest options &key depends-on component-type &allow-other-keys))~ :: This is a macro that expands to ~(progn)~ (i.e., it does nothing when the file is compiled or loaded). If ~component-type~ is provided, it must be something usable as the first form in the component definition. If not provided, it defaults to ~:file~. If ~depends-on~ is provided, it must be a list of dependency definitions. The provided list is processed before being handed off to ASDF's built in machinery. If the component name of any dependency starts with a ~.~ character, it is turned into an intrasystem dependency. To do so, the component name is parsed as a pathname relative to the file containing the defining form. Otherwise, the dependency is assumed to refer to a system. Every other option must be a keyword argument valid for explicitly defining a component. + ~(aic:define-package (package-name &rest options))~ :: This is a macro that is roughly equivalent to a ~aic:define-component~ form and a ~uiop:define-package~ form. The option ~(:component-def &rest plist)~ is used to define the options for creating the component. Every pair in ~plist~ is treated the same as if it were provided to ~aic:define-component~. There may be only one ~:component-def~ option. There may be any number of options of the form ~(:feature feature-expression &rest package-options)~. Every option in ~package-options~ is treated as described below, but its effects only happen if ~feature-expression~ evaluates to true (see ~uiop:featurep~). The remaining options are the package defining options. These options behave the same as if they were passed to ~uiop:define-package~. However, anywhere a package name is expected this macro also accepts a relative package name or a system name/package name pair. For example, if ~package-name~ is ~a/b/c~, the parent system is named ~a~, and ~options~ contains a form ~(:use #:../d/e)~, the package will use the package ~a/d/e~ and depend on the component ~("d" "e")~ in system ~a~. If ~options~ contains a form ~(:use (#:cl-z #:z))~, the package will use the package ~z~ and depend on the system ~cl-z~. If the first item in system/package pair is nil, no dependencies are extracted. While it would be possible to leave dependencies on other systems on each component (e.g., file ~a.lisp~ depends on system ~z~), doing so would likely break many tools and other features of ASDF. See [[https://gitlab.common-lisp.net/asdf/asdf/-/issues/68#note_7941]]. Therefore, any dependencies on systems are hoisted up to the parent system. * Future Work 1. Provide ability to specify component defining forms in file comments. This would be useful if someone wanted to use this project, but also make sure someone could load their system (by hand?) without having ASDF or AIC present. This could also lay the ground work for extending support beyond Common Lisp source files. 2. Recognize files for CFFI's groveller. It's used enough that we should have first class support for it. 3. Handle symlinks. I haven't tried it, but I wouldn't be surprised if things break horribly if there are symlinks within the source folders. But handling those isn't portable across OSes or implementations, so you're not using them, right? 4. Add option to disable hoisting of external dependencies to the parent system.
asdf-inferred-components.asd 0 → 100644 +25 −0 Original line number Diff line number Diff line ;;;; ASDF-Inferred-Components System Definition ;;;; ;;;; This file is part of the asdf-inferred-components project. See README.org ;;;; and LICENSE for more information. (defsystem #:asdf-inferred-components :version (:read-file-form "version.lisp-expr") :license "MIT" :pathname "src" :in-order-to ((test-op (load-op "asdf-inferred-components/test"))) :perform (test-op (o c) (uiop:symbol-call :parachute :test :asdf-inferred-components-test)) :components ((:file "package") (:file "file-wrappers" :depends-on ("package")) (:file "inferred-component-system" :depends-on ("package" "file-wrappers")) (:file "define-component" :depends-on ("package" "inferred-component-system")) (:file "define-package" :depends-on ("package" "inferred-component-system" "define-component")))) (defsystem #:asdf-inferred-components/test :version (:read-file-form "version.lisp-expr") :license "MIT" :pathname "test" :components ((:file "package") (:file "aic-test-1") (:file "aic-test-2")) :depends-on (#:alexandria #:parachute))
clpmfile 0 → 100644 +8 −0 Original line number Diff line number Diff line ;;; -*- mode: common-lisp -*- (:api-version "0.3") (:source "quicklisp" :type :ql-clpi :url "https://quicklisp.common-lisp-project-index.org/") (:asd "asdf-inferred-components.asd")