From: Gabor Melis
Subject: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307220100.1fa89300@posting.google.com>
I want to load a *compiled* file that defines a package named PACK
if it is not yet defined at load time. Unfortunately, conditionally
loading the compiled file is not an option.

The following code seems to work interpreted, but if compiled it makes
the decision too early.

#.(progn
    (when (find-package "PACK")
      (pushnew :pack *features*))
    nil)

#-pack
(defpackage "PACK"
  (:use "COMMON-LISP")
  (:export "FOO"
           "BAR"))

#-pack
(in-package "PACK")

#-pack
(progn
(defmacro mac (x)
  `(concatenate 'string ,x "-mac"))

(defun foo ()
  "foo")

(defun bar ()
  (mac "bar"))
)

The reader needs the package set at compile time to put 'foo into the
right package, but the package cannot be created at compile time. I
can see no way out of it. Maybe rename-package? Hmm.

(defpackage "PACK-TEMP"
  (:use "COMMON-LISP")
  (:export "FOO"
           "BAR"))

(in-package "PACK-TEMP")

(eval-when (:load-toplevel :execute)
  (if (find-package "PACK")
      (delete-package "PACK-TEMP")
      (progn
        (defmacro mac (x)
          `(concatenate 'string ,x "-mac"))
        (defun foo ()
          "foo")
        (defun bar ()
          (mac "bar"))
        (rename-package "PACK-TEMP" "PACK"))))

This one works even when compiled, but doesn't look very elegant.
Better solutions?

From: Kalle Olavi Niemitalo
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <87n0f6u9vx.fsf@Astalo.kon.iki.fi>
····@hotpop.com (Gabor Melis) writes:

> I want to load a *compiled* file that defines a package named PACK
> if it is not yet defined at load time. Unfortunately, conditionally
> loading the compiled file is not an option.

Why not?  Can you conditionally load some *other* file?

> The following code seems to work interpreted, but if compiled it makes
> the decision too early.

Right.  Reader tricks won't work.  I guess EVAL-WHEN is the key.
Let's see if I understood correctly:

- If the source file is loaded directly, and PACK is not already
  defined, then PACK, PACK::MAC, PACK:FOO and PACK:BAR should be
  defined.

- If the source file is loaded directly, and PACK is already
  defined, then nothing should happen.

- If the source file is compiled, then PACK should be defined so
  that PACK::MAC, PACK:FOO and PACK:BAR can be compiled.  Whether
  PACK was already defined at compile time should not affect the
  contents of the compiled file.

- If the compiled file is loaded, and PACK is not already
  defined, then PACK, PACK::MAC, PACK:FOO and PACK:BAR should be
  defined.

- If the compiled file is loaded, and PACK is already defined,
  then nothing should happen.

Is this right?
From: Gabor Melis
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307220636.5b1ff652@posting.google.com>
Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> Let's see if I understood correctly:
> 
> - If the source file is loaded directly, and PACK is not already
>   defined, then PACK, PACK::MAC, PACK:FOO and PACK:BAR should be
>   defined.
> 
> - If the source file is loaded directly, and PACK is already
>   defined, then nothing should happen.
> 
> - If the source file is compiled, then PACK should be defined so
>   that PACK::MAC, PACK:FOO and PACK:BAR can be compiled.  Whether
>   PACK was already defined at compile time should not affect the
>   contents of the compiled file.
> 
> - If the compiled file is loaded, and PACK is not already
>   defined, then PACK, PACK::MAC, PACK:FOO and PACK:BAR should be
>   defined.
> 
> - If the compiled file is loaded, and PACK is already defined,
>   then nothing should happen.
> 
> Is this right?

Exactly.
From: Kalle Olavi Niemitalo
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <87d6g2jvfp.fsf@Astalo.kon.iki.fi>
····@hotpop.com (Gabor Melis) writes:

> Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
>> Is this right?
>
> Exactly.

Well, here is a version that appears to work on CMUCL, but it is
far less elegant than your use of RENAME-PACKAGE.

;; This file defines the package PACK and some symbols in it.
;; However, if PACK is already defined, then nothing is done.
;;
;; The implementation has two parts.  The first part makes PACK and
;; selects it as the current package so that the second part can be
;; properly read.  The second part then defines the symbols if PACK
;; did not already exist.
;;
;; The first part passes this information to the second part via the
;; special variable PACK::*WAS-ALREADY-DEFINED*.  It cannot be a local
;; variable, because then the parts would have to be wrapped in the
;; same form, and the second part would be read too early.

;; At compile time, always define the package afresh.
(eval-when (:compile-toplevel)
  (defpackage "PACK"
    (:use "COMMON-LISP")
    (:export "FOO"
	     "BAR"))
  ;; This form can be read before the package is defined.
  (setf (symbol-value (intern "*WAS-ALREADY-DEFINED*" "PACK")) nil))

;; Otherwise, define the package only if it was not already defined.
(eval-when (:load-toplevel :execute)
  (cond ((find-package "PACK")
	 (setf (symbol-value (intern "*WAS-ALREADY-DEFINED*" "PACK")) t))
	(t
	 (defpackage "PACK"
	   (:use "COMMON-LISP")
	   (:export "FOO"
		    "BAR"))
	 (setf (symbol-value (intern "*WAS-ALREADY-DEFINED*" "PACK")) nil))))

;; The package has now been defined.
(in-package "PACK")
(declaim (special *was-already-defined*))

;; The second part follows.

(eval-when (:compile-toplevel :load-toplevel :execute)
  (unless *was-already-defined*
    (defmacro mac (x)
      `(concatenate 'string ,x "-mac"))))

(unless *was-already-defined*
  (defun foo ()
    "foo")

  (defun bar ()
    (mac "bar")))
From: Gabor Melis
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307230140.76d33e8b@posting.google.com>
Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> ····@hotpop.com (Gabor Melis) writes:
> 
> > Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> >> Is this right?
> >
> > Exactly.
> 
> Well, here is a version that appears to work on CMUCL, but it is
> far less elegant than your use of RENAME-PACKAGE.
> 
> ;; This file defines the package PACK and some symbols in it.
> ;; However, if PACK is already defined, then nothing is done.
> ;;
> ;; The implementation has two parts.  The first part makes PACK and
> ;; selects it as the current package so that the second part can be
> ;; properly read.  The second part then defines the symbols if PACK
> ;; did not already exist.
> ;;
> ;; The first part passes this information to the second part via the
> ;; special variable PACK::*WAS-ALREADY-DEFINED*.  It cannot be a local
> ;; variable, because then the parts would have to be wrapped in the
> ;; same form, and the second part would be read too early.
> 
> ;; At compile time, always define the package afresh.
> (eval-when (:compile-toplevel)
>   (defpackage "PACK"
>     (:use "COMMON-LISP")
>     (:export "FOO"
> 	     "BAR"))
>   ;; This form can be read before the package is defined.
>   (setf (symbol-value (intern "*WAS-ALREADY-DEFINED*" "PACK")) nil))
> 
> ;; Otherwise, define the package only if it was not already defined.
> (eval-when (:load-toplevel :execute)
>   (cond ((find-package "PACK")
> 	 (setf (symbol-value (intern "*WAS-ALREADY-DEFINED*" "PACK")) t))
> 	(t
> 	 (defpackage "PACK"
> 	   (:use "COMMON-LISP")
> 	   (:export "FOO"
> 		    "BAR"))
> 	 (setf (symbol-value (intern "*WAS-ALREADY-DEFINED*" "PACK")) nil))))
> 
> ;; The package has now been defined.
> (in-package "PACK")
> (declaim (special *was-already-defined*))
> 
> ;; The second part follows.
> 
> (eval-when (:compile-toplevel :load-toplevel :execute)
>   (unless *was-already-defined*
>     (defmacro mac (x)
>       `(concatenate 'string ,x "-mac"))))
> 
> (unless *was-already-defined*
>   (defun foo ()
>     "foo")
> 
>   (defun bar ()
>     (mac "bar")))

Oh, I see. I would have thought that "FOO" defined in the compile time
"PACK" could not be loaded because the load time "PACK" is another
object. But  obviously it does not work that way and in the compiled
file the symbol foo references its package by name. Maybe I need to
read more about compilation.

Anyway, thank you for the solution it solves the problem nicely.

What bugs me a bit is the "toplevelism" displayed by common lisp:
in-package must be a toplevel form for the reader to read into the
correct package, eval-when only has :compile-toplevel and
:load-toplevel, while :compile and :load would be handy as well.

Maybe, in general, a more democratic system (no special treatment for
toplevel forms) would be nice to have, since this whole problem could
have been solved by putting a big WHEN around the file, but that's a
different language and I guess this dictinction is not here without a
reason.

Gabor
From: Gabor Melis
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307220714.99a211e@posting.google.com>
Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> ····@hotpop.com (Gabor Melis) writes:
> 
> > I want to load a *compiled* file that defines a package named PACK
> > if it is not yet defined at load time. Unfortunately, conditionally
> > loading the compiled file is not an option.
> 
> Why not?  Can you conditionally load some *other* file?

Oops, I'm gonna break my self imposed word limit this time :-). The
program I am writing is a "configurator" called coma (from
COnfiguration MAnagement). It is much like autoconf in the sense that
the source tree of numerous packages include it (think configure). So
the user ends up with coma.sh, coma.lisp in his source tree. When a
new version of coma is released he may decide to upgrade to it. If the
newer version of coma contains more (or less) files then he needs to
manually communicate (:-) type it, really) the changes to the version
control system which is error prone.

Coma can compile itself if necessary and produce a set of fas files
that the user may decide to put under version control as well. Having
a constant set of fas files is desirable for exactly the same reasons
as above, so why not make it one?

I already put all sources from various files into one file
(coma-all.bundle) which is simple concatenation of sources from the
development tree with separator lines between them. This allows
coma.lisp to split the bundle into files and compile them if
necessary.

The same trick of bundling several fas files into one file is
possible, but it would be slower to unbundle and load than loading one
fas file. Startup time is very important here.

I hope that's enough background information.

Cheers, Gabor
From: Marco Antoniotti
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <3F1EABC6.8020205@cs.nyu.edu>
Shameless plug...


Gabor Melis wrote:
> Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> 
>>····@hotpop.com (Gabor Melis) writes:
>>
>>
>>>I want to load a *compiled* file that defines a package named PACK
>>>if it is not yet defined at load time. Unfortunately, conditionally
>>>loading the compiled file is not an option.
>>
>>Why not?  Can you conditionally load some *other* file?
> 
> 
> Oops, I'm gonna break my self imposed word limit this time :-). The
> program I am writing is a "configurator" called coma (from
> COnfiguration MAnagement). It is much like autoconf...

Have you looked at CL-CONFIGURATION in the CLOCC?  Same philosophy, no 
UNIX dependencies. 100% CL (well, almost :) ).

Cheers

--
Marco
From: Gabor Melis
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307240303.7438249d@posting.google.com>
Marco Antoniotti <·······@cs.nyu.edu> wrote in message news:<················@cs.nyu.edu>...
> Shameless plug...
> 
> 
> Gabor Melis wrote:
> > Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> > 
> >>····@hotpop.com (Gabor Melis) writes:
> >>
> >>
> >>>I want to load a *compiled* file that defines a package named PACK
> >>>if it is not yet defined at load time. Unfortunately, conditionally
> >>>loading the compiled file is not an option.
> >>
> >>Why not?  Can you conditionally load some *other* file?
> > 
> > 
> > Oops, I'm gonna break my self imposed word limit this time :-). The
> > program I am writing is a "configurator" called coma (from
> > COnfiguration MAnagement). It is much like autoconf...
> 
> Have you looked at CL-CONFIGURATION in the CLOCC?  Same philosophy, no 
> UNIX dependencies. 100% CL (well, almost :) ).
> 
> Cheers

Having overlooked CL-CONFIGURATION until now, I am releived to see
that it solves a different problem. Thanks for the pointer. I honour
the ancient tradition of plug for a plug.

Coma is intended to support the config mgmt process for a number of
products sharing common components.

It provides a uniform configuration interface for any kind of
configuration item. One of its main feautures is to define items with
parameters and "kits" (configurations really) that can fetch and
configure other items. Here is an example item definition:

(defitem simple-item
    (
     ;; debug mode, default false
     (debug nil)
     ;; produce compilation output suitable for emacs, default true
     (emacs t)
     ;; compilation flags
     flags)
  (:rule (:build)
         (member emacs '(t nil)))
  (:rule (:run)
         (member debug '(t nil)))
  (:config-files "out"))

It is a class definition behind the scenes with some rules and
config-files. Config files are generated from simple templates like
this (file out.coma):

······@·····@
······@(if emacs "true" "false")@
······@(concatenate 'string flags)@

And this is a kit description:

(simple-item
 ;; this value is passed to generic function fetch-item
 #S(hoc-module :name "simple-item" :version-or-tag #v0.0.0)
 ;; these settings are pushed onto the item
 ((debug t)
  (flags '("-O"))))

(other-item
 #S(hoc-module :name "simple-item" :version-or-tag #vMY-BRANCH:1.2.3)
 ((my-lib simple-item)))

Simple-item and other-item are fetched by the kit, then their
configuration modified according to bindings specified (debug, flags).
Notice that an object representing the whole configuration of
simple-item is passed as a value to other-item's my-lib slot.

Luckily it integrates nicely with our version control system (ho-cvs
or hoc) that was tailored to support it :-).
From: Joe Marshall
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <d6g2793c.fsf@ccs.neu.edu>
····@hotpop.com (Gabor Melis) writes:

> I want to load a *compiled* file that defines a package named PACK
> if it is not yet defined at load time. 

It is quite difficult to do this correctly, and it will create bizarre
dependencies among other files that use this package.  I would suggest
doing this in two phases:  one phase that sets up the package system
and places the symbols where they need to be, and the second phase
would be the compiling or loading of the compiled code.
From: Alexey Dejneka
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <m365lu7uwq.fsf@comail.ru>
····@hotpop.com (Gabor Melis) writes:

> (defpackage "PACK-TEMP"
>   (:use "COMMON-LISP")
>   (:export "FOO"
>            "BAR"))
> 
> (in-package "PACK-TEMP")
> 
> (eval-when (:load-toplevel :execute)
>   (if (find-package "PACK")
>       (delete-package "PACK-TEMP")
>       (progn
>         (defmacro mac (x)
>           `(concatenate 'string ,x "-mac"))
>         (defun foo ()
>           "foo")
>         (defun bar ()
>           (mac "bar"))
>         (rename-package "PACK-TEMP" "PACK"))))
> 
> This one works even when compiled

It works?! The form (DEFMACRO MAC ...) is not on top level, so MAC is
not defined in the compilation environment, form (MAC "BAR") will be
considered to be a call of a /function/ MAC, and in runtime, in the
best case, you'll get "call of undefined function MAC" error.

-- 
Regards,
Alexey Dejneka

"Alas, the spheres of truth are less transparent than those of
illusion." -- L.E.J. Brouwer
From: Gabor Melis
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307220635.1f18d85e@posting.google.com>
Alexey Dejneka <········@comail.ru> wrote in message news:<··············@comail.ru>...
> > 
> > This one works even when compiled
> 
> It works?! The form (DEFMACRO MAC ...) is not on top level, so MAC is
> not defined in the compilation environment, form (MAC "BAR") will be
> considered to be a call of a /function/ MAC, and in runtime, in the
> best case, you'll get "call of undefined function MAC" error.

Tested it with cmucl and it indeed doesn't work. But in clisp it does.
From: Christophe Rhodes
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <sqoezmaael.fsf@lambda.jcn.srcf.net>
····@hotpop.com (Gabor Melis) writes:

> Alexey Dejneka <········@comail.ru> wrote in message news:<··············@comail.ru>...
>> > 
>> > This one works even when compiled
>> 
>> It works?! The form (DEFMACRO MAC ...) is not on top level, so MAC is
>> not defined in the compilation environment, form (MAC "BAR") will be
>> considered to be a call of a /function/ MAC, and in runtime, in the
>> best case, you'll get "call of undefined function MAC" error.
>
> Tested it with cmucl and it indeed doesn't work. But in clisp it does.

clisp has issues with EVAL-WHEN not at toplevel.  Some of these issues
have been fixed in clisp's CVS.

Christophe
-- 
http://www-jcsu.jesus.cam.ac.uk/~csr21/       +44 1223 510 299/+44 7729 383 757
(set-pprint-dispatch 'number (lambda (s o) (declare (special b)) (format s b)))
(defvar b "~&Just another Lisp hacker~%")    (pprint #36rJesusCollegeCambridge)
From: Kalle Olavi Niemitalo
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <873cgxzg5y.fsf@Astalo.kon.iki.fi>
With RENAME-PACKAGE, I think this would be the easiest way:

  (defpackage "PACK-TEMP"
    (:use "COMMON-LISP")
    (:export "FOO"
             "BAR"))

  (in-package "PACK-TEMP")

  (defmacro mac (x)
    `(concatenate 'string ,x "-mac"))

  (defun foo ()
    "foo")

  (defun bar ()
    (mac "bar"))

  (if (find-package "PACK")
      (delete-package "PACK-TEMP")
      (rename-package "PACK-TEMP" "PACK"))

i.e., always define the macro and functions so that the DEFMACRO
form can be at top level, but then throw them all away if they
aren't needed.

Note that compiling this file defines PACK-TEMP, not PACK.  You
could change that with EVAL-WHEN.  Alternatively, change the last
top-level form to:

  (unless (find-package "PACK")
    (rename-package "PACK-TEMP" "PACK-TEMP" '("PACK")))

so that the package has one permanent name and PACK is just a
conditional nickname for it.  It would then make sense to change
"PACK-TEMP" to "PACK-DEFAULT", too.
From: Gabor Melis
Subject: Re: Defining a package conditionally in a compiled file
Date: 
Message-ID: <fb0fb805.0307230737.61dbf38e@posting.google.com>
Kalle Olavi Niemitalo <···@iki.fi> wrote in message news:<··············@Astalo.kon.iki.fi>...
> With RENAME-PACKAGE, I think this would be the easiest way:
> 
>   (defpackage "PACK-TEMP"
>     (:use "COMMON-LISP")
>     (:export "FOO"
>              "BAR"))
> 
>   (in-package "PACK-TEMP")
> 
>   (defmacro mac (x)
>     `(concatenate 'string ,x "-mac"))
> 
>   (defun foo ()
>     "foo")
> 
>   (defun bar ()
>     (mac "bar"))
> 
>   (if (find-package "PACK")
>       (delete-package "PACK-TEMP")
>       (rename-package "PACK-TEMP" "PACK"))
> 
> i.e., always define the macro and functions so that the DEFMACRO
> form can be at top level, but then throw them all away if they
> aren't needed.
> 
> Note that compiling this file defines PACK-TEMP, not PACK.  You
> could change that with EVAL-WHEN.  Alternatively, change the last
> top-level form to:
> 
>   (unless (find-package "PACK")
>     (rename-package "PACK-TEMP" "PACK-TEMP" '("PACK")))
> 
> so that the package has one permanent name and PACK is just a
> conditional nickname for it.  It would then make sense to change
> "PACK-TEMP" to "PACK-DEFAULT", too.

This one looks very pretty, no clutter at all. Thank you, again.