Merge lp:~gary/zc.buildout/python-support-4 into lp:zc.buildout
- python-support-4
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~gary/zc.buildout/python-support-4 |
Merge into: | lp:zc.buildout |
Prerequisite: | lp:~gary/zc.buildout/python-support-3-options |
Diff against target: |
3435 lines (+2506/-195) 26 files modified
CHANGES.txt (+32/-0) README.txt (+9/-1) buildout.cfg (+3/-1) setup.py (+1/-1) src/zc/buildout/bootstrap.txt (+3/-3) src/zc/buildout/easy_install.py (+523/-107) src/zc/buildout/easy_install.txt (+587/-23) src/zc/buildout/testing.py (+86/-22) src/zc/buildout/tests.py (+285/-0) src/zc/buildout/testselectingpython.py (+28/-1) src/zc/buildout/update.txt (+1/-0) z3c.recipe.scripts_/CHANGES.txt (+7/-0) z3c.recipe.scripts_/README.txt (+10/-0) z3c.recipe.scripts_/setup.py (+76/-0) z3c.recipe.scripts_/src/z3c/__init__.py (+1/-0) z3c.recipe.scripts_/src/z3c/recipe/__init__.py (+1/-0) z3c.recipe.scripts_/src/z3c/recipe/scripts/README.txt (+402/-0) z3c.recipe.scripts_/src/z3c/recipe/scripts/__init__.py (+1/-0) z3c.recipe.scripts_/src/z3c/recipe/scripts/scripts.py (+101/-0) z3c.recipe.scripts_/src/z3c/recipe/scripts/tests.py (+293/-0) zc.recipe.egg_/setup.py (+2/-2) zc.recipe.egg_/src/zc/recipe/egg/README.txt (+4/-1) zc.recipe.egg_/src/zc/recipe/egg/api.txt (+1/-0) zc.recipe.egg_/src/zc/recipe/egg/custom.txt (+5/-0) zc.recipe.egg_/src/zc/recipe/egg/egg.py (+42/-28) zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt (+2/-5) |
To merge this branch: | bzr merge lp:~gary/zc.buildout/python-support-4 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis J. Lacoste (community) | Approve | ||
Review via email: mp+19547@code.launchpad.net |
Commit message
Description of the change
Gary Poster (gary) wrote : | # |
Francis J. Lacoste (flacoste) wrote : | # |
Hi Gary,
Wow, this was a big one. I have a bunch of comments, mostly about
clarifications and typos.
But good to merge otherwise as far as Launchpad is concerned.
Cheers
> === modified file 'CHANGES.txt'
> --- CHANGES.txt 2010-02-17 22:17:12 +0000
> +++ CHANGES.txt 2010-02-17 22:17:12 +0000
> @@ -6,6 +6,25 @@
>
> New Features:
>
> +- Buildout can be safely used with a system Python, as long as you use the
> + new z3c.recipe.scripts recipe to generate scripts and interpreters, rather
> + than zc.recipe.egg (which is still a fully supported, and simpler, way of
> + generating scripts and interpreters if you are using a "clean" Python).
Really, I tohugh zc.recipe.egg would also do the right thing. Can you explain
the differences to me?
> +
> + A hopefully slight limitation: in no cases are distributions in your
> + site-packages used to satisfy buildout dependencies. The
> + site-packages can be used in addition to the dependencies specified in
> + your buildout, and buildout dependencies can override code in your
> + site-packages, but even if your Python's site-packages has the same
> + exact version as specified in your buildout configuration, buildout
> + will still use its own copy.
I assume dependencies related to zc.buildout itself like distribute,
and setuptools. Not general dependencies managed by buildout, right?
>
> +- Installing a namespace package using a Python that already has a package
> + in the same namespace (e.g., in the Python's site-packages) failed in
> + some cases.
> +
I assume you mean this is fixed. (IOW, that is in a fixed bugs section?)
> +- Another variation of this error showed itself when at least two
> + dependencies were in a shared location like site-packages, and the
> + first one met the "versions" setting. The first dependency would be
> + added, but subsequent dependencies from the same location (e.g.,
> + site-packages) would use the version of the package found in the
> + shared location, ignoring the version setting.
> +
I assume the same thing here.
> === modified file 'README.txt'
> --- README.txt 2010-02-17 22:17:12 +0000
> +++ README.txt 2010-02-17 22:17:12 +0000
> @@ -37,6 +37,11 @@
> dependencies. It installs their console-script entry points with
> the needed eggs included in their paths.
>
> +`z3c.recipe.
> + This scripts recipe builds interpreter scripts and entry point scripts
> + based on eggs. These scripts have more features and flexibility than the
> + ones offered by zc.recipe.egg.
> +
Again, might be worthwhile to go into more details of when one should be used
instead of the other.
=== modified file 'src/zc/
--- src/zc/
+++ src/zc/
@@ -137,9 +137,50 @@
else:
_safe_arg = str
-_easy_install_cmd = _safe_arg(
- 'from setuptools.
- )
+# The following string is used to run easy_install in
+# Installer.
+# don't import site at start). That flag, a...
- 550. By Gary Poster
-
merge from gary-3
- 551. By Gary Poster
-
switch ``generate_
scripts` ` to ``sitepackage_ safe_scripts` ` per review - 552. By Gary Poster
-
add missing reset_interpreter calls
- 553. By Gary Poster
-
change the way that we identify namespace packages and better comment the code, per review.
- 554. By Gary Poster
-
doc fixes per review.
- 555. By Gary Poster
-
try again to describe the change.
- 556. By Gary Poster
-
fix docstring
Gary Poster (gary) wrote : | # |
Thank you, Francis!
Francis and I talked about the review. On the basis of his comments and the discussion, I did the revisions listed above as well as two further branches: https:/
I'll respond to his comments point by point now.
> === modified file 'CHANGES.txt'
> --- CHANGES.txt 2010-02-17 22:17:12 +0000
> +++ CHANGES.txt 2010-02-17 22:17:12 +0000
> @@ -6,6 +6,25 @@
>
> New Features:
>
> +- Buildout can be safely used with a system Python, as long as you use the
> + new z3c.recipe.scripts recipe to generate scripts and interpreters, rather
> + than zc.recipe.egg (which is still a fully supported, and simpler, way of
> + generating scripts and interpreters if you are using a "clean" Python).
>
> Really, I tohugh zc.recipe.egg would also do the right thing. Can you explain
> the differences to me?
We discussed this. He meant that he wanted the CHANGES document to more clearly describe the differences. I tried this:
- Buildout can be safely used with a system Python (or any Python with code
in site-packages), as long as you use the new z3c.recipe.scripts
recipe to generate scripts and interpreters, rather than zc.recipe.egg.
zc.recipe.egg is still a fully supported, and simpler, way of
generating scripts and interpreters if you are using a "clean" Python,
without code installed in site-packages. It keeps its previous behavior in
order to provide backwards compatibility.
>
> +
> + A hopefully slight limitation: in no cases are distributions in your
> + site-packages used to satisfy buildout dependencies. The
> + site-packages can be used in addition to the dependencies specified in
> + your buildout, and buildout dependencies can override code in your
> + site-packages, but even if your Python's site-packages has the same
> + exact version as specified in your buildout configuration, buildout
> + will still use its own copy.
>
> I assume dependencies related to zc.buildout itself like distribute,
> and setuptools. Not general dependencies managed by buildout, right?
My comment was wrong--and the actual situation turned out to be a big problem, leading to the two subsequent branches I mentioned above.
The actual situation was that dependencies would be obtained from site-packages--even if you said you didn't want to use site-packages. This is completely broken for the use case of wanting to use a system Python without site-packages.
I fixed this in the later branches.
The later branches mean that one can choose to get general dependencies from site-packages, or not.
> +- Installing a namespace package using a Python that already has a package
> + in the same namespace (e.g., in the Python's site-packages) failed in
> + some cases.
> +
>
> I assume you mean this is fixed. (IOW, that is in a fixed bugs section?)
Right.
> +- Another variation of this error showed itself when at least two
> + dependencies were in a shared location like site-packages, and the
> + first one met the "versions" setting. The first dependency would be
> + ad...
Unmerged revisions
- 556. By Gary Poster
-
fix docstring
- 555. By Gary Poster
-
try again to describe the change.
- 554. By Gary Poster
-
doc fixes per review.
- 553. By Gary Poster
-
change the way that we identify namespace packages and better comment the code, per review.
- 552. By Gary Poster
-
add missing reset_interpreter calls
- 551. By Gary Poster
-
switch ``generate_
scripts` ` to ``sitepackage_ safe_scripts` ` per review - 550. By Gary Poster
-
merge from gary-3
- 549. By Gary Poster
-
propagate merge from gary-3 <- gary-2 <- gary-1 <- trunk
- 548. By gary
-
fix some tests on other Python versions
- 547. By gary
-
revert attempt to skip some of the pkg_resources dance: it caused me trouble.
Preview Diff
1 | === modified file 'CHANGES.txt' | |||
2 | --- CHANGES.txt 2010-02-23 20:41:17 +0000 | |||
3 | +++ CHANGES.txt 2010-02-23 20:41:17 +0000 | |||
4 | @@ -6,6 +6,27 @@ | |||
5 | 6 | 6 | ||
6 | 7 | New Features: | 7 | New Features: |
7 | 8 | 8 | ||
8 | 9 | - Buildout can be safely used with a system Python (or any Python with code | ||
9 | 10 | in site-packages), as long as you use the new z3c.recipe.scripts | ||
10 | 11 | recipe to generate scripts and interpreters, rather than zc.recipe.egg. | ||
11 | 12 | |||
12 | 13 | zc.recipe.egg is still a fully supported, and simpler, way of | ||
13 | 14 | generating scripts and interpreters if you are using a "clean" Python, | ||
14 | 15 | without code installed in site-packages. It keeps its previous behavior in | ||
15 | 16 | order to provide backwards compatibility. | ||
16 | 17 | |||
17 | 18 | (Note that this branch is incomplete in its implementation of this feature: | ||
18 | 19 | if eggs are in installed in site-packages but you do not want to use | ||
19 | 20 | site-packages, the eggs will drag in site-packages even if you try to | ||
20 | 21 | exclude it. This is addressed in subsequent branches in the series of | ||
21 | 22 | which this one is a part.) | ||
22 | 23 | |||
23 | 24 | - Added new function, ``zc.buildout.easy_install.sitepackage_safe_scripts``, | ||
24 | 25 | to generate scripts and interpreter. It produces a full-featured | ||
25 | 26 | interpreter (all command-line options supported) and the ability to | ||
26 | 27 | safely let scripts include site packages, such as with a system | ||
27 | 28 | Python. The ``z3c.recipe.scripts`` recipe uses this new function. | ||
28 | 29 | |||
29 | 9 | - Improve bootstrap. | 30 | - Improve bootstrap. |
30 | 10 | 31 | ||
31 | 11 | * New options let you specify where to find ez_setup.py and where to find | 32 | * New options let you specify where to find ez_setup.py and where to find |
32 | @@ -23,6 +44,17 @@ | |||
33 | 23 | This means, among other things, that ``bin/buildout -vv`` and | 44 | This means, among other things, that ``bin/buildout -vv`` and |
34 | 24 | ``bin/buildout annotate`` correctly list more of the options. | 45 | ``bin/buildout annotate`` correctly list more of the options. |
35 | 25 | 46 | ||
36 | 47 | - Installing a namespace package using a Python that already has a package | ||
37 | 48 | in the same namespace (e.g., in the Python's site-packages) failed in | ||
38 | 49 | some cases. | ||
39 | 50 | |||
40 | 51 | - Another variation of this error showed itself when at least two | ||
41 | 52 | dependencies were in a shared location like site-packages, and the | ||
42 | 53 | first one met the "versions" setting. The first dependency would be | ||
43 | 54 | added, but subsequent dependencies from the same location (e.g., | ||
44 | 55 | site-packages) would use the version of the package found in the | ||
45 | 56 | shared location, ignoring the version setting. | ||
46 | 57 | |||
47 | 26 | 1.4.3 (2009-12-10) | 58 | 1.4.3 (2009-12-10) |
48 | 27 | ================== | 59 | ================== |
49 | 28 | 60 | ||
50 | 29 | 61 | ||
51 | === modified file 'README.txt' | |||
52 | --- README.txt 2010-02-23 20:41:17 +0000 | |||
53 | +++ README.txt 2010-02-23 20:41:17 +0000 | |||
54 | @@ -35,7 +35,15 @@ | |||
55 | 35 | `zc.recipe.egg <http://pypi.python.org/pypi/zc.recipe.egg>`_ | 35 | `zc.recipe.egg <http://pypi.python.org/pypi/zc.recipe.egg>`_ |
56 | 36 | The egg recipe installes one or more eggs, with their | 36 | The egg recipe installes one or more eggs, with their |
57 | 37 | dependencies. It installs their console-script entry points with | 37 | dependencies. It installs their console-script entry points with |
59 | 38 | the needed eggs included in their paths. | 38 | the needed eggs included in their paths. It is suitable for use with |
60 | 39 | a "clean" Python: one without packages installed in site-packages. | ||
61 | 40 | |||
62 | 41 | `z3c.recipe.scripts <http://pypi.python.org/pypi/z3c.recipe.scripts>`_ | ||
63 | 42 | Like zc.recipe.egg, this recipe builds interpreter scripts and entry | ||
64 | 43 | point scripts based on eggs. It can be used with a Python that has | ||
65 | 44 | packages installed in site-packages, such as a system Python. The | ||
66 | 45 | interpreter also has more features than the one offered by | ||
67 | 46 | zc.recipe.egg. | ||
68 | 39 | 47 | ||
69 | 40 | `zc.recipe.testrunner <http://pypi.python.org/pypi/zc.recipe.testrunner>`_ | 48 | `zc.recipe.testrunner <http://pypi.python.org/pypi/zc.recipe.testrunner>`_ |
70 | 41 | The testrunner egg creates a test runner script for one or | 49 | The testrunner egg creates a test runner script for one or |
71 | 42 | 50 | ||
72 | === modified file 'buildout.cfg' | |||
73 | --- buildout.cfg 2009-10-23 08:25:24 +0000 | |||
74 | +++ buildout.cfg 2010-02-23 20:41:17 +0000 | |||
75 | @@ -1,5 +1,5 @@ | |||
76 | 1 | [buildout] | 1 | [buildout] |
78 | 2 | develop = zc.recipe.egg_ . | 2 | develop = zc.recipe.egg_ z3c.recipe.scripts_ . |
79 | 3 | parts = test oltest py | 3 | parts = test oltest py |
80 | 4 | 4 | ||
81 | 5 | [py] | 5 | [py] |
82 | @@ -13,6 +13,7 @@ | |||
83 | 13 | eggs = | 13 | eggs = |
84 | 14 | zc.buildout | 14 | zc.buildout |
85 | 15 | zc.recipe.egg | 15 | zc.recipe.egg |
86 | 16 | z3c.recipe.scripts | ||
87 | 16 | 17 | ||
88 | 17 | # Tests that can be run wo a network | 18 | # Tests that can be run wo a network |
89 | 18 | [oltest] | 19 | [oltest] |
90 | @@ -20,6 +21,7 @@ | |||
91 | 20 | eggs = | 21 | eggs = |
92 | 21 | zc.buildout | 22 | zc.buildout |
93 | 22 | zc.recipe.egg | 23 | zc.recipe.egg |
94 | 24 | z3c.recipe.scripts | ||
95 | 23 | defaults = | 25 | defaults = |
96 | 24 | [ | 26 | [ |
97 | 25 | '-t', | 27 | '-t', |
98 | 26 | 28 | ||
99 | === modified file 'setup.py' | |||
100 | --- setup.py 2009-12-10 16:19:55 +0000 | |||
101 | +++ setup.py 2010-02-23 20:41:17 +0000 | |||
102 | @@ -12,7 +12,7 @@ | |||
103 | 12 | # | 12 | # |
104 | 13 | ############################################################################## | 13 | ############################################################################## |
105 | 14 | name = "zc.buildout" | 14 | name = "zc.buildout" |
107 | 15 | version = "1.4.4dev" | 15 | version = "1.5.0dev" |
108 | 16 | 16 | ||
109 | 17 | import os | 17 | import os |
110 | 18 | from setuptools import setup | 18 | from setuptools import setup |
111 | 19 | 19 | ||
112 | === modified file 'src/zc/buildout/bootstrap.txt' | |||
113 | --- src/zc/buildout/bootstrap.txt 2010-02-23 20:41:17 +0000 | |||
114 | +++ src/zc/buildout/bootstrap.txt 2010-02-23 20:41:17 +0000 | |||
115 | @@ -232,8 +232,8 @@ | |||
116 | 232 | >>> print system( | 232 | >>> print system( |
117 | 233 | ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ | 233 | ... zc.buildout.easy_install._safe_arg(sys.executable)+' '+ |
118 | 234 | ... 'bootstrap.py --help'), | 234 | ... 'bootstrap.py --help'), |
121 | 235 | ... # doctest: +ELLIPSIS | 235 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE |
122 | 236 | usage: [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] | 236 | Usage: [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] |
123 | 237 | <BLANKLINE> | 237 | <BLANKLINE> |
124 | 238 | Bootstraps a buildout-based project. | 238 | Bootstraps a buildout-based project. |
125 | 239 | <BLANKLINE> | 239 | <BLANKLINE> |
126 | @@ -244,7 +244,7 @@ | |||
127 | 244 | local resources, you can keep this script from going over the network. | 244 | local resources, you can keep this script from going over the network. |
128 | 245 | <BLANKLINE> | 245 | <BLANKLINE> |
129 | 246 | <BLANKLINE> | 246 | <BLANKLINE> |
131 | 247 | options: | 247 | Options: |
132 | 248 | -h, --help show this help message and exit | 248 | -h, --help show this help message and exit |
133 | 249 | -v VERSION, --version=VERSION | 249 | -v VERSION, --version=VERSION |
134 | 250 | use a specific zc.buildout version | 250 | use a specific zc.buildout version |
135 | 251 | 251 | ||
136 | === modified file 'src/zc/buildout/easy_install.py' | |||
137 | --- src/zc/buildout/easy_install.py 2010-02-23 20:41:17 +0000 | |||
138 | +++ src/zc/buildout/easy_install.py 2010-02-23 20:41:17 +0000 | |||
139 | @@ -60,12 +60,13 @@ | |||
140 | 60 | pkg_resources.Requirement.parse('setuptools') | 60 | pkg_resources.Requirement.parse('setuptools') |
141 | 61 | ).location | 61 | ).location |
142 | 62 | 62 | ||
149 | 63 | # Include buildout and setuptools eggs in paths | 63 | # Include buildout and setuptools eggs in paths. We prevent dupes just to |
150 | 64 | buildout_and_setuptools_path = [ | 64 | # keep from duplicating any log messages about them. |
151 | 65 | setuptools_loc, | 65 | buildout_loc = pkg_resources.working_set.find( |
152 | 66 | pkg_resources.working_set.find( | 66 | pkg_resources.Requirement.parse('zc.buildout')).location |
153 | 67 | pkg_resources.Requirement.parse('zc.buildout')).location, | 67 | buildout_and_setuptools_path = [setuptools_loc] |
154 | 68 | ] | 68 | if os.path.normpath(setuptools_loc) != os.path.normpath(buildout_loc): |
155 | 69 | buildout_and_setuptools_path.append(buildout_loc) | ||
156 | 69 | 70 | ||
157 | 70 | 71 | ||
158 | 71 | class IncompatibleVersionError(zc.buildout.UserError): | 72 | class IncompatibleVersionError(zc.buildout.UserError): |
159 | @@ -137,9 +138,73 @@ | |||
160 | 137 | else: | 138 | else: |
161 | 138 | _safe_arg = str | 139 | _safe_arg = str |
162 | 139 | 140 | ||
166 | 140 | _easy_install_cmd = _safe_arg( | 141 | # The following string is used to run easy_install in |
167 | 141 | 'from setuptools.command.easy_install import main; main()' | 142 | # Installer._call_easy_install. It is started with python -S (that is, |
168 | 142 | ) | 143 | # don't import site at start). That flag, and all of the code in this |
169 | 144 | # snippet above the last two lines, exist to work around a relatively rare | ||
170 | 145 | # problem. If | ||
171 | 146 | # | ||
172 | 147 | # - your buildout configuration is trying to install a package that is within | ||
173 | 148 | # a namespace package, and | ||
174 | 149 | # | ||
175 | 150 | # - you use a Python that has a different version of this package | ||
176 | 151 | # installed in in its site-packages using | ||
177 | 152 | # --single-version-externally-managed (that is, using the mechanism | ||
178 | 153 | # sometimes used by system packagers: | ||
179 | 154 | # http://peak.telecommunity.com/DevCenter/setuptools#install-command ), and | ||
180 | 155 | # | ||
181 | 156 | # - the new package tries to do sys.path tricks in the setup.py to get a | ||
182 | 157 | # __version__, | ||
183 | 158 | # | ||
184 | 159 | # then the older package will be loaded first, making the setup version | ||
185 | 160 | # the wrong number. While very arguably packages simply shouldn't do | ||
186 | 161 | # the sys.path tricks, some do, and we don't want buildout to fall over | ||
187 | 162 | # when they do. | ||
188 | 163 | # | ||
189 | 164 | # The namespace packages installed in site-packages with | ||
190 | 165 | # --single-version-externally-managed use a mechanism that cause them to | ||
191 | 166 | # be processed when site.py is imported (see | ||
192 | 167 | # http://mail.python.org/pipermail/distutils-sig/2009-May/011730.html | ||
193 | 168 | # for another description of the problem). Simply starting Python with | ||
194 | 169 | # -S addresses the problem in Python 2.4 and 2.5, but Python 2.6's | ||
195 | 170 | # distutils imports a value from the site module, so we unfortunately | ||
196 | 171 | # have to do more drastic surgery in the _easy_install_cmd code below. | ||
197 | 172 | # | ||
198 | 173 | # Here's an example of the .pth files created by setuptools when using that | ||
199 | 174 | # flag: | ||
200 | 175 | # | ||
201 | 176 | # import sys,new,os; | ||
202 | 177 | # p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('<NAMESPACE>',)); | ||
203 | 178 | # ie = os.path.exists(os.path.join(p,'__init__.py')); | ||
204 | 179 | # m = not ie and sys.modules.setdefault('<NAMESPACE>',new.module('<NAMESPACE>')); | ||
205 | 180 | # mp = (m or []) and m.__dict__.setdefault('__path__',[]); | ||
206 | 181 | # (p not in mp) and mp.append(p) | ||
207 | 182 | # | ||
208 | 183 | # The code, below, then, runs under -S, indicating that site.py should | ||
209 | 184 | # not be loaded initially. It gets the initial sys.path under these | ||
210 | 185 | # circumstances, and then imports site (because Python 2.6's distutils | ||
211 | 186 | # will want it, as mentioned above). It then reinstates the old sys.path | ||
212 | 187 | # value. Then it removes namespace packages (created by the setuptools | ||
213 | 188 | # code above) from sys.modules. It identifies namespace packages by | ||
214 | 189 | # iterating over every loaded module. It first looks if there is a | ||
215 | 190 | # __path__, so it is a package; and then it sees if that __path__ does | ||
216 | 191 | # not have an __init__.py. (Note that PEP 382, | ||
217 | 192 | # http://www.python.org/dev/peps/pep-0382, makes it possible to have a | ||
218 | 193 | # namespace package that has an __init__.py, but also should make it | ||
219 | 194 | # unnecessary for site.py to preprocess these packages, so it should be | ||
220 | 195 | # fine, as far as can be guessed as of this writing.) Finally, it | ||
221 | 196 | # imports easy_install and runs it. | ||
222 | 197 | |||
223 | 198 | _easy_install_cmd = _safe_arg('''\ | ||
224 | 199 | import sys,os;\ | ||
225 | 200 | p = sys.path[:];\ | ||
226 | 201 | import site;\ | ||
227 | 202 | sys.path[:] = p;\ | ||
228 | 203 | [sys.modules.pop(k) for k, v in sys.modules.items()\ | ||
229 | 204 | if hasattr(v, '__path__') and len(v.__path__)==1 and\ | ||
230 | 205 | not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))];\ | ||
231 | 206 | from setuptools.command.easy_install import main;\ | ||
232 | 207 | main()''') | ||
233 | 143 | 208 | ||
234 | 144 | 209 | ||
235 | 145 | class Installer: | 210 | class Installer: |
236 | @@ -301,7 +366,7 @@ | |||
237 | 301 | try: | 366 | try: |
238 | 302 | path = setuptools_loc | 367 | path = setuptools_loc |
239 | 303 | 368 | ||
241 | 304 | args = ('-c', _easy_install_cmd, '-mUNxd', _safe_arg(tmp)) | 369 | args = ('-Sc', _easy_install_cmd, '-mUNxd', _safe_arg(tmp)) |
242 | 305 | if self._always_unzip: | 370 | if self._always_unzip: |
243 | 306 | args += ('-Z', ) | 371 | args += ('-Z', ) |
244 | 307 | level = logger.getEffectiveLevel() | 372 | level = logger.getEffectiveLevel() |
245 | @@ -904,6 +969,9 @@ | |||
246 | 904 | def working_set(specs, executable, path): | 969 | def working_set(specs, executable, path): |
247 | 905 | return install(specs, None, executable=executable, path=path) | 970 | return install(specs, None, executable=executable, path=path) |
248 | 906 | 971 | ||
249 | 972 | ############################################################################ | ||
250 | 973 | # Script generation functions | ||
251 | 974 | |||
252 | 907 | def scripts(reqs, working_set, executable, dest, | 975 | def scripts(reqs, working_set, executable, dest, |
253 | 908 | scripts=None, | 976 | scripts=None, |
254 | 909 | extra_paths=(), | 977 | extra_paths=(), |
255 | @@ -912,20 +980,86 @@ | |||
256 | 912 | initialization='', | 980 | initialization='', |
257 | 913 | relative_paths=False, | 981 | relative_paths=False, |
258 | 914 | ): | 982 | ): |
260 | 915 | 983 | """Generate scripts and/or an interpreter. | |
261 | 984 | |||
262 | 985 | See sitepackage_safe_scripts for a version that can be used with a Python | ||
263 | 986 | that has code installed in site-packages. It has more options and a | ||
264 | 987 | different approach. | ||
265 | 988 | """ | ||
266 | 989 | path = _get_path(working_set, extra_paths) | ||
267 | 990 | if initialization: | ||
268 | 991 | initialization = '\n'+initialization+'\n' | ||
269 | 992 | generated = _generate_scripts( | ||
270 | 993 | reqs, working_set, dest, path, scripts, relative_paths, | ||
271 | 994 | initialization, executable, arguments) | ||
272 | 995 | if interpreter: | ||
273 | 996 | sname = os.path.join(dest, interpreter) | ||
274 | 997 | spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths) | ||
275 | 998 | generated.extend( | ||
276 | 999 | _pyscript(spath, sname, executable, rpsetup)) | ||
277 | 1000 | return generated | ||
278 | 1001 | |||
279 | 1002 | def sitepackage_safe_scripts( | ||
280 | 1003 | dest, working_set, executable, site_py_dest, | ||
281 | 1004 | reqs=(), scripts=None, interpreter=None, extra_paths=(), | ||
282 | 1005 | initialization='', add_site_packages=False, exec_sitecustomize=False, | ||
283 | 1006 | relative_paths=False, script_arguments='', script_initialization=''): | ||
284 | 1007 | """Generate scripts and/or an interpreter from a system Python. | ||
285 | 1008 | |||
286 | 1009 | This accomplishes the same job as the ``scripts`` function, above, | ||
287 | 1010 | but it does so in an alternative way that allows safely including | ||
288 | 1011 | Python site packages, if desired, and choosing to execute the Python's | ||
289 | 1012 | sitecustomize. | ||
290 | 1013 | """ | ||
291 | 1014 | generated = [] | ||
292 | 1015 | generated.append(_generate_sitecustomize( | ||
293 | 1016 | site_py_dest, executable, initialization, exec_sitecustomize)) | ||
294 | 1017 | generated.append(_generate_site( | ||
295 | 1018 | site_py_dest, working_set, executable, extra_paths, | ||
296 | 1019 | add_site_packages, relative_paths)) | ||
297 | 1020 | script_initialization = ( | ||
298 | 1021 | '\nimport site # imports custom buildout-generated site.py\n%s' % ( | ||
299 | 1022 | script_initialization,)) | ||
300 | 1023 | if not script_initialization.endswith('\n'): | ||
301 | 1024 | script_initialization += '\n' | ||
302 | 1025 | generated.extend(_generate_scripts( | ||
303 | 1026 | reqs, working_set, dest, [site_py_dest], scripts, relative_paths, | ||
304 | 1027 | script_initialization, executable, script_arguments, block_site=True)) | ||
305 | 1028 | if interpreter: | ||
306 | 1029 | generated.extend(_generate_interpreter( | ||
307 | 1030 | interpreter, dest, executable, site_py_dest, relative_paths)) | ||
308 | 1031 | return generated | ||
309 | 1032 | |||
310 | 1033 | # Utilities for the script generation functions. | ||
311 | 1034 | |||
312 | 1035 | # These are shared by both ``scripts`` and ``sitepackage_safe_scripts`` | ||
313 | 1036 | |||
314 | 1037 | def _get_path(working_set, extra_paths=()): | ||
315 | 1038 | """Given working set and extra paths, return a normalized path list.""" | ||
316 | 916 | path = [dist.location for dist in working_set] | 1039 | path = [dist.location for dist in working_set] |
317 | 917 | path.extend(extra_paths) | 1040 | path.extend(extra_paths) |
322 | 918 | path = map(realpath, path) | 1041 | return map(realpath, path) |
323 | 919 | 1042 | ||
324 | 920 | generated = [] | 1043 | def _generate_scripts(reqs, working_set, dest, path, scripts, relative_paths, |
325 | 921 | 1044 | initialization, executable, arguments, | |
326 | 1045 | block_site=False): | ||
327 | 1046 | """Generate scripts for the given requirements. | ||
328 | 1047 | |||
329 | 1048 | - reqs is an iterable of string requirements or entry points. | ||
330 | 1049 | - The requirements must be findable in the given working_set. | ||
331 | 1050 | - The dest is the directory in which the scripts should be created. | ||
332 | 1051 | - The path is a list of paths that should be added to sys.path. | ||
333 | 1052 | - The scripts is an optional dictionary. If included, the keys should be | ||
334 | 1053 | the names of the scripts that should be created, as identified in their | ||
335 | 1054 | entry points; and the values should be the name the script should | ||
336 | 1055 | actually be created with. | ||
337 | 1056 | - relative_paths, if given, should be the path that is the root of the | ||
338 | 1057 | buildout (the common path that should be the root of what is relative). | ||
339 | 1058 | """ | ||
340 | 922 | if isinstance(reqs, str): | 1059 | if isinstance(reqs, str): |
341 | 923 | raise TypeError('Expected iterable of requirements or entry points,' | 1060 | raise TypeError('Expected iterable of requirements or entry points,' |
342 | 924 | ' got string.') | 1061 | ' got string.') |
347 | 925 | 1062 | generated = [] | |
344 | 926 | if initialization: | ||
345 | 927 | initialization = '\n'+initialization+'\n' | ||
346 | 928 | |||
348 | 929 | entry_points = [] | 1063 | entry_points = [] |
349 | 930 | for req in reqs: | 1064 | for req in reqs: |
350 | 931 | if isinstance(req, str): | 1065 | if isinstance(req, str): |
351 | @@ -939,7 +1073,6 @@ | |||
352 | 939 | ) | 1073 | ) |
353 | 940 | else: | 1074 | else: |
354 | 941 | entry_points.append(req) | 1075 | entry_points.append(req) |
355 | 942 | |||
356 | 943 | for name, module_name, attrs in entry_points: | 1076 | for name, module_name, attrs in entry_points: |
357 | 944 | if scripts is not None: | 1077 | if scripts is not None: |
358 | 945 | sname = scripts.get(name) | 1078 | sname = scripts.get(name) |
359 | @@ -947,40 +1080,51 @@ | |||
360 | 947 | continue | 1080 | continue |
361 | 948 | else: | 1081 | else: |
362 | 949 | sname = name | 1082 | sname = name |
363 | 950 | |||
364 | 951 | sname = os.path.join(dest, sname) | 1083 | sname = os.path.join(dest, sname) |
365 | 952 | spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths) | 1084 | spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths) |
366 | 953 | |||
367 | 954 | generated.extend( | 1085 | generated.extend( |
377 | 955 | _script(module_name, attrs, spath, sname, executable, arguments, | 1086 | _script(sname, executable, rpsetup, spath, initialization, |
378 | 956 | initialization, rpsetup) | 1087 | module_name, attrs, arguments, block_site=block_site)) |
370 | 957 | ) | ||
371 | 958 | |||
372 | 959 | if interpreter: | ||
373 | 960 | sname = os.path.join(dest, interpreter) | ||
374 | 961 | spath, rpsetup = _relative_path_and_setup(sname, path, relative_paths) | ||
375 | 962 | generated.extend(_pyscript(spath, sname, executable, rpsetup)) | ||
376 | 963 | |||
379 | 964 | return generated | 1088 | return generated |
380 | 965 | 1089 | ||
382 | 966 | def _relative_path_and_setup(sname, path, relative_paths): | 1090 | def _relative_path_and_setup(sname, path, |
383 | 1091 | relative_paths=False, indent_level=1, | ||
384 | 1092 | omit_os_import=False): | ||
385 | 1093 | """Return a string of code of paths and of setup if appropriate. | ||
386 | 1094 | |||
387 | 1095 | - sname is the full path to the script name to be created. | ||
388 | 1096 | - path is the list of paths to be added to sys.path. | ||
389 | 1097 | - relative_paths, if given, should be the path that is the root of the | ||
390 | 1098 | buildout (the common path that should be the root of what is relative). | ||
391 | 1099 | - indent_level is the number of four-space indents that the path should | ||
392 | 1100 | insert before each element of the path. | ||
393 | 1101 | """ | ||
394 | 967 | if relative_paths: | 1102 | if relative_paths: |
395 | 968 | relative_paths = os.path.normcase(relative_paths) | 1103 | relative_paths = os.path.normcase(relative_paths) |
396 | 969 | sname = os.path.normcase(os.path.abspath(sname)) | 1104 | sname = os.path.normcase(os.path.abspath(sname)) |
398 | 970 | spath = ',\n '.join( | 1105 | spath = _format_paths( |
399 | 971 | [_relativitize(os.path.normcase(path_item), sname, relative_paths) | 1106 | [_relativitize(os.path.normcase(path_item), sname, relative_paths) |
402 | 972 | for path_item in path] | 1107 | for path_item in path], indent_level=indent_level) |
401 | 973 | ) | ||
403 | 974 | rpsetup = relative_paths_setup | 1108 | rpsetup = relative_paths_setup |
404 | 1109 | if not omit_os_import: | ||
405 | 1110 | rpsetup = '\n\nimport os\n' + rpsetup | ||
406 | 975 | for i in range(_relative_depth(relative_paths, sname)): | 1111 | for i in range(_relative_depth(relative_paths, sname)): |
408 | 976 | rpsetup += "base = os.path.dirname(base)\n" | 1112 | rpsetup += "\nbase = os.path.dirname(base)" |
409 | 977 | else: | 1113 | else: |
411 | 978 | spath = repr(path)[1:-1].replace(', ', ',\n ') | 1114 | spath = _format_paths((repr(p) for p in path), |
412 | 1115 | indent_level=indent_level) | ||
413 | 979 | rpsetup = '' | 1116 | rpsetup = '' |
414 | 980 | return spath, rpsetup | 1117 | return spath, rpsetup |
415 | 981 | 1118 | ||
416 | 982 | |||
417 | 983 | def _relative_depth(common, path): | 1119 | def _relative_depth(common, path): |
418 | 1120 | """Return number of dirs separating ``path`` from ancestor, ``common``. | ||
419 | 1121 | |||
420 | 1122 | For instance, if path is /foo/bar/baz/bing, and common is /foo, this will | ||
421 | 1123 | return 2--in UNIX, the number of ".." to get from bing's directory | ||
422 | 1124 | to foo. | ||
423 | 1125 | |||
424 | 1126 | This is a helper for _relative_path_and_setup. | ||
425 | 1127 | """ | ||
426 | 984 | n = 0 | 1128 | n = 0 |
427 | 985 | while 1: | 1129 | while 1: |
428 | 986 | dirname = os.path.dirname(path) | 1130 | dirname = os.path.dirname(path) |
429 | @@ -993,6 +1137,11 @@ | |||
430 | 993 | return n | 1137 | return n |
431 | 994 | 1138 | ||
432 | 995 | def _relative_path(common, path): | 1139 | def _relative_path(common, path): |
433 | 1140 | """Return the relative path from ``common`` to ``path``. | ||
434 | 1141 | |||
435 | 1142 | This is a helper for _relativitize, which is a helper to | ||
436 | 1143 | _relative_path_and_setup. | ||
437 | 1144 | """ | ||
438 | 996 | r = [] | 1145 | r = [] |
439 | 997 | while 1: | 1146 | while 1: |
440 | 998 | dirname, basename = os.path.split(path) | 1147 | dirname, basename = os.path.split(path) |
441 | @@ -1006,6 +1155,11 @@ | |||
442 | 1006 | return os.path.join(*r) | 1155 | return os.path.join(*r) |
443 | 1007 | 1156 | ||
444 | 1008 | def _relativitize(path, script, relative_paths): | 1157 | def _relativitize(path, script, relative_paths): |
445 | 1158 | """Return a code string for the given path. | ||
446 | 1159 | |||
447 | 1160 | Path is relative to the base path ``relative_paths``if the common prefix | ||
448 | 1161 | between ``path`` and ``script`` starts with ``relative_paths``. | ||
449 | 1162 | """ | ||
450 | 1009 | if path == script: | 1163 | if path == script: |
451 | 1010 | raise AssertionError("path == script") | 1164 | raise AssertionError("path == script") |
452 | 1011 | common = os.path.dirname(os.path.commonprefix([path, script])) | 1165 | common = os.path.dirname(os.path.commonprefix([path, script])) |
453 | @@ -1016,66 +1170,82 @@ | |||
454 | 1016 | else: | 1170 | else: |
455 | 1017 | return repr(path) | 1171 | return repr(path) |
456 | 1018 | 1172 | ||
457 | 1019 | |||
458 | 1020 | relative_paths_setup = """ | 1173 | relative_paths_setup = """ |
459 | 1021 | import os | ||
460 | 1022 | |||
461 | 1023 | join = os.path.join | 1174 | join = os.path.join |
467 | 1024 | base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) | 1175 | base = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))""" |
468 | 1025 | """ | 1176 | |
469 | 1026 | 1177 | def _write_script(full_name, contents, logged_type): | |
470 | 1027 | def _script(module_name, attrs, path, dest, executable, arguments, | 1178 | """Write contents of script in full_name, logging the action. |
471 | 1028 | initialization, rsetup): | 1179 | |
472 | 1180 | The only tricky bit in this function is that it supports Windows by | ||
473 | 1181 | creating exe files using a pkg_resources helper. | ||
474 | 1182 | """ | ||
475 | 1029 | generated = [] | 1183 | generated = [] |
494 | 1030 | script = dest | 1184 | script_name = full_name |
495 | 1031 | if is_win32: | 1185 | if is_win32: |
496 | 1032 | dest += '-script.py' | 1186 | script_name += '-script.py' |
497 | 1033 | 1187 | # Generate exe file and give the script a magic name. | |
498 | 1034 | contents = script_template % dict( | 1188 | exe = full_name + '.exe' |
481 | 1035 | python = _safe_arg(executable), | ||
482 | 1036 | path = path, | ||
483 | 1037 | module_name = module_name, | ||
484 | 1038 | attrs = attrs, | ||
485 | 1039 | arguments = arguments, | ||
486 | 1040 | initialization = initialization, | ||
487 | 1041 | relative_paths_setup = rsetup, | ||
488 | 1042 | ) | ||
489 | 1043 | changed = not (os.path.exists(dest) and open(dest).read() == contents) | ||
490 | 1044 | |||
491 | 1045 | if is_win32: | ||
492 | 1046 | # generate exe file and give the script a magic name: | ||
493 | 1047 | exe = script+'.exe' | ||
499 | 1048 | new_data = pkg_resources.resource_string('setuptools', 'cli.exe') | 1189 | new_data = pkg_resources.resource_string('setuptools', 'cli.exe') |
500 | 1049 | if not os.path.exists(exe) or (open(exe, 'rb').read() != new_data): | 1190 | if not os.path.exists(exe) or (open(exe, 'rb').read() != new_data): |
501 | 1050 | # Only write it if it's different. | 1191 | # Only write it if it's different. |
502 | 1051 | open(exe, 'wb').write(new_data) | 1192 | open(exe, 'wb').write(new_data) |
503 | 1052 | generated.append(exe) | 1193 | generated.append(exe) |
505 | 1053 | 1194 | changed = not (os.path.exists(script_name) and | |
506 | 1195 | open(script_name).read() == contents) | ||
507 | 1054 | if changed: | 1196 | if changed: |
511 | 1055 | open(dest, 'w').write(contents) | 1197 | open(script_name, 'w').write(contents) |
509 | 1056 | logger.info("Generated script %r.", script) | ||
510 | 1057 | |||
512 | 1058 | try: | 1198 | try: |
514 | 1059 | os.chmod(dest, 0755) | 1199 | os.chmod(script_name, 0755) |
515 | 1060 | except (AttributeError, os.error): | 1200 | except (AttributeError, os.error): |
516 | 1061 | pass | 1201 | pass |
519 | 1062 | 1202 | logger.info("Generated %s %r.", logged_type, full_name) | |
520 | 1063 | generated.append(dest) | 1203 | generated.append(script_name) |
521 | 1064 | return generated | 1204 | return generated |
522 | 1065 | 1205 | ||
523 | 1206 | def _format_paths(paths, indent_level=1): | ||
524 | 1207 | """Format paths for inclusion in a script.""" | ||
525 | 1208 | separator = ',\n' + indent_level * ' ' | ||
526 | 1209 | return separator.join(paths) | ||
527 | 1210 | |||
528 | 1211 | def _script(dest, executable, relative_paths_setup, path, initialization, | ||
529 | 1212 | module_name, attrs, arguments, block_site=False): | ||
530 | 1213 | if block_site: | ||
531 | 1214 | dash_S = ' -S' | ||
532 | 1215 | else: | ||
533 | 1216 | dash_S = '' | ||
534 | 1217 | contents = script_template % dict( | ||
535 | 1218 | python=_safe_arg(executable), | ||
536 | 1219 | dash_S=dash_S, | ||
537 | 1220 | path=path, | ||
538 | 1221 | module_name=module_name, | ||
539 | 1222 | attrs=attrs, | ||
540 | 1223 | arguments=arguments, | ||
541 | 1224 | initialization=initialization, | ||
542 | 1225 | relative_paths_setup=relative_paths_setup, | ||
543 | 1226 | ) | ||
544 | 1227 | return _write_script(dest, contents, 'script') | ||
545 | 1228 | |||
546 | 1066 | if is_jython and jython_os_name == 'linux': | 1229 | if is_jython and jython_os_name == 'linux': |
548 | 1067 | script_header = '#!/usr/bin/env %(python)s' | 1230 | script_header = '#!/usr/bin/env %(python)s%(dash_S)s' |
549 | 1068 | else: | 1231 | else: |
551 | 1069 | script_header = '#!%(python)s' | 1232 | script_header = '#!%(python)s%(dash_S)s' |
552 | 1070 | 1233 | ||
553 | 1234 | sys_path_template = '''\ | ||
554 | 1235 | import sys | ||
555 | 1236 | sys.path[0:0] = [ | ||
556 | 1237 | %s, | ||
557 | 1238 | ] | ||
558 | 1239 | ''' | ||
559 | 1071 | 1240 | ||
560 | 1072 | script_template = script_header + '''\ | 1241 | script_template = script_header + '''\ |
561 | 1073 | |||
562 | 1074 | %(relative_paths_setup)s | 1242 | %(relative_paths_setup)s |
563 | 1243 | |||
564 | 1075 | import sys | 1244 | import sys |
565 | 1076 | sys.path[0:0] = [ | 1245 | sys.path[0:0] = [ |
568 | 1077 | %(path)s, | 1246 | %(path)s, |
569 | 1078 | ] | 1247 | ] |
570 | 1248 | |||
571 | 1079 | %(initialization)s | 1249 | %(initialization)s |
572 | 1080 | import %(module_name)s | 1250 | import %(module_name)s |
573 | 1081 | 1251 | ||
574 | @@ -1083,47 +1253,25 @@ | |||
575 | 1083 | %(module_name)s.%(attrs)s(%(arguments)s) | 1253 | %(module_name)s.%(attrs)s(%(arguments)s) |
576 | 1084 | ''' | 1254 | ''' |
577 | 1085 | 1255 | ||
578 | 1256 | # These are used only by the older ``scripts`` function. | ||
579 | 1086 | 1257 | ||
580 | 1087 | def _pyscript(path, dest, executable, rsetup): | 1258 | def _pyscript(path, dest, executable, rsetup): |
581 | 1088 | generated = [] | ||
582 | 1089 | script = dest | ||
583 | 1090 | if is_win32: | ||
584 | 1091 | dest += '-script.py' | ||
585 | 1092 | |||
586 | 1093 | contents = py_script_template % dict( | 1259 | contents = py_script_template % dict( |
590 | 1094 | python = _safe_arg(executable), | 1260 | python=_safe_arg(executable), |
591 | 1095 | path = path, | 1261 | dash_S='', |
592 | 1096 | relative_paths_setup = rsetup, | 1262 | path=path, |
593 | 1263 | relative_paths_setup=rsetup, | ||
594 | 1097 | ) | 1264 | ) |
615 | 1098 | changed = not (os.path.exists(dest) and open(dest).read() == contents) | 1265 | return _write_script(dest, contents, 'interpreter') |
596 | 1099 | |||
597 | 1100 | if is_win32: | ||
598 | 1101 | # generate exe file and give the script a magic name: | ||
599 | 1102 | exe = script + '.exe' | ||
600 | 1103 | open(exe, 'wb').write( | ||
601 | 1104 | pkg_resources.resource_string('setuptools', 'cli.exe') | ||
602 | 1105 | ) | ||
603 | 1106 | generated.append(exe) | ||
604 | 1107 | |||
605 | 1108 | if changed: | ||
606 | 1109 | open(dest, 'w').write(contents) | ||
607 | 1110 | try: | ||
608 | 1111 | os.chmod(dest,0755) | ||
609 | 1112 | except (AttributeError, os.error): | ||
610 | 1113 | pass | ||
611 | 1114 | logger.info("Generated interpreter %r.", script) | ||
612 | 1115 | |||
613 | 1116 | generated.append(dest) | ||
614 | 1117 | return generated | ||
616 | 1118 | 1266 | ||
617 | 1119 | py_script_template = script_header + '''\ | 1267 | py_script_template = script_header + '''\ |
618 | 1120 | |||
619 | 1121 | %(relative_paths_setup)s | 1268 | %(relative_paths_setup)s |
620 | 1269 | |||
621 | 1122 | import sys | 1270 | import sys |
622 | 1123 | 1271 | ||
623 | 1124 | sys.path[0:0] = [ | 1272 | sys.path[0:0] = [ |
626 | 1125 | %(path)s, | 1273 | %(path)s, |
627 | 1126 | ] | 1274 | ] |
628 | 1127 | 1275 | ||
629 | 1128 | _interactive = True | 1276 | _interactive = True |
630 | 1129 | if len(sys.argv) > 1: | 1277 | if len(sys.argv) > 1: |
631 | @@ -1151,6 +1299,274 @@ | |||
632 | 1151 | __import__("code").interact(banner="", local=globals()) | 1299 | __import__("code").interact(banner="", local=globals()) |
633 | 1152 | ''' | 1300 | ''' |
634 | 1153 | 1301 | ||
635 | 1302 | # These are used only by the newer ``sitepackage_safe_scripts`` function. | ||
636 | 1303 | |||
637 | 1304 | def _get_system_paths(executable): | ||
638 | 1305 | """Return lists of standard lib and site paths for executable. | ||
639 | 1306 | """ | ||
640 | 1307 | # We want to get a list of the site packages, which is not easy. | ||
641 | 1308 | # The canonical way to do this is to use | ||
642 | 1309 | # distutils.sysconfig.get_python_lib(), but that only returns a | ||
643 | 1310 | # single path, which does not reflect reality for many system | ||
644 | 1311 | # Pythons, which have multiple additions. Instead, we start Python | ||
645 | 1312 | # with -S, which does not import site.py and set up the extra paths | ||
646 | 1313 | # like site-packages or (Ubuntu/Debian) dist-packages and | ||
647 | 1314 | # python-support. We then compare that sys.path with the normal one | ||
648 | 1315 | # (minus user packages if this is Python 2.6, because we don't | ||
649 | 1316 | # support those (yet?). The set of the normal one minus the set of | ||
650 | 1317 | # the ones in ``python -S`` is the set of packages that are | ||
651 | 1318 | # effectively site-packages. | ||
652 | 1319 | # | ||
653 | 1320 | # The given executable might not be the current executable, so it is | ||
654 | 1321 | # appropriate to do another subprocess to figure out what the | ||
655 | 1322 | # additional site-package paths are. Moreover, even if this | ||
656 | 1323 | # executable *is* the current executable, this code might be run in | ||
657 | 1324 | # the context of code that has manipulated the sys.path--for | ||
658 | 1325 | # instance, to add local zc.buildout or setuptools eggs. | ||
659 | 1326 | def get_sys_path(*args, **kwargs): | ||
660 | 1327 | cmd = [executable] | ||
661 | 1328 | cmd.extend(args) | ||
662 | 1329 | cmd.extend([ | ||
663 | 1330 | "-c", "import sys, os;" | ||
664 | 1331 | "print repr([os.path.normpath(p) for p in sys.path if p])"]) | ||
665 | 1332 | # Windows needs some (as yet to be determined) part of the real env. | ||
666 | 1333 | env = os.environ.copy() | ||
667 | 1334 | env.update(kwargs) | ||
668 | 1335 | _proc = subprocess.Popen( | ||
669 | 1336 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) | ||
670 | 1337 | stdout, stderr = _proc.communicate(); | ||
671 | 1338 | if _proc.returncode: | ||
672 | 1339 | raise RuntimeError( | ||
673 | 1340 | 'error trying to get system packages:\n%s' % (stderr,)) | ||
674 | 1341 | res = eval(stdout.strip()) | ||
675 | 1342 | try: | ||
676 | 1343 | res.remove('.') | ||
677 | 1344 | except ValueError: | ||
678 | 1345 | pass | ||
679 | 1346 | return res | ||
680 | 1347 | stdlib = get_sys_path('-S') # stdlib only | ||
681 | 1348 | no_user_paths = get_sys_path(PYTHONNOUSERSITE='x') | ||
682 | 1349 | site_paths = [p for p in no_user_paths if p not in stdlib] | ||
683 | 1350 | return (stdlib, site_paths) | ||
684 | 1351 | |||
685 | 1352 | def _get_module_file(executable, name): | ||
686 | 1353 | """Return a module's file path. | ||
687 | 1354 | |||
688 | 1355 | - executable is a path to the desired Python executable. | ||
689 | 1356 | - name is the name of the (pure, not C) Python module. | ||
690 | 1357 | """ | ||
691 | 1358 | cmd = [executable, "-c", | ||
692 | 1359 | "import imp; " | ||
693 | 1360 | "fp, path, desc = imp.find_module(%r); " | ||
694 | 1361 | "fp.close; " | ||
695 | 1362 | "print path" % (name,)] | ||
696 | 1363 | _proc = subprocess.Popen( | ||
697 | 1364 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
698 | 1365 | stdout, stderr = _proc.communicate(); | ||
699 | 1366 | if _proc.returncode: | ||
700 | 1367 | logger.info( | ||
701 | 1368 | 'Could not find file for module %s:\n%s', name, stderr) | ||
702 | 1369 | return None | ||
703 | 1370 | # else: ... | ||
704 | 1371 | res = stdout.strip() | ||
705 | 1372 | if res.endswith('.pyc') or res.endswith('.pyo'): | ||
706 | 1373 | raise RuntimeError('Cannot find uncompiled version of %s' % (name,)) | ||
707 | 1374 | if not os.path.exists(res): | ||
708 | 1375 | raise RuntimeError( | ||
709 | 1376 | 'File does not exist for module %s:\n%s' % (name, res)) | ||
710 | 1377 | return res | ||
711 | 1378 | |||
712 | 1379 | def _generate_sitecustomize(dest, executable, initialization='', | ||
713 | 1380 | exec_sitecustomize=False): | ||
714 | 1381 | """Write a sitecustomize file with optional custom initialization. | ||
715 | 1382 | |||
716 | 1383 | The created script will execute the underlying Python's | ||
717 | 1384 | sitecustomize if exec_sitecustomize is True. | ||
718 | 1385 | """ | ||
719 | 1386 | sitecustomize_path = os.path.join(dest, 'sitecustomize.py') | ||
720 | 1387 | sitecustomize = open(sitecustomize_path, 'w') | ||
721 | 1388 | if initialization: | ||
722 | 1389 | sitecustomize.write(initialization + '\n') | ||
723 | 1390 | if exec_sitecustomize: | ||
724 | 1391 | real_sitecustomize_path = _get_module_file( | ||
725 | 1392 | executable, 'sitecustomize') | ||
726 | 1393 | if real_sitecustomize_path: | ||
727 | 1394 | real_sitecustomize = open(real_sitecustomize_path, 'r') | ||
728 | 1395 | sitecustomize.write( | ||
729 | 1396 | '\n# The following is from\n# %s\n' % | ||
730 | 1397 | (real_sitecustomize_path,)) | ||
731 | 1398 | sitecustomize.write(real_sitecustomize.read()) | ||
732 | 1399 | real_sitecustomize.close() | ||
733 | 1400 | sitecustomize.close() | ||
734 | 1401 | return sitecustomize_path | ||
735 | 1402 | |||
736 | 1403 | def _generate_site(dest, working_set, executable, extra_paths=(), | ||
737 | 1404 | add_site_packages=False, relative_paths=False): | ||
738 | 1405 | """Write a site.py file with eggs from working_set. | ||
739 | 1406 | |||
740 | 1407 | extra_paths will be added to the path. If add_site_packages is True, | ||
741 | 1408 | paths from the underlying Python will be added. | ||
742 | 1409 | """ | ||
743 | 1410 | path = _get_path(working_set, extra_paths) | ||
744 | 1411 | site_path = os.path.join(dest, 'site.py') | ||
745 | 1412 | egg_path_string, preamble = _relative_path_and_setup( | ||
746 | 1413 | site_path, path, relative_paths, indent_level=2, omit_os_import=True) | ||
747 | 1414 | if preamble: | ||
748 | 1415 | preamble = '\n'.join( | ||
749 | 1416 | [(line and ' %s' % (line,) or line) | ||
750 | 1417 | for line in preamble.split('\n')]) | ||
751 | 1418 | original_path_setup = '' | ||
752 | 1419 | if add_site_packages: | ||
753 | 1420 | stdlib, site_paths = _get_system_paths(executable) | ||
754 | 1421 | original_path_setup = original_path_snippet % ( | ||
755 | 1422 | _format_paths((repr(p) for p in site_paths), 2),) | ||
756 | 1423 | distribution = working_set.find( | ||
757 | 1424 | pkg_resources.Requirement.parse('setuptools')) | ||
758 | 1425 | if distribution is not None: | ||
759 | 1426 | # We need to worry about namespace packages. | ||
760 | 1427 | if relative_paths: | ||
761 | 1428 | location = _relativitize( | ||
762 | 1429 | distribution.location, | ||
763 | 1430 | os.path.normcase(os.path.abspath(site_path)), | ||
764 | 1431 | relative_paths) | ||
765 | 1432 | else: | ||
766 | 1433 | location = repr(distribution.location) | ||
767 | 1434 | preamble += namespace_add_site_packages_setup % (location,) | ||
768 | 1435 | original_path_setup = ( | ||
769 | 1436 | addsitedir_namespace_originalpackages_snippet + | ||
770 | 1437 | original_path_setup) | ||
771 | 1438 | addsitepackages_marker = 'def addsitepackages(' | ||
772 | 1439 | enableusersite_marker = 'ENABLE_USER_SITE = ' | ||
773 | 1440 | successful_rewrite = False | ||
774 | 1441 | real_site_path = _get_module_file(executable, 'site') | ||
775 | 1442 | real_site = open(real_site_path, 'r') | ||
776 | 1443 | site = open(site_path, 'w') | ||
777 | 1444 | try: | ||
778 | 1445 | for line in real_site.readlines(): | ||
779 | 1446 | if line.startswith(enableusersite_marker): | ||
780 | 1447 | site.write(enableusersite_marker) | ||
781 | 1448 | site.write('False # buildout does not support user sites.\n') | ||
782 | 1449 | elif line.startswith(addsitepackages_marker): | ||
783 | 1450 | site.write(addsitepackages_script % ( | ||
784 | 1451 | preamble, egg_path_string, original_path_setup)) | ||
785 | 1452 | site.write(line[len(addsitepackages_marker):]) | ||
786 | 1453 | successful_rewrite = True | ||
787 | 1454 | else: | ||
788 | 1455 | site.write(line) | ||
789 | 1456 | finally: | ||
790 | 1457 | site.close() | ||
791 | 1458 | real_site.close() | ||
792 | 1459 | if not successful_rewrite: | ||
793 | 1460 | raise RuntimeError('Buildout did not successfully rewrite site.py') | ||
794 | 1461 | return site_path | ||
795 | 1462 | |||
796 | 1463 | namespace_add_site_packages_setup = ''' | ||
797 | 1464 | setuptools_path = %s | ||
798 | 1465 | sys.path.append(setuptools_path) | ||
799 | 1466 | known_paths.add(os.path.normcase(setuptools_path)) | ||
800 | 1467 | import pkg_resources''' | ||
801 | 1468 | |||
802 | 1469 | addsitedir_namespace_originalpackages_snippet = ''' | ||
803 | 1470 | pkg_resources.working_set.add_entry(sitedir)''' | ||
804 | 1471 | |||
805 | 1472 | original_path_snippet = ''' | ||
806 | 1473 | original_paths = [ | ||
807 | 1474 | %s | ||
808 | 1475 | ] | ||
809 | 1476 | for path in original_paths: | ||
810 | 1477 | addsitedir(path, known_paths)''' | ||
811 | 1478 | |||
812 | 1479 | addsitepackages_script = '''\ | ||
813 | 1480 | def addsitepackages(known_paths): | ||
814 | 1481 | """Add site packages, as determined by zc.buildout. | ||
815 | 1482 | |||
816 | 1483 | See original_addsitepackages, below, for the original version."""%s | ||
817 | 1484 | buildout_paths = [ | ||
818 | 1485 | %s | ||
819 | 1486 | ] | ||
820 | 1487 | for path in buildout_paths: | ||
821 | 1488 | sitedir, sitedircase = makepath(path) | ||
822 | 1489 | if not sitedircase in known_paths and os.path.exists(sitedir): | ||
823 | 1490 | sys.path.append(sitedir) | ||
824 | 1491 | known_paths.add(sitedircase)%s | ||
825 | 1492 | return known_paths | ||
826 | 1493 | |||
827 | 1494 | def original_addsitepackages(''' | ||
828 | 1495 | |||
829 | 1496 | def _generate_interpreter(name, dest, executable, site_py_dest, | ||
830 | 1497 | relative_paths=False): | ||
831 | 1498 | """Write an interpreter script, using the site.py approach.""" | ||
832 | 1499 | full_name = os.path.join(dest, name) | ||
833 | 1500 | site_py_dest_string, rpsetup = _relative_path_and_setup( | ||
834 | 1501 | full_name, [site_py_dest], relative_paths, omit_os_import=True) | ||
835 | 1502 | if rpsetup: | ||
836 | 1503 | rpsetup += "\n" | ||
837 | 1504 | if sys.platform == 'win32': | ||
838 | 1505 | windows_import = '\nimport subprocess' | ||
839 | 1506 | # os.exec* is a mess on Windows, particularly if the path | ||
840 | 1507 | # to the executable has spaces and the Python is using MSVCRT. | ||
841 | 1508 | # The standard fix is to surround the executable's path with quotes, | ||
842 | 1509 | # but that has been unreliable in testing. | ||
843 | 1510 | # | ||
844 | 1511 | # Here's a demonstration of the problem. Given a Python | ||
845 | 1512 | # compiled with a MSVCRT-based compiler, such as the free Visual | ||
846 | 1513 | # C++ 2008 Express Edition, and an executable path with spaces | ||
847 | 1514 | # in it such as the below, we see the following. | ||
848 | 1515 | # | ||
849 | 1516 | # >>> import os | ||
850 | 1517 | # >>> p0 = 'C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe' | ||
851 | 1518 | # >>> os.path.exists(p0) | ||
852 | 1519 | # True | ||
853 | 1520 | # >>> os.execv(p0, []) | ||
854 | 1521 | # Traceback (most recent call last): | ||
855 | 1522 | # File "<stdin>", line 1, in <module> | ||
856 | 1523 | # OSError: [Errno 22] Invalid argument | ||
857 | 1524 | # | ||
858 | 1525 | # That seems like a standard problem. The standard solution is | ||
859 | 1526 | # to quote the path (see, for instance | ||
860 | 1527 | # http://bugs.python.org/issue436259). However, this solution, | ||
861 | 1528 | # and other variations, fail: | ||
862 | 1529 | # | ||
863 | 1530 | # >>> p1 = '"C:\\Documents and Settings\\Administrator\\My Documents\\Downloads\\Python-2.6.4\\PCbuild\\python.exe"' | ||
864 | 1531 | # >>> os.execv(p1, []) | ||
865 | 1532 | # Traceback (most recent call last): | ||
866 | 1533 | # File "<stdin>", line 1, in <module> | ||
867 | 1534 | # OSError: [Errno 22] Invalid argument | ||
868 | 1535 | # | ||
869 | 1536 | # We simply use subprocess instead, since it handles everything | ||
870 | 1537 | # nicely, and the transparency of exec* (that is, not running, | ||
871 | 1538 | # perhaps unexpectedly, in a subprocess) is arguably not a | ||
872 | 1539 | # necessity, at least for many use cases. | ||
873 | 1540 | execute = 'subprocess.call(argv, env=environ)' | ||
874 | 1541 | else: | ||
875 | 1542 | windows_import = '' | ||
876 | 1543 | execute = 'os.execve(sys.executable, argv, environ)' | ||
877 | 1544 | contents = interpreter_template % dict( | ||
878 | 1545 | python=_safe_arg(executable), | ||
879 | 1546 | dash_S=' -S', | ||
880 | 1547 | site_dest=site_py_dest_string, | ||
881 | 1548 | relative_paths_setup=rpsetup, | ||
882 | 1549 | windows_import=windows_import, | ||
883 | 1550 | execute=execute, | ||
884 | 1551 | ) | ||
885 | 1552 | return _write_script(full_name, contents, 'interpreter') | ||
886 | 1553 | |||
887 | 1554 | interpreter_template = script_header + ''' | ||
888 | 1555 | import os | ||
889 | 1556 | import sys%(windows_import)s | ||
890 | 1557 | %(relative_paths_setup)s | ||
891 | 1558 | argv = [sys.executable] + sys.argv[1:] | ||
892 | 1559 | environ = os.environ.copy() | ||
893 | 1560 | path = %(site_dest)s | ||
894 | 1561 | if environ.get('PYTHONPATH'): | ||
895 | 1562 | path = os.pathsep.join([path, environ['PYTHONPATH']]) | ||
896 | 1563 | environ['PYTHONPATH'] = path | ||
897 | 1564 | %(execute)s | ||
898 | 1565 | ''' | ||
899 | 1566 | |||
900 | 1567 | # End of script generation code. | ||
901 | 1568 | ############################################################################ | ||
902 | 1569 | |||
903 | 1154 | runsetup_template = """ | 1570 | runsetup_template = """ |
904 | 1155 | import sys | 1571 | import sys |
905 | 1156 | sys.path.insert(0, %(setupdir)r) | 1572 | sys.path.insert(0, %(setupdir)r) |
906 | 1157 | 1573 | ||
907 | === modified file 'src/zc/buildout/easy_install.txt' | |||
908 | --- src/zc/buildout/easy_install.txt 2009-11-11 21:21:11 +0000 | |||
909 | +++ src/zc/buildout/easy_install.txt 2010-02-23 20:41:17 +0000 | |||
910 | @@ -521,25 +521,38 @@ | |||
911 | 521 | Script generation | 521 | Script generation |
912 | 522 | ----------------- | 522 | ----------------- |
913 | 523 | 523 | ||
918 | 524 | The easy_install module provides support for creating scripts from | 524 | The easy_install module provides support for creating scripts from eggs. |
919 | 525 | eggs. It provides a function similar to setuptools except that it | 525 | It provides two competing functions. One, ``scripts``, is a |
920 | 526 | provides facilities for baking a script's path into the script. This | 526 | well-established approach to generating reliable scripts with a "clean" |
921 | 527 | has two advantages: | 527 | Python--e.g., one that does not have any packages in its site-packages. |
922 | 528 | The other, ``sitepackage_safe_scripts``, is newer, a bit trickier, and is | ||
923 | 529 | designed to work with a Python that has code in its site-packages, such | ||
924 | 530 | as a system Python. | ||
925 | 531 | |||
926 | 532 | Both are similar to setuptools except that they provides facilities for | ||
927 | 533 | baking a script's path into the script. This has two advantages: | ||
928 | 528 | 534 | ||
929 | 529 | - The eggs to be used by a script are not chosen at run time, making | 535 | - The eggs to be used by a script are not chosen at run time, making |
930 | 530 | startup faster and, more importantly, deterministic. | 536 | startup faster and, more importantly, deterministic. |
931 | 531 | 537 | ||
940 | 532 | - The script doesn't have to import pkg_resources because the logic | 538 | - The script doesn't have to import pkg_resources because the logic that |
941 | 533 | that pkg_resources would execute at run time is executed at | 539 | pkg_resources would execute at run time is executed at script-creation |
942 | 534 | script-creation time. | 540 | time. (There is an exception in ``sitepackage_safe_scripts`` if you |
943 | 535 | 541 | want to have your Python's site packages available, as discussed | |
944 | 536 | The scripts method can be used to generate scripts. Let's create a | 542 | below, but even in that case pkg_resources is only partially |
945 | 537 | destination directory for it to place them in: | 543 | activated, which can be a significant time savings.) |
946 | 538 | 544 | ||
947 | 539 | >>> import tempfile | 545 | |
948 | 546 | The ``scripts`` function | ||
949 | 547 | ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
950 | 548 | |||
951 | 549 | The ``scripts`` function is the first way to generate scripts that we'll | ||
952 | 550 | examine. It is the earlier approach that the package offered. Let's | ||
953 | 551 | create a destination directory for it to place them in: | ||
954 | 552 | |||
955 | 540 | >>> bin = tmpdir('bin') | 553 | >>> bin = tmpdir('bin') |
956 | 541 | 554 | ||
958 | 542 | Now, we'll use the scripts method to generate scripts in this directory | 555 | Now, we'll use the scripts function to generate scripts in this directory |
959 | 543 | from the demo egg: | 556 | from the demo egg: |
960 | 544 | 557 | ||
961 | 545 | >>> import sys | 558 | >>> import sys |
962 | @@ -736,8 +749,8 @@ | |||
963 | 736 | >>> print system(os.path.join(bin, 'run')), | 749 | >>> print system(os.path.join(bin, 'run')), |
964 | 737 | 3 1 | 750 | 3 1 |
965 | 738 | 751 | ||
968 | 739 | Including extra paths in scripts | 752 | The ``scripts`` function: Including extra paths in scripts |
969 | 740 | -------------------------------- | 753 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
970 | 741 | 754 | ||
971 | 742 | We can pass a keyword argument, extra paths, to cause additional paths | 755 | We can pass a keyword argument, extra paths, to cause additional paths |
972 | 743 | to be included in the a generated script: | 756 | to be included in the a generated script: |
973 | @@ -762,8 +775,8 @@ | |||
974 | 762 | if __name__ == '__main__': | 775 | if __name__ == '__main__': |
975 | 763 | eggrecipedemo.main() | 776 | eggrecipedemo.main() |
976 | 764 | 777 | ||
979 | 765 | Providing script arguments | 778 | The ``scripts`` function: Providing script arguments |
980 | 766 | -------------------------- | 779 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
981 | 767 | 780 | ||
982 | 768 | An "argument" keyword argument can be used to pass arguments to an | 781 | An "argument" keyword argument can be used to pass arguments to an |
983 | 769 | entry point. The value passed is a source string to be placed between the | 782 | entry point. The value passed is a source string to be placed between the |
984 | @@ -786,8 +799,8 @@ | |||
985 | 786 | if __name__ == '__main__': | 799 | if __name__ == '__main__': |
986 | 787 | eggrecipedemo.main(1, 2) | 800 | eggrecipedemo.main(1, 2) |
987 | 788 | 801 | ||
990 | 789 | Passing initialization code | 802 | The ``scripts`` function: Passing initialization code |
991 | 790 | --------------------------- | 803 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
992 | 791 | 804 | ||
993 | 792 | You can also pass script initialization code: | 805 | You can also pass script initialization code: |
994 | 793 | 806 | ||
995 | @@ -812,8 +825,8 @@ | |||
996 | 812 | if __name__ == '__main__': | 825 | if __name__ == '__main__': |
997 | 813 | eggrecipedemo.main(1, 2) | 826 | eggrecipedemo.main(1, 2) |
998 | 814 | 827 | ||
1001 | 815 | Relative paths | 828 | The ``scripts`` function: Relative paths |
1002 | 816 | -------------- | 829 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
1003 | 817 | 830 | ||
1004 | 818 | Sometimes, you want to be able to move a buildout directory around and | 831 | Sometimes, you want to be able to move a buildout directory around and |
1005 | 819 | have scripts still work without having to rebuild them. We can | 832 | have scripts still work without having to rebuild them. We can |
1006 | @@ -836,7 +849,7 @@ | |||
1007 | 836 | ... interpreter='py', | 849 | ... interpreter='py', |
1008 | 837 | ... relative_paths=bo) | 850 | ... relative_paths=bo) |
1009 | 838 | 851 | ||
1011 | 839 | >>> cat(bo, 'bin', 'run') | 852 | >>> cat(bo, 'bin', 'run') # doctest: +NORMALIZE_WHITESPACE |
1012 | 840 | #!/usr/local/bin/python2.4 | 853 | #!/usr/local/bin/python2.4 |
1013 | 841 | <BLANKLINE> | 854 | <BLANKLINE> |
1014 | 842 | import os | 855 | import os |
1015 | @@ -868,7 +881,7 @@ | |||
1016 | 868 | 881 | ||
1017 | 869 | We specified an interpreter and its paths are adjusted too: | 882 | We specified an interpreter and its paths are adjusted too: |
1018 | 870 | 883 | ||
1020 | 871 | >>> cat(bo, 'bin', 'py') | 884 | >>> cat(bo, 'bin', 'py') # doctest: +NORMALIZE_WHITESPACE |
1021 | 872 | #!/usr/local/bin/python2.4 | 885 | #!/usr/local/bin/python2.4 |
1022 | 873 | <BLANKLINE> | 886 | <BLANKLINE> |
1023 | 874 | import os | 887 | import os |
1024 | @@ -911,6 +924,557 @@ | |||
1025 | 911 | del _interactive | 924 | del _interactive |
1026 | 912 | __import__("code").interact(banner="", local=globals()) | 925 | __import__("code").interact(banner="", local=globals()) |
1027 | 913 | 926 | ||
1028 | 927 | The ``sitepackage_safe_scripts`` function | ||
1029 | 928 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
1030 | 929 | |||
1031 | 930 | The newer function for creating scripts is ``sitepackage_safe_scripts``. | ||
1032 | 931 | It has the same basic functionality as the ``scripts`` function: it can | ||
1033 | 932 | create scripts to run arbitrary entry points, and to run a Python | ||
1034 | 933 | interpreter. The following are the differences from a user's | ||
1035 | 934 | perspective. | ||
1036 | 935 | |||
1037 | 936 | - It can be used safely with a Python that has packages installed itself, | ||
1038 | 937 | such as a system-installed Python. | ||
1039 | 938 | |||
1040 | 939 | - In contrast to the interpreter generated by the ``scripts`` method, which | ||
1041 | 940 | supports only a small subset of the usual Python executable's options, | ||
1042 | 941 | the interpreter generated by ``sitepackage_safe_scripts`` supports all | ||
1043 | 942 | of them. This makes it possible to use as full Python replacement for | ||
1044 | 943 | scripts that need the distributions specified in your buildout. | ||
1045 | 944 | |||
1046 | 945 | - Both the interpreter and the entry point scripts allow you to include the | ||
1047 | 946 | site packages, and/or the sitecustomize, of the Python executable, if | ||
1048 | 947 | desired. | ||
1049 | 948 | |||
1050 | 949 | It works by creating site.py and sitecustomize.py files that set up the | ||
1051 | 950 | desired paths and initialization. These must be placed within an otherwise | ||
1052 | 951 | empty directory. Typically this is in a recipe's parts directory. | ||
1053 | 952 | |||
1054 | 953 | Here's the simplest example, building an interpreter script. | ||
1055 | 954 | |||
1056 | 955 | >>> interpreter_dir = tmpdir('interpreter') | ||
1057 | 956 | >>> interpreter_parts_dir = os.path.join( | ||
1058 | 957 | ... interpreter_dir, 'parts', 'interpreter') | ||
1059 | 958 | >>> interpreter_bin_dir = os.path.join(interpreter_dir, 'bin') | ||
1060 | 959 | >>> mkdir(interpreter_bin_dir) | ||
1061 | 960 | >>> mkdir(interpreter_dir, 'eggs') | ||
1062 | 961 | >>> mkdir(interpreter_dir, 'parts') | ||
1063 | 962 | >>> mkdir(interpreter_parts_dir) | ||
1064 | 963 | |||
1065 | 964 | >>> ws = zc.buildout.easy_install.install( | ||
1066 | 965 | ... ['demo'], join(interpreter_dir, 'eggs'), links=[link_server], | ||
1067 | 966 | ... index=link_server+'index/') | ||
1068 | 967 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1069 | 968 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1070 | 969 | ... interpreter='py') | ||
1071 | 970 | |||
1072 | 971 | Depending on whether the machine being used is running Windows or not, this | ||
1073 | 972 | produces either three or four files. In both cases, we have site.py and | ||
1074 | 973 | sitecustomize.py generated in the parts/interpreter directory. For Windows, | ||
1075 | 974 | we have py.exe and py-script.py; for other operating systems, we have py. | ||
1076 | 975 | |||
1077 | 976 | >>> sitecustomize_path = os.path.join( | ||
1078 | 977 | ... interpreter_parts_dir, 'sitecustomize.py') | ||
1079 | 978 | >>> site_path = os.path.join(interpreter_parts_dir, 'site.py') | ||
1080 | 979 | >>> interpreter_path = os.path.join(interpreter_bin_dir, 'py') | ||
1081 | 980 | >>> if sys.platform == 'win32': | ||
1082 | 981 | ... py_path = os.path.join(interpreter_bin_dir, 'py-script.py') | ||
1083 | 982 | ... expected = [sitecustomize_path, | ||
1084 | 983 | ... site_path, | ||
1085 | 984 | ... os.path.join(interpreter_bin_dir, 'py.exe'), | ||
1086 | 985 | ... py_path] | ||
1087 | 986 | ... else: | ||
1088 | 987 | ... py_path = interpreter_path | ||
1089 | 988 | ... expected = [sitecustomize_path, site_path, py_path] | ||
1090 | 989 | ... | ||
1091 | 990 | >>> assert generated == expected, repr((generated, expected)) | ||
1092 | 991 | |||
1093 | 992 | We didn't ask for any initialization, and we didn't ask to use the underlying | ||
1094 | 993 | sitecustomization, so sitecustomize.py is empty. | ||
1095 | 994 | |||
1096 | 995 | >>> cat(sitecustomize_path) | ||
1097 | 996 | |||
1098 | 997 | The interpreter script is simple. It puts the directory with the | ||
1099 | 998 | site.py and sitecustomize.py on the PYTHONPATH and (re)starts Python. | ||
1100 | 999 | |||
1101 | 1000 | >>> cat(py_path) | ||
1102 | 1001 | #!/usr/bin/python -S | ||
1103 | 1002 | import os | ||
1104 | 1003 | import sys | ||
1105 | 1004 | <BLANKLINE> | ||
1106 | 1005 | argv = [sys.executable] + sys.argv[1:] | ||
1107 | 1006 | environ = os.environ.copy() | ||
1108 | 1007 | path = '/interpreter/parts/interpreter' | ||
1109 | 1008 | if environ.get('PYTHONPATH'): | ||
1110 | 1009 | path = os.pathsep.join([path, environ['PYTHONPATH']]) | ||
1111 | 1010 | environ['PYTHONPATH'] = path | ||
1112 | 1011 | os.execve(sys.executable, argv, environ) | ||
1113 | 1012 | |||
1114 | 1013 | The site.py file is a modified version of the underlying Python's site.py. | ||
1115 | 1014 | The most important modification is that it has a different version of the | ||
1116 | 1015 | addsitepackages function. It sets up the Python path, similarly to the | ||
1117 | 1016 | behavior of the function it replaces. The following shows the part that | ||
1118 | 1017 | buildout inserts, in the simplest case. | ||
1119 | 1018 | |||
1120 | 1019 | >>> sys.stdout.write('#\n'); cat(site_path) | ||
1121 | 1020 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1122 | 1021 | #... | ||
1123 | 1022 | def addsitepackages(known_paths): | ||
1124 | 1023 | """Add site packages, as determined by zc.buildout. | ||
1125 | 1024 | <BLANKLINE> | ||
1126 | 1025 | See original_addsitepackages, below, for the original version.""" | ||
1127 | 1026 | buildout_paths = [ | ||
1128 | 1027 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1129 | 1028 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg' | ||
1130 | 1029 | ] | ||
1131 | 1030 | for path in buildout_paths: | ||
1132 | 1031 | sitedir, sitedircase = makepath(path) | ||
1133 | 1032 | if not sitedircase in known_paths and os.path.exists(sitedir): | ||
1134 | 1033 | sys.path.append(sitedir) | ||
1135 | 1034 | known_paths.add(sitedircase) | ||
1136 | 1035 | return known_paths | ||
1137 | 1036 | <BLANKLINE> | ||
1138 | 1037 | def original_addsitepackages(known_paths):... | ||
1139 | 1038 | |||
1140 | 1039 | Here are some examples of the interpreter in use. | ||
1141 | 1040 | |||
1142 | 1041 | >>> print call_py(interpreter_path, "print 16+26") | ||
1143 | 1042 | 42 | ||
1144 | 1043 | <BLANKLINE> | ||
1145 | 1044 | >>> res = call_py(interpreter_path, "import sys; print sys.path") | ||
1146 | 1045 | >>> print res # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1147 | 1046 | ['', | ||
1148 | 1047 | '/interpreter/parts/interpreter', | ||
1149 | 1048 | ..., | ||
1150 | 1049 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1151 | 1050 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] | ||
1152 | 1051 | <BLANKLINE> | ||
1153 | 1052 | >>> clean_paths = eval(res.strip()) # This is used later for comparison. | ||
1154 | 1053 | |||
1155 | 1054 | If you provide initialization, it goes in sitecustomize.py. | ||
1156 | 1055 | |||
1157 | 1056 | >>> def reset_interpreter(): | ||
1158 | 1057 | ... # This is necessary because, in our tests, the timestamps of the | ||
1159 | 1058 | ... # .pyc files are not outdated when we want them to be. | ||
1160 | 1059 | ... rmdir(interpreter_bin_dir) | ||
1161 | 1060 | ... mkdir(interpreter_bin_dir) | ||
1162 | 1061 | ... rmdir(interpreter_parts_dir) | ||
1163 | 1062 | ... mkdir(interpreter_parts_dir) | ||
1164 | 1063 | ... | ||
1165 | 1064 | >>> reset_interpreter() | ||
1166 | 1065 | |||
1167 | 1066 | >>> initialization_string = """\ | ||
1168 | 1067 | ... import os | ||
1169 | 1068 | ... os.environ['FOO'] = 'bar baz bing shazam'""" | ||
1170 | 1069 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1171 | 1070 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1172 | 1071 | ... interpreter='py', initialization=initialization_string) | ||
1173 | 1072 | >>> cat(sitecustomize_path) | ||
1174 | 1073 | import os | ||
1175 | 1074 | os.environ['FOO'] = 'bar baz bing shazam' | ||
1176 | 1075 | >>> print call_py(interpreter_path, "import os; print os.environ['FOO']") | ||
1177 | 1076 | bar baz bing shazam | ||
1178 | 1077 | <BLANKLINE> | ||
1179 | 1078 | |||
1180 | 1079 | If you use relative paths, this affects the interpreter and site.py. (This is | ||
1181 | 1080 | again the UNIX version; the Windows version uses subprocess instead of | ||
1182 | 1081 | os.execve.) | ||
1183 | 1082 | |||
1184 | 1083 | >>> reset_interpreter() | ||
1185 | 1084 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1186 | 1085 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1187 | 1086 | ... interpreter='py', relative_paths=interpreter_dir) | ||
1188 | 1087 | >>> cat(py_path) | ||
1189 | 1088 | #!/usr/bin/python -S | ||
1190 | 1089 | import os | ||
1191 | 1090 | import sys | ||
1192 | 1091 | <BLANKLINE> | ||
1193 | 1092 | join = os.path.join | ||
1194 | 1093 | base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) | ||
1195 | 1094 | base = os.path.dirname(base) | ||
1196 | 1095 | <BLANKLINE> | ||
1197 | 1096 | argv = [sys.executable] + sys.argv[1:] | ||
1198 | 1097 | environ = os.environ.copy() | ||
1199 | 1098 | path = join(base, 'parts/interpreter') | ||
1200 | 1099 | if environ.get('PYTHONPATH'): | ||
1201 | 1100 | path = os.pathsep.join([path, environ['PYTHONPATH']]) | ||
1202 | 1101 | environ['PYTHONPATH'] = path | ||
1203 | 1102 | os.execve(sys.executable, argv, environ) | ||
1204 | 1103 | |||
1205 | 1104 | For site.py, we again show only the pertinent parts. Notice that the egg | ||
1206 | 1105 | paths join a base to a path, as with the use of this argument in the | ||
1207 | 1106 | ``scripts`` function. | ||
1208 | 1107 | |||
1209 | 1108 | >>> sys.stdout.write('#\n'); cat(site_path) # doctest: +ELLIPSIS | ||
1210 | 1109 | #... | ||
1211 | 1110 | def addsitepackages(known_paths): | ||
1212 | 1111 | """Add site packages, as determined by zc.buildout. | ||
1213 | 1112 | <BLANKLINE> | ||
1214 | 1113 | See original_addsitepackages, below, for the original version.""" | ||
1215 | 1114 | join = os.path.join | ||
1216 | 1115 | base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) | ||
1217 | 1116 | base = os.path.dirname(base) | ||
1218 | 1117 | base = os.path.dirname(base) | ||
1219 | 1118 | buildout_paths = [ | ||
1220 | 1119 | join(base, 'eggs/demo-0.3-pyN.N.egg'), | ||
1221 | 1120 | join(base, 'eggs/demoneeded-1.1-pyN.N.egg') | ||
1222 | 1121 | ]... | ||
1223 | 1122 | |||
1224 | 1123 | The paths resolve in practice as you would expect. | ||
1225 | 1124 | |||
1226 | 1125 | >>> print call_py(interpreter_path, | ||
1227 | 1126 | ... "import sys, pprint; pprint.pprint(sys.path)") | ||
1228 | 1127 | ... # doctest: +ELLIPSIS | ||
1229 | 1128 | ['', | ||
1230 | 1129 | '/interpreter/parts/interpreter', | ||
1231 | 1130 | ..., | ||
1232 | 1131 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1233 | 1132 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg'] | ||
1234 | 1133 | <BLANKLINE> | ||
1235 | 1134 | |||
1236 | 1135 | The ``extra_paths`` argument affects the path in site.py. Notice that | ||
1237 | 1136 | /interpreter/other is added after the eggs. | ||
1238 | 1137 | |||
1239 | 1138 | >>> reset_interpreter() | ||
1240 | 1139 | >>> mkdir(interpreter_dir, 'other') | ||
1241 | 1140 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1242 | 1141 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1243 | 1142 | ... interpreter='py', extra_paths=[join(interpreter_dir, 'other')]) | ||
1244 | 1143 | >>> sys.stdout.write('#\n'); cat(site_path) # doctest: +ELLIPSIS | ||
1245 | 1144 | #... | ||
1246 | 1145 | def addsitepackages(known_paths): | ||
1247 | 1146 | """Add site packages, as determined by zc.buildout. | ||
1248 | 1147 | <BLANKLINE> | ||
1249 | 1148 | See original_addsitepackages, below, for the original version.""" | ||
1250 | 1149 | buildout_paths = [ | ||
1251 | 1150 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1252 | 1151 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', | ||
1253 | 1152 | '/interpreter/other' | ||
1254 | 1153 | ]... | ||
1255 | 1154 | |||
1256 | 1155 | >>> print call_py(interpreter_path, | ||
1257 | 1156 | ... "import sys, pprint; pprint.pprint(sys.path)") | ||
1258 | 1157 | ... # doctest: +ELLIPSIS | ||
1259 | 1158 | ['', | ||
1260 | 1159 | '/interpreter/parts/interpreter', | ||
1261 | 1160 | ..., | ||
1262 | 1161 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1263 | 1162 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', | ||
1264 | 1163 | '/interpreter/other'] | ||
1265 | 1164 | <BLANKLINE> | ||
1266 | 1165 | |||
1267 | 1166 | The ``sitepackage_safe_scripts`` function: using site-packages | ||
1268 | 1167 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
1269 | 1168 | |||
1270 | 1169 | The ``sitepackage_safe_scripts`` function supports including site | ||
1271 | 1170 | packages. This has some advantages and some serious dangers. | ||
1272 | 1171 | |||
1273 | 1172 | A typical reason to include site-packages is that it is easier to | ||
1274 | 1173 | install one or more dependencies in your Python than it is with | ||
1275 | 1174 | buildout. Some packages, such as lxml or Python PostgreSQL integration, | ||
1276 | 1175 | have dependencies that can be much easier to build and/or install using | ||
1277 | 1176 | other mechanisms, such as your operating system's package manager. By | ||
1278 | 1177 | installing some core packages into your Python's site-packages, this can | ||
1279 | 1178 | significantly simplify some application installations. | ||
1280 | 1179 | |||
1281 | 1180 | However, doing this has a significant danger. One of the primary goals | ||
1282 | 1181 | of buildout is to provide repeatability. Some packages (one of the | ||
1283 | 1182 | better known Python openid packages, for instance) change their behavior | ||
1284 | 1183 | depending on what packages are available. If Python curl bindings are | ||
1285 | 1184 | available, these may be preferred by the library. If a certain XML | ||
1286 | 1185 | package is installed, it may be preferred by the library. These hidden | ||
1287 | 1186 | choices may cause small or large behavior differences. The fact that | ||
1288 | 1187 | they can be rarely encountered can actually make it worse: you forget | ||
1289 | 1188 | that this might be a problem, and debugging the differences can be | ||
1290 | 1189 | difficult. If you allow site-packages to be included in your buildout, | ||
1291 | 1190 | and the Python you use is not managed precisely by your application (for | ||
1292 | 1191 | instance, it is a system Python), you open yourself up to these | ||
1293 | 1192 | possibilities. Don't be unaware of the dangers. | ||
1294 | 1193 | |||
1295 | 1194 | That explained, let's see how it works. If you don't use namespace packages, | ||
1296 | 1195 | this is very straightforward. | ||
1297 | 1196 | |||
1298 | 1197 | >>> reset_interpreter() | ||
1299 | 1198 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1300 | 1199 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1301 | 1200 | ... interpreter='py', add_site_packages=True) | ||
1302 | 1201 | >>> sys.stdout.write('#\n'); cat(site_path) | ||
1303 | 1202 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1304 | 1203 | #... | ||
1305 | 1204 | def addsitepackages(known_paths): | ||
1306 | 1205 | """Add site packages, as determined by zc.buildout. | ||
1307 | 1206 | <BLANKLINE> | ||
1308 | 1207 | See original_addsitepackages, below, for the original version.""" | ||
1309 | 1208 | buildout_paths = [ | ||
1310 | 1209 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1311 | 1210 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg' | ||
1312 | 1211 | ] | ||
1313 | 1212 | for path in buildout_paths: | ||
1314 | 1213 | sitedir, sitedircase = makepath(path) | ||
1315 | 1214 | if not sitedircase in known_paths and os.path.exists(sitedir): | ||
1316 | 1215 | sys.path.append(sitedir) | ||
1317 | 1216 | known_paths.add(sitedircase) | ||
1318 | 1217 | original_paths = [ | ||
1319 | 1218 | ... | ||
1320 | 1219 | ] | ||
1321 | 1220 | for path in original_paths: | ||
1322 | 1221 | addsitedir(path, known_paths) | ||
1323 | 1222 | return known_paths | ||
1324 | 1223 | <BLANKLINE> | ||
1325 | 1224 | def original_addsitepackages(known_paths):... | ||
1326 | 1225 | |||
1327 | 1226 | It simply adds the original paths using addsitedir after the code to add the | ||
1328 | 1227 | buildout paths. | ||
1329 | 1228 | |||
1330 | 1229 | Here's an example of the new script in use. Other documents and tests in | ||
1331 | 1230 | this package give the feature a more thorough workout, but this should | ||
1332 | 1231 | give you an idea of the feature. | ||
1333 | 1232 | |||
1334 | 1233 | >>> res = call_py(interpreter_path, "import sys; print sys.path") | ||
1335 | 1234 | >>> print res # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1336 | 1235 | ['', | ||
1337 | 1236 | '/interpreter/parts/interpreter', | ||
1338 | 1237 | ..., | ||
1339 | 1238 | '/interpreter/eggs/demo-0.3-py2.4.egg', | ||
1340 | 1239 | '/interpreter/eggs/demoneeded-1.1-py2.4.egg', | ||
1341 | 1240 | ...] | ||
1342 | 1241 | <BLANKLINE> | ||
1343 | 1242 | |||
1344 | 1243 | The clean_paths gathered earlier is a subset of this full list of paths. | ||
1345 | 1244 | |||
1346 | 1245 | >>> full_paths = eval(res.strip()) | ||
1347 | 1246 | >>> len(clean_paths) < len(full_paths) | ||
1348 | 1247 | True | ||
1349 | 1248 | >>> set(os.path.normpath(p) for p in clean_paths).issubset( | ||
1350 | 1249 | ... os.path.normpath(p) for p in full_paths) | ||
1351 | 1250 | True | ||
1352 | 1251 | |||
1353 | 1252 | Unfortunately, because of how setuptools namespace packages are implemented | ||
1354 | 1253 | differently for operating system packages (debs or rpms) as opposed to | ||
1355 | 1254 | standard setuptools installation, there's a slightly trickier dance if you | ||
1356 | 1255 | use them. To show this we'll needs some extra eggs that use namespaces. | ||
1357 | 1256 | We'll use the ``tellmy.fortune`` package, which we'll need to make an initial | ||
1358 | 1257 | call to another text fixture to create. | ||
1359 | 1258 | |||
1360 | 1259 | >>> from zc.buildout.tests import create_sample_namespace_eggs | ||
1361 | 1260 | >>> namespace_eggs = tmpdir('namespace_eggs') | ||
1362 | 1261 | >>> create_sample_namespace_eggs(namespace_eggs) | ||
1363 | 1262 | |||
1364 | 1263 | >>> reset_interpreter() | ||
1365 | 1264 | >>> ws = zc.buildout.easy_install.install( | ||
1366 | 1265 | ... ['demo', 'tellmy.fortune'], join(interpreter_dir, 'eggs'), | ||
1367 | 1266 | ... links=[link_server, namespace_eggs], index=link_server+'index/') | ||
1368 | 1267 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1369 | 1268 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1370 | 1269 | ... interpreter='py', add_site_packages=True) | ||
1371 | 1270 | >>> sys.stdout.write('#\n'); cat(site_path) | ||
1372 | 1271 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1373 | 1272 | #... | ||
1374 | 1273 | def addsitepackages(known_paths): | ||
1375 | 1274 | """Add site packages, as determined by zc.buildout. | ||
1376 | 1275 | <BLANKLINE> | ||
1377 | 1276 | See original_addsitepackages, below, for the original version.""" | ||
1378 | 1277 | setuptools_path = '...setuptools...' | ||
1379 | 1278 | sys.path.append(setuptools_path) | ||
1380 | 1279 | known_paths.add(os.path.normcase(setuptools_path)) | ||
1381 | 1280 | import pkg_resources | ||
1382 | 1281 | buildout_paths = [ | ||
1383 | 1282 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1384 | 1283 | '/interpreter/eggs/tellmy.fortune-1.0-pyN.N.egg', | ||
1385 | 1284 | '...setuptools...', | ||
1386 | 1285 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg' | ||
1387 | 1286 | ] | ||
1388 | 1287 | for path in buildout_paths: | ||
1389 | 1288 | sitedir, sitedircase = makepath(path) | ||
1390 | 1289 | if not sitedircase in known_paths and os.path.exists(sitedir): | ||
1391 | 1290 | sys.path.append(sitedir) | ||
1392 | 1291 | known_paths.add(sitedircase) | ||
1393 | 1292 | pkg_resources.working_set.add_entry(sitedir) | ||
1394 | 1293 | original_paths = [ | ||
1395 | 1294 | ... | ||
1396 | 1295 | ] | ||
1397 | 1296 | for path in original_paths: | ||
1398 | 1297 | addsitedir(path, known_paths) | ||
1399 | 1298 | return known_paths | ||
1400 | 1299 | <BLANKLINE> | ||
1401 | 1300 | def original_addsitepackages(known_paths):... | ||
1402 | 1301 | |||
1403 | 1302 | >>> print call_py(interpreter_path, "import sys; print sys.path") | ||
1404 | 1303 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1405 | 1304 | ['', | ||
1406 | 1305 | '/interpreter/parts/interpreter', | ||
1407 | 1306 | ..., | ||
1408 | 1307 | '...setuptools...', | ||
1409 | 1308 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1410 | 1309 | '/interpreter/eggs/tellmy.fortune-1.0-pyN.N.egg', | ||
1411 | 1310 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', | ||
1412 | 1311 | ...] | ||
1413 | 1312 | |||
1414 | 1313 | As you can see, the script now first imports pkg_resources. Then we | ||
1415 | 1314 | need to process egg files specially to look for namespace packages there | ||
1416 | 1315 | *before* we process process lines in .pth files that use the "import" | ||
1417 | 1316 | feature--lines that might be part of the setuptools namespace package | ||
1418 | 1317 | implementation for system packages, as mentioned above, and that must | ||
1419 | 1318 | come after processing egg namespaces. | ||
1420 | 1319 | |||
1421 | 1320 | The most complex that this function gets is if you use namespace packages, | ||
1422 | 1321 | include site-packages, and use relative paths. For completeness, we'll look | ||
1423 | 1322 | at that result. | ||
1424 | 1323 | |||
1425 | 1324 | >>> reset_interpreter() | ||
1426 | 1325 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1427 | 1326 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1428 | 1327 | ... interpreter='py', add_site_packages=True, | ||
1429 | 1328 | ... relative_paths=interpreter_dir) | ||
1430 | 1329 | >>> sys.stdout.write('#\n'); cat(site_path) | ||
1431 | 1330 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1432 | 1331 | #... | ||
1433 | 1332 | def addsitepackages(known_paths): | ||
1434 | 1333 | """Add site packages, as determined by zc.buildout. | ||
1435 | 1334 | <BLANKLINE> | ||
1436 | 1335 | See original_addsitepackages, below, for the original version.""" | ||
1437 | 1336 | join = os.path.join | ||
1438 | 1337 | base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) | ||
1439 | 1338 | base = os.path.dirname(base) | ||
1440 | 1339 | base = os.path.dirname(base) | ||
1441 | 1340 | setuptools_path = '...setuptools...' | ||
1442 | 1341 | sys.path.append(setuptools_path) | ||
1443 | 1342 | known_paths.add(os.path.normcase(setuptools_path)) | ||
1444 | 1343 | import pkg_resources | ||
1445 | 1344 | buildout_paths = [ | ||
1446 | 1345 | join(base, 'eggs/demo-0.3-pyN.N.egg'), | ||
1447 | 1346 | join(base, 'eggs/tellmy.fortune-1.0-pyN.N.egg'), | ||
1448 | 1347 | '...setuptools...', | ||
1449 | 1348 | join(base, 'eggs/demoneeded-1.1-pyN.N.egg') | ||
1450 | 1349 | ] | ||
1451 | 1350 | for path in buildout_paths: | ||
1452 | 1351 | sitedir, sitedircase = makepath(path) | ||
1453 | 1352 | if not sitedircase in known_paths and os.path.exists(sitedir): | ||
1454 | 1353 | sys.path.append(sitedir) | ||
1455 | 1354 | known_paths.add(sitedircase) | ||
1456 | 1355 | pkg_resources.working_set.add_entry(sitedir) | ||
1457 | 1356 | original_paths = [ | ||
1458 | 1357 | ... | ||
1459 | 1358 | ] | ||
1460 | 1359 | for path in original_paths: | ||
1461 | 1360 | addsitedir(path, known_paths) | ||
1462 | 1361 | return known_paths | ||
1463 | 1362 | <BLANKLINE> | ||
1464 | 1363 | def original_addsitepackages(known_paths):... | ||
1465 | 1364 | |||
1466 | 1365 | >>> print call_py(interpreter_path, "import sys; print sys.path") | ||
1467 | 1366 | ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE | ||
1468 | 1367 | ['', | ||
1469 | 1368 | '/interpreter/parts/interpreter', | ||
1470 | 1369 | ..., | ||
1471 | 1370 | '...setuptools...', | ||
1472 | 1371 | '/interpreter/eggs/demo-0.3-pyN.N.egg', | ||
1473 | 1372 | '/interpreter/eggs/tellmy.fortune-1.0-pyN.N.egg', | ||
1474 | 1373 | '/interpreter/eggs/demoneeded-1.1-pyN.N.egg', | ||
1475 | 1374 | ...] | ||
1476 | 1375 | |||
1477 | 1376 | The ``exec_sitecustomize`` argument does the same thing for the | ||
1478 | 1377 | sitecustomize module--it allows you to include the code from the | ||
1479 | 1378 | sitecustomize module in the underlying Python if you set the argument to | ||
1480 | 1379 | True. The z3c.recipe.scripts package sets up the full environment necessary | ||
1481 | 1380 | to demonstrate this piece. | ||
1482 | 1381 | |||
1483 | 1382 | The ``sitepackage_safe_scripts`` function: writing scripts for entry points | ||
1484 | 1383 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
1485 | 1384 | |||
1486 | 1385 | All of the examples so far for this function have been creating | ||
1487 | 1386 | interpreters. The function can also write scripts for entry | ||
1488 | 1387 | points. They are almost identical to the scripts that we saw for the | ||
1489 | 1388 | ``scripts`` function except that they ``import site`` after setting the | ||
1490 | 1389 | sys.path to include our custom site.py and sitecustomize.py files. These | ||
1491 | 1390 | files then initialize the Python environment as we have already seen. Let's | ||
1492 | 1391 | see a simple example. | ||
1493 | 1392 | |||
1494 | 1393 | >>> reset_interpreter() | ||
1495 | 1394 | >>> ws = zc.buildout.easy_install.install( | ||
1496 | 1395 | ... ['demo'], join(interpreter_dir, 'eggs'), links=[link_server], | ||
1497 | 1396 | ... index=link_server+'index/') | ||
1498 | 1397 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1499 | 1398 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1500 | 1399 | ... reqs=['demo']) | ||
1501 | 1400 | |||
1502 | 1401 | As before, in Windows, 2 files are generated for each script. A script | ||
1503 | 1402 | file, ending in '-script.py', and an exe file that allows the script | ||
1504 | 1403 | to be invoked directly without having to specify the Python | ||
1505 | 1404 | interpreter and without having to provide a '.py' suffix. This is in addition | ||
1506 | 1405 | to the site.py and sitecustomize.py files that are generated as with our | ||
1507 | 1406 | interpreter examples above. | ||
1508 | 1407 | |||
1509 | 1408 | >>> if sys.platform == 'win32': | ||
1510 | 1409 | ... demo_path = os.path.join(interpreter_bin_dir, 'demo-script.py') | ||
1511 | 1410 | ... expected = [sitecustomize_path, | ||
1512 | 1411 | ... site_path, | ||
1513 | 1412 | ... os.path.join(interpreter_bin_dir, 'demo.exe'), | ||
1514 | 1413 | ... demo_path] | ||
1515 | 1414 | ... else: | ||
1516 | 1415 | ... demo_path = os.path.join(interpreter_bin_dir, 'demo') | ||
1517 | 1416 | ... expected = [sitecustomize_path, site_path, demo_path] | ||
1518 | 1417 | ... | ||
1519 | 1418 | >>> assert generated == expected, repr((generated, expected)) | ||
1520 | 1419 | |||
1521 | 1420 | The demo script runs the entry point defined in the demo egg: | ||
1522 | 1421 | |||
1523 | 1422 | >>> cat(demo_path) # doctest: +NORMALIZE_WHITESPACE | ||
1524 | 1423 | #!/usr/local/bin/python2.4 -S | ||
1525 | 1424 | <BLANKLINE> | ||
1526 | 1425 | import sys | ||
1527 | 1426 | sys.path[0:0] = [ | ||
1528 | 1427 | '/interpreter/parts/interpreter', | ||
1529 | 1428 | ] | ||
1530 | 1429 | <BLANKLINE> | ||
1531 | 1430 | <BLANKLINE> | ||
1532 | 1431 | import site # imports custom buildout-generated site.py | ||
1533 | 1432 | <BLANKLINE> | ||
1534 | 1433 | import eggrecipedemo | ||
1535 | 1434 | <BLANKLINE> | ||
1536 | 1435 | if __name__ == '__main__': | ||
1537 | 1436 | eggrecipedemo.main() | ||
1538 | 1437 | |||
1539 | 1438 | >>> demo_call = join(interpreter_bin_dir, 'demo') | ||
1540 | 1439 | >>> if sys.platform == 'win32': | ||
1541 | 1440 | ... demo_call = '"%s"' % demo_call | ||
1542 | 1441 | >>> print system(demo_call) | ||
1543 | 1442 | 3 1 | ||
1544 | 1443 | <BLANKLINE> | ||
1545 | 1444 | |||
1546 | 1445 | There are a few differences from the ``scripts`` function. First, the | ||
1547 | 1446 | ``reqs`` argument (an iterable of string requirements or entry point | ||
1548 | 1447 | tuples) is a keyword argument here. We see that in the example above. | ||
1549 | 1448 | Second, the ``arguments`` argument is now named ``script_arguments`` to | ||
1550 | 1449 | try and clarify that it does not affect interpreters. While the | ||
1551 | 1450 | ``initialization`` argument continues to affect both the interpreters | ||
1552 | 1451 | and the entry point scripts, if you have initialization that is only | ||
1553 | 1452 | pertinent to the entry point scripts, you can use the | ||
1554 | 1453 | ``script_initialization`` argument. | ||
1555 | 1454 | |||
1556 | 1455 | Let's see ``script_arguments`` and ``script_initialization`` in action. | ||
1557 | 1456 | |||
1558 | 1457 | >>> reset_interpreter() | ||
1559 | 1458 | >>> generated = zc.buildout.easy_install.sitepackage_safe_scripts( | ||
1560 | 1459 | ... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir, | ||
1561 | 1460 | ... reqs=['demo'], script_arguments='1, 2', | ||
1562 | 1461 | ... script_initialization='import os\nos.chdir("foo")') | ||
1563 | 1462 | |||
1564 | 1463 | >>> cat(demo_path) # doctest: +NORMALIZE_WHITESPACE | ||
1565 | 1464 | #!/usr/local/bin/python2.4 -S | ||
1566 | 1465 | import sys | ||
1567 | 1466 | sys.path[0:0] = [ | ||
1568 | 1467 | '/interpreter/parts/interpreter', | ||
1569 | 1468 | ] | ||
1570 | 1469 | <BLANKLINE> | ||
1571 | 1470 | import site # imports custom buildout-generated site.py | ||
1572 | 1471 | import os | ||
1573 | 1472 | os.chdir("foo") | ||
1574 | 1473 | <BLANKLINE> | ||
1575 | 1474 | import eggrecipedemo | ||
1576 | 1475 | <BLANKLINE> | ||
1577 | 1476 | if __name__ == '__main__': | ||
1578 | 1477 | eggrecipedemo.main(1, 2) | ||
1579 | 914 | 1478 | ||
1580 | 915 | Handling custom build options for extensions provided in source distributions | 1479 | Handling custom build options for extensions provided in source distributions |
1581 | 916 | ----------------------------------------------------------------------------- | 1480 | ----------------------------------------------------------------------------- |
1582 | 917 | 1481 | ||
1583 | === modified file 'src/zc/buildout/testing.py' | |||
1584 | --- src/zc/buildout/testing.py 2010-02-23 20:41:17 +0000 | |||
1585 | +++ src/zc/buildout/testing.py 2010-02-23 20:41:17 +0000 | |||
1586 | @@ -28,6 +28,7 @@ | |||
1587 | 28 | import subprocess | 28 | import subprocess |
1588 | 29 | import sys | 29 | import sys |
1589 | 30 | import tempfile | 30 | import tempfile |
1590 | 31 | import textwrap | ||
1591 | 31 | import threading | 32 | import threading |
1592 | 32 | import time | 33 | import time |
1593 | 33 | import urllib2 | 34 | import urllib2 |
1594 | @@ -105,6 +106,16 @@ | |||
1595 | 105 | e.close() | 106 | e.close() |
1596 | 106 | return result | 107 | return result |
1597 | 107 | 108 | ||
1598 | 109 | def call_py(interpreter, cmd, flags=None): | ||
1599 | 110 | if sys.platform == 'win32': | ||
1600 | 111 | args = ['"%s"' % arg for arg in (interpreter, flags, cmd) if arg] | ||
1601 | 112 | args.insert(-1, '"-c"') | ||
1602 | 113 | return system('"%s"' % ' '.join(args)) | ||
1603 | 114 | else: | ||
1604 | 115 | cmd = repr(cmd) | ||
1605 | 116 | return system( | ||
1606 | 117 | ' '.join(arg for arg in (interpreter, flags, '-c', cmd) if arg)) | ||
1607 | 118 | |||
1608 | 108 | def get(url): | 119 | def get(url): |
1609 | 109 | return urllib2.urlopen(url).read() | 120 | return urllib2.urlopen(url).read() |
1610 | 110 | 121 | ||
1611 | @@ -116,7 +127,11 @@ | |||
1612 | 116 | args = [zc.buildout.easy_install._safe_arg(arg) | 127 | args = [zc.buildout.easy_install._safe_arg(arg) |
1613 | 117 | for arg in args] | 128 | for arg in args] |
1614 | 118 | args.insert(0, '-q') | 129 | args.insert(0, '-q') |
1616 | 119 | args.append(dict(os.environ, PYTHONPATH=setuptools_location)) | 130 | env = dict(os.environ) |
1617 | 131 | if executable == sys.executable: | ||
1618 | 132 | env['PYTHONPATH'] = setuptools_location | ||
1619 | 133 | # else pass an executable that has setuptools! See testselectingpython.py. | ||
1620 | 134 | args.append(env) | ||
1621 | 120 | 135 | ||
1622 | 121 | here = os.getcwd() | 136 | here = os.getcwd() |
1623 | 122 | try: | 137 | try: |
1624 | @@ -135,6 +150,11 @@ | |||
1625 | 135 | def bdist_egg(setup, executable, dest): | 150 | def bdist_egg(setup, executable, dest): |
1626 | 136 | _runsetup(setup, executable, 'bdist_egg', '-d', dest) | 151 | _runsetup(setup, executable, 'bdist_egg', '-d', dest) |
1627 | 137 | 152 | ||
1628 | 153 | def sys_install(setup, dest): | ||
1629 | 154 | _runsetup(setup, sys.executable, 'install', '--install-purelib', dest, | ||
1630 | 155 | '--record', os.path.join(dest, '__added_files__'), | ||
1631 | 156 | '--single-version-externally-managed') | ||
1632 | 157 | |||
1633 | 138 | def find_python(version): | 158 | def find_python(version): |
1634 | 139 | e = os.environ.get('PYTHON%s' % version) | 159 | e = os.environ.get('PYTHON%s' % version) |
1635 | 140 | if e is not None: | 160 | if e is not None: |
1636 | @@ -202,6 +222,24 @@ | |||
1637 | 202 | time.sleep(0.01) | 222 | time.sleep(0.01) |
1638 | 203 | raise ValueError('Timed out waiting for: '+label) | 223 | raise ValueError('Timed out waiting for: '+label) |
1639 | 204 | 224 | ||
1640 | 225 | def make_buildout(): | ||
1641 | 226 | # Create a basic buildout.cfg to avoid a warning from buildout: | ||
1642 | 227 | open('buildout.cfg', 'w').write( | ||
1643 | 228 | "[buildout]\nparts =\n" | ||
1644 | 229 | ) | ||
1645 | 230 | # Use the buildout bootstrap command to create a buildout | ||
1646 | 231 | zc.buildout.buildout.Buildout( | ||
1647 | 232 | 'buildout.cfg', | ||
1648 | 233 | [('buildout', 'log-level', 'WARNING'), | ||
1649 | 234 | # trick bootstrap into putting the buildout develop egg | ||
1650 | 235 | # in the eggs dir. | ||
1651 | 236 | ('buildout', 'develop-eggs-directory', 'eggs'), | ||
1652 | 237 | ] | ||
1653 | 238 | ).bootstrap([]) | ||
1654 | 239 | # Create the develop-eggs dir, which didn't get created the usual | ||
1655 | 240 | # way due to the trick above: | ||
1656 | 241 | os.mkdir('develop-eggs') | ||
1657 | 242 | |||
1658 | 205 | def buildoutSetUp(test): | 243 | def buildoutSetUp(test): |
1659 | 206 | 244 | ||
1660 | 207 | test.globs['__tear_downs'] = __tear_downs = [] | 245 | test.globs['__tear_downs'] = __tear_downs = [] |
1661 | @@ -255,27 +293,7 @@ | |||
1662 | 255 | sample = tmpdir('sample-buildout') | 293 | sample = tmpdir('sample-buildout') |
1663 | 256 | 294 | ||
1664 | 257 | os.chdir(sample) | 295 | os.chdir(sample) |
1686 | 258 | 296 | make_buildout() | |
1666 | 259 | # Create a basic buildout.cfg to avoid a warning from buildout: | ||
1667 | 260 | open('buildout.cfg', 'w').write( | ||
1668 | 261 | "[buildout]\nparts =\n" | ||
1669 | 262 | ) | ||
1670 | 263 | |||
1671 | 264 | # Use the buildout bootstrap command to create a buildout | ||
1672 | 265 | zc.buildout.buildout.Buildout( | ||
1673 | 266 | 'buildout.cfg', | ||
1674 | 267 | [('buildout', 'log-level', 'WARNING'), | ||
1675 | 268 | # trick bootstrap into putting the buildout develop egg | ||
1676 | 269 | # in the eggs dir. | ||
1677 | 270 | ('buildout', 'develop-eggs-directory', 'eggs'), | ||
1678 | 271 | ] | ||
1679 | 272 | ).bootstrap([]) | ||
1680 | 273 | |||
1681 | 274 | |||
1682 | 275 | |||
1683 | 276 | # Create the develop-eggs dir, which didn't get created the usual | ||
1684 | 277 | # way due to the trick above: | ||
1685 | 278 | os.mkdir('develop-eggs') | ||
1687 | 279 | 297 | ||
1688 | 280 | def start_server(path): | 298 | def start_server(path): |
1689 | 281 | port, thread = _start_server(path, name=path) | 299 | port, thread = _start_server(path, name=path) |
1690 | @@ -283,6 +301,50 @@ | |||
1691 | 283 | register_teardown(lambda: stop_server(url, thread)) | 301 | register_teardown(lambda: stop_server(url, thread)) |
1692 | 284 | return url | 302 | return url |
1693 | 285 | 303 | ||
1694 | 304 | def make_py(initialization=''): | ||
1695 | 305 | """Returns paths to new executable and to its site-packages. | ||
1696 | 306 | """ | ||
1697 | 307 | buildout = tmpdir('executable_buildout') | ||
1698 | 308 | site_packages_dir = os.path.join(buildout, 'site-packages') | ||
1699 | 309 | mkdir(site_packages_dir) | ||
1700 | 310 | old_wd = os.getcwd() | ||
1701 | 311 | os.chdir(buildout) | ||
1702 | 312 | make_buildout() | ||
1703 | 313 | # Normally we don't process .pth files in extra-paths. We want to | ||
1704 | 314 | # in this case so that we can test with setuptools system installs | ||
1705 | 315 | # (--single-version-externally-managed), which use .pth files. | ||
1706 | 316 | initialization = ( | ||
1707 | 317 | ('import sys\n' | ||
1708 | 318 | 'import site\n' | ||
1709 | 319 | 'known_paths = set(sys.path)\n' | ||
1710 | 320 | 'site_packages_dir = %r\n' | ||
1711 | 321 | 'site.addsitedir(site_packages_dir, known_paths)\n' | ||
1712 | 322 | ) % (site_packages_dir,)) + initialization | ||
1713 | 323 | initialization = '\n'.join( | ||
1714 | 324 | ' ' + line for line in initialization.split('\n')) | ||
1715 | 325 | install_develop( | ||
1716 | 326 | 'zc.recipe.egg', os.path.join(buildout, 'develop-eggs')) | ||
1717 | 327 | install_develop( | ||
1718 | 328 | 'z3c.recipe.scripts', os.path.join(buildout, 'develop-eggs')) | ||
1719 | 329 | write('buildout.cfg', textwrap.dedent('''\ | ||
1720 | 330 | [buildout] | ||
1721 | 331 | parts = py | ||
1722 | 332 | |||
1723 | 333 | [py] | ||
1724 | 334 | recipe = z3c.recipe.scripts | ||
1725 | 335 | interpreter = py | ||
1726 | 336 | initialization = | ||
1727 | 337 | %(initialization)s | ||
1728 | 338 | extra-paths = %(site-packages)s | ||
1729 | 339 | eggs = setuptools | ||
1730 | 340 | ''') % { | ||
1731 | 341 | 'initialization': initialization, | ||
1732 | 342 | 'site-packages': site_packages_dir}) | ||
1733 | 343 | system(os.path.join(buildout, 'bin', 'buildout')) | ||
1734 | 344 | os.chdir(old_wd) | ||
1735 | 345 | return ( | ||
1736 | 346 | os.path.join(buildout, 'bin', 'py'), site_packages_dir) | ||
1737 | 347 | |||
1738 | 286 | test.globs.update(dict( | 348 | test.globs.update(dict( |
1739 | 287 | sample_buildout = sample, | 349 | sample_buildout = sample, |
1740 | 288 | ls = ls, | 350 | ls = ls, |
1741 | @@ -293,6 +355,7 @@ | |||
1742 | 293 | tmpdir = tmpdir, | 355 | tmpdir = tmpdir, |
1743 | 294 | write = write, | 356 | write = write, |
1744 | 295 | system = system, | 357 | system = system, |
1745 | 358 | call_py = call_py, | ||
1746 | 296 | get = get, | 359 | get = get, |
1747 | 297 | cd = (lambda *path: os.chdir(os.path.join(*path))), | 360 | cd = (lambda *path: os.chdir(os.path.join(*path))), |
1748 | 298 | join = os.path.join, | 361 | join = os.path.join, |
1749 | @@ -301,6 +364,7 @@ | |||
1750 | 301 | start_server = start_server, | 364 | start_server = start_server, |
1751 | 302 | buildout = os.path.join(sample, 'bin', 'buildout'), | 365 | buildout = os.path.join(sample, 'bin', 'buildout'), |
1752 | 303 | wait_until = wait_until, | 366 | wait_until = wait_until, |
1753 | 367 | make_py = make_py | ||
1754 | 304 | )) | 368 | )) |
1755 | 305 | 369 | ||
1756 | 306 | zc.buildout.easy_install.prefer_final(prefer_final) | 370 | zc.buildout.easy_install.prefer_final(prefer_final) |
1757 | 307 | 371 | ||
1758 | === modified file 'src/zc/buildout/tests.py' | |||
1759 | --- src/zc/buildout/tests.py 2010-02-23 20:41:17 +0000 | |||
1760 | +++ src/zc/buildout/tests.py 2010-02-23 20:41:17 +0000 | |||
1761 | @@ -53,6 +53,7 @@ | |||
1762 | 53 | 53 | ||
1763 | 54 | >>> ls('develop-eggs') | 54 | >>> ls('develop-eggs') |
1764 | 55 | - foo.egg-link | 55 | - foo.egg-link |
1765 | 56 | - z3c.recipe.scripts.egg-link | ||
1766 | 56 | - zc.recipe.egg.egg-link | 57 | - zc.recipe.egg.egg-link |
1767 | 57 | 58 | ||
1768 | 58 | """ | 59 | """ |
1769 | @@ -84,6 +85,7 @@ | |||
1770 | 84 | 85 | ||
1771 | 85 | >>> ls('develop-eggs') | 86 | >>> ls('develop-eggs') |
1772 | 86 | - foo.egg-link | 87 | - foo.egg-link |
1773 | 88 | - z3c.recipe.scripts.egg-link | ||
1774 | 87 | - zc.recipe.egg.egg-link | 89 | - zc.recipe.egg.egg-link |
1775 | 88 | 90 | ||
1776 | 89 | >>> print system(join('bin', 'buildout')+' -vvv'), # doctest: +ELLIPSIS | 91 | >>> print system(join('bin', 'buildout')+' -vvv'), # doctest: +ELLIPSIS |
1777 | @@ -668,6 +670,7 @@ | |||
1778 | 668 | 670 | ||
1779 | 669 | >>> ls('develop-eggs') | 671 | >>> ls('develop-eggs') |
1780 | 670 | - foox.egg-link | 672 | - foox.egg-link |
1781 | 673 | - z3c.recipe.scripts.egg-link | ||
1782 | 671 | - zc.recipe.egg.egg-link | 674 | - zc.recipe.egg.egg-link |
1783 | 672 | 675 | ||
1784 | 673 | Create another: | 676 | Create another: |
1785 | @@ -692,6 +695,7 @@ | |||
1786 | 692 | >>> ls('develop-eggs') | 695 | >>> ls('develop-eggs') |
1787 | 693 | - foox.egg-link | 696 | - foox.egg-link |
1788 | 694 | - fooy.egg-link | 697 | - fooy.egg-link |
1789 | 698 | - z3c.recipe.scripts.egg-link | ||
1790 | 695 | - zc.recipe.egg.egg-link | 699 | - zc.recipe.egg.egg-link |
1791 | 696 | 700 | ||
1792 | 697 | Remove one: | 701 | Remove one: |
1793 | @@ -709,6 +713,7 @@ | |||
1794 | 709 | 713 | ||
1795 | 710 | >>> ls('develop-eggs') | 714 | >>> ls('develop-eggs') |
1796 | 711 | - fooy.egg-link | 715 | - fooy.egg-link |
1797 | 716 | - z3c.recipe.scripts.egg-link | ||
1798 | 712 | - zc.recipe.egg.egg-link | 717 | - zc.recipe.egg.egg-link |
1799 | 713 | 718 | ||
1800 | 714 | Remove the other: | 719 | Remove the other: |
1801 | @@ -723,6 +728,7 @@ | |||
1802 | 723 | All gone | 728 | All gone |
1803 | 724 | 729 | ||
1804 | 725 | >>> ls('develop-eggs') | 730 | >>> ls('develop-eggs') |
1805 | 731 | - z3c.recipe.scripts.egg-link | ||
1806 | 726 | - zc.recipe.egg.egg-link | 732 | - zc.recipe.egg.egg-link |
1807 | 727 | ''' | 733 | ''' |
1808 | 728 | 734 | ||
1809 | @@ -797,6 +803,7 @@ | |||
1810 | 797 | ... + join(sample_buildout, 'eggs')) | 803 | ... + join(sample_buildout, 'eggs')) |
1811 | 798 | 804 | ||
1812 | 799 | >>> ls('develop-eggs') | 805 | >>> ls('develop-eggs') |
1813 | 806 | - z3c.recipe.scripts.egg-link | ||
1814 | 800 | - zc.recipe.egg.egg-link | 807 | - zc.recipe.egg.egg-link |
1815 | 801 | 808 | ||
1816 | 802 | >>> ls('eggs') # doctest: +ELLIPSIS | 809 | >>> ls('eggs') # doctest: +ELLIPSIS |
1817 | @@ -1769,6 +1776,235 @@ | |||
1818 | 1769 | 1 2 | 1776 | 1 2 |
1819 | 1770 | """ | 1777 | """ |
1820 | 1771 | 1778 | ||
1821 | 1779 | def versions_section_ignored_for_dependency_in_favor_of_site_packages(): | ||
1822 | 1780 | r""" | ||
1823 | 1781 | This is a test for a bugfix. | ||
1824 | 1782 | |||
1825 | 1783 | The error showed itself when at least two dependencies were in a shared | ||
1826 | 1784 | location like site-packages, and the first one met the "versions" setting. The | ||
1827 | 1785 | first dependency would be added, but subsequent dependencies from the same | ||
1828 | 1786 | location (e.g., site-packages) would use the version of the package found in | ||
1829 | 1787 | the shared location, ignoring the version setting. | ||
1830 | 1788 | |||
1831 | 1789 | We begin with a Python that has demoneeded version 1.1 installed and a | ||
1832 | 1790 | demo version 0.3, all in a site-packages-like shared directory. We need | ||
1833 | 1791 | to create this. ``eggrecipedemo.main()`` shows the number after the dot | ||
1834 | 1792 | (that is, ``X`` in ``1.X``), for the demo package and the demoneeded | ||
1835 | 1793 | package, so this demonstrates that our Python does in fact have demo | ||
1836 | 1794 | version 0.3 and demoneeded version 1.1. | ||
1837 | 1795 | |||
1838 | 1796 | >>> py_path = make_py_with_system_install(make_py, sample_eggs) | ||
1839 | 1797 | >>> print call_py( | ||
1840 | 1798 | ... py_path, | ||
1841 | 1799 | ... "import tellmy.version; print tellmy.version.__version__"), | ||
1842 | 1800 | 1.1 | ||
1843 | 1801 | |||
1844 | 1802 | Now here's a setup that would expose the bug, using the | ||
1845 | 1803 | zc.buildout.easy_install API. | ||
1846 | 1804 | |||
1847 | 1805 | >>> example_dest = tmpdir('example_dest') | ||
1848 | 1806 | >>> workingset = zc.buildout.easy_install.install( | ||
1849 | 1807 | ... ['tellmy.version'], example_dest, links=[sample_eggs], | ||
1850 | 1808 | ... executable=py_path, | ||
1851 | 1809 | ... index=None, | ||
1852 | 1810 | ... versions={'tellmy.version': '1.0'}) | ||
1853 | 1811 | >>> for dist in workingset: | ||
1854 | 1812 | ... res = str(dist) | ||
1855 | 1813 | ... if res.startswith('tellmy.version'): | ||
1856 | 1814 | ... print res | ||
1857 | 1815 | ... break | ||
1858 | 1816 | tellmy.version 1.0 | ||
1859 | 1817 | |||
1860 | 1818 | Before the bugfix, the desired tellmy.version distribution would have | ||
1861 | 1819 | been blocked the one in site-packages. | ||
1862 | 1820 | """ | ||
1863 | 1821 | |||
1864 | 1822 | def handle_namespace_package_in_both_site_packages_and_buildout_eggs(): | ||
1865 | 1823 | r""" | ||
1866 | 1824 | If you have the same namespace package in both site-packages and in | ||
1867 | 1825 | buildout, we need to be very careful that faux-Python-executables and | ||
1868 | 1826 | scripts generated by easy_install.sitepackage_safe_scripts correctly | ||
1869 | 1827 | combine the two. We show this with the local recipe that uses the | ||
1870 | 1828 | function, z3c.recipe.scripts. | ||
1871 | 1829 | |||
1872 | 1830 | To demonstrate this, we will create three packages: tellmy.version 1.0, | ||
1873 | 1831 | tellmy.version 1.1, and tellmy.fortune 1.0. tellmy.version 1.1 is installed. | ||
1874 | 1832 | |||
1875 | 1833 | >>> py_path = make_py_with_system_install(make_py, sample_eggs) | ||
1876 | 1834 | >>> print call_py( | ||
1877 | 1835 | ... py_path, | ||
1878 | 1836 | ... "import tellmy.version; print tellmy.version.__version__") | ||
1879 | 1837 | 1.1 | ||
1880 | 1838 | <BLANKLINE> | ||
1881 | 1839 | |||
1882 | 1840 | Now we will create a buildout that creates a script and a faux-Python script. | ||
1883 | 1841 | We want to see that both can successfully import the specified versions of | ||
1884 | 1842 | tellmy.version and tellmy.fortune. | ||
1885 | 1843 | |||
1886 | 1844 | >>> write('buildout.cfg', | ||
1887 | 1845 | ... ''' | ||
1888 | 1846 | ... [buildout] | ||
1889 | 1847 | ... parts = eggs | ||
1890 | 1848 | ... find-links = %(link_server)s | ||
1891 | 1849 | ... | ||
1892 | 1850 | ... [primed_python] | ||
1893 | 1851 | ... executable = %(py_path)s | ||
1894 | 1852 | ... | ||
1895 | 1853 | ... [eggs] | ||
1896 | 1854 | ... recipe = z3c.recipe.scripts | ||
1897 | 1855 | ... python = primed_python | ||
1898 | 1856 | ... interpreter = py | ||
1899 | 1857 | ... add-site-packages = true | ||
1900 | 1858 | ... eggs = tellmy.version == 1.0 | ||
1901 | 1859 | ... tellmy.fortune == 1.0 | ||
1902 | 1860 | ... demo | ||
1903 | 1861 | ... script-initialization = | ||
1904 | 1862 | ... import tellmy.version | ||
1905 | 1863 | ... print tellmy.version.__version__ | ||
1906 | 1864 | ... import tellmy.fortune | ||
1907 | 1865 | ... print tellmy.fortune.__version__ | ||
1908 | 1866 | ... ''' % globals()) | ||
1909 | 1867 | |||
1910 | 1868 | >>> print system(buildout) | ||
1911 | 1869 | Installing eggs. | ||
1912 | 1870 | Getting distribution for 'tellmy.version==1.0'. | ||
1913 | 1871 | Got tellmy.version 1.0. | ||
1914 | 1872 | Getting distribution for 'tellmy.fortune==1.0'. | ||
1915 | 1873 | Got tellmy.fortune 1.0. | ||
1916 | 1874 | Getting distribution for 'demo'. | ||
1917 | 1875 | Got demo 0.4c1. | ||
1918 | 1876 | Getting distribution for 'demoneeded'. | ||
1919 | 1877 | Got demoneeded 1.2c1. | ||
1920 | 1878 | Generated script '/sample-buildout/bin/demo'. | ||
1921 | 1879 | Generated interpreter '/sample-buildout/bin/py'. | ||
1922 | 1880 | <BLANKLINE> | ||
1923 | 1881 | |||
1924 | 1882 | Finally, we are ready for the actual test. Prior to the bug fix that | ||
1925 | 1883 | this tests, the results of both calls below was the following:: | ||
1926 | 1884 | |||
1927 | 1885 | 1.1 | ||
1928 | 1886 | Traceback (most recent call last): | ||
1929 | 1887 | ... | ||
1930 | 1888 | ImportError: No module named fortune | ||
1931 | 1889 | <BLANKLINE> | ||
1932 | 1890 | |||
1933 | 1891 | In other words, we got the site-packages version of tellmy.version, and | ||
1934 | 1892 | we could not import tellmy.fortune at all. The following are the correct | ||
1935 | 1893 | results for the interpreter and for the script. | ||
1936 | 1894 | |||
1937 | 1895 | >>> print call_py( | ||
1938 | 1896 | ... join('bin', 'py'), | ||
1939 | 1897 | ... "import tellmy.version; " + | ||
1940 | 1898 | ... "print tellmy.version.__version__; " + | ||
1941 | 1899 | ... "import tellmy.fortune; " + | ||
1942 | 1900 | ... "print tellmy.fortune.__version__") # doctest: +ELLIPSIS | ||
1943 | 1901 | 1.0 | ||
1944 | 1902 | 1.0... | ||
1945 | 1903 | |||
1946 | 1904 | >>> print system(join('bin', 'demo')) | ||
1947 | 1905 | 1.0 | ||
1948 | 1906 | 1.0 | ||
1949 | 1907 | 4 2 | ||
1950 | 1908 | <BLANKLINE> | ||
1951 | 1909 | """ | ||
1952 | 1910 | |||
1953 | 1911 | def handle_sys_path_version_hack(): | ||
1954 | 1912 | r""" | ||
1955 | 1913 | This is a test for a bugfix. | ||
1956 | 1914 | |||
1957 | 1915 | If you use a Python that has a different version of one of your | ||
1958 | 1916 | dependencies, and the new package tries to do sys.path tricks in the | ||
1959 | 1917 | setup.py to get a __version__, and it uses namespace packages, the older | ||
1960 | 1918 | package will be loaded first, making the setup version the wrong number. | ||
1961 | 1919 | While very arguably packages simply shouldn't do this, some do, and we | ||
1962 | 1920 | don't want buildout to fall over when they do. | ||
1963 | 1921 | |||
1964 | 1922 | To demonstrate this, we will need to create a distribution that has one of | ||
1965 | 1923 | these unpleasant tricks, and a Python that has an older version installed. | ||
1966 | 1924 | |||
1967 | 1925 | >>> py_path, site_packages_path = make_py() | ||
1968 | 1926 | >>> for version in ('1.0', '1.1'): | ||
1969 | 1927 | ... tmp = tempfile.mkdtemp() | ||
1970 | 1928 | ... try: | ||
1971 | 1929 | ... write(tmp, 'README.txt', '') | ||
1972 | 1930 | ... mkdir(tmp, 'src') | ||
1973 | 1931 | ... mkdir(tmp, 'src', 'tellmy') | ||
1974 | 1932 | ... write(tmp, 'src', 'tellmy', '__init__.py', | ||
1975 | 1933 | ... "__import__(" | ||
1976 | 1934 | ... "'pkg_resources').declare_namespace(__name__)\n") | ||
1977 | 1935 | ... mkdir(tmp, 'src', 'tellmy', 'version') | ||
1978 | 1936 | ... write(tmp, 'src', 'tellmy', 'version', | ||
1979 | 1937 | ... '__init__.py', '__version__=%r\n' % version) | ||
1980 | 1938 | ... write( | ||
1981 | 1939 | ... tmp, 'setup.py', | ||
1982 | 1940 | ... "from setuptools import setup\n" | ||
1983 | 1941 | ... "import sys\n" | ||
1984 | 1942 | ... "sys.path.insert(0, 'src')\n" | ||
1985 | 1943 | ... "from tellmy.version import __version__\n" | ||
1986 | 1944 | ... "setup(\n" | ||
1987 | 1945 | ... " name='tellmy.version',\n" | ||
1988 | 1946 | ... " package_dir = {'': 'src'},\n" | ||
1989 | 1947 | ... " packages = ['tellmy', 'tellmy.version'],\n" | ||
1990 | 1948 | ... " install_requires = ['setuptools'],\n" | ||
1991 | 1949 | ... " namespace_packages=['tellmy'],\n" | ||
1992 | 1950 | ... " zip_safe=True, version=__version__,\n" | ||
1993 | 1951 | ... " author='bob', url='bob', author_email='bob')\n" | ||
1994 | 1952 | ... ) | ||
1995 | 1953 | ... zc.buildout.testing.sdist(tmp, sample_eggs) | ||
1996 | 1954 | ... if version == '1.0': | ||
1997 | 1955 | ... # We install the 1.0 version in site packages the way a | ||
1998 | 1956 | ... # system packaging system (debs, rpms) would do it. | ||
1999 | 1957 | ... zc.buildout.testing.sys_install(tmp, site_packages_path) | ||
2000 | 1958 | ... finally: | ||
2001 | 1959 | ... shutil.rmtree(tmp) | ||
2002 | 1960 | >>> print call_py( | ||
2003 | 1961 | ... py_path, | ||
2004 | 1962 | ... "import tellmy.version; print tellmy.version.__version__") | ||
2005 | 1963 | 1.0 | ||
2006 | 1964 | <BLANKLINE> | ||
2007 | 1965 | >>> write('buildout.cfg', | ||
2008 | 1966 | ... ''' | ||
2009 | 1967 | ... [buildout] | ||
2010 | 1968 | ... parts = eggs | ||
2011 | 1969 | ... find-links = %(sample_eggs)s | ||
2012 | 1970 | ... | ||
2013 | 1971 | ... [primed_python] | ||
2014 | 1972 | ... executable = %(py_path)s | ||
2015 | 1973 | ... | ||
2016 | 1974 | ... [eggs] | ||
2017 | 1975 | ... recipe = zc.recipe.egg:eggs | ||
2018 | 1976 | ... python = primed_python | ||
2019 | 1977 | ... eggs = tellmy.version == 1.1 | ||
2020 | 1978 | ... ''' % globals()) | ||
2021 | 1979 | |||
2022 | 1980 | Before the bugfix, running this buildout would generate this error: | ||
2023 | 1981 | |||
2024 | 1982 | Installing eggs. | ||
2025 | 1983 | Getting distribution for 'tellmy.version==1.1'. | ||
2026 | 1984 | Installing tellmy.version 1.1 | ||
2027 | 1985 | Caused installation of a distribution: | ||
2028 | 1986 | tellmy.version 1.0 | ||
2029 | 1987 | with a different version. | ||
2030 | 1988 | Got None. | ||
2031 | 1989 | While: | ||
2032 | 1990 | Installing eggs. | ||
2033 | 1991 | Error: There is a version conflict. | ||
2034 | 1992 | We already have: tellmy.version 1.0 | ||
2035 | 1993 | <BLANKLINE> | ||
2036 | 1994 | |||
2037 | 1995 | You can see the copiously commented fix for this in easy_install.py (see | ||
2038 | 1996 | zc.buildout.easy_install.Installer._call_easy_install and particularly | ||
2039 | 1997 | the comment leading up to zc.buildout.easy_install._easy_install_cmd). | ||
2040 | 1998 | Now the install works correctly, as seen here. | ||
2041 | 1999 | |||
2042 | 2000 | >>> print system(buildout) | ||
2043 | 2001 | Installing eggs. | ||
2044 | 2002 | Getting distribution for 'tellmy.version==1.1'. | ||
2045 | 2003 | Got tellmy.version 1.1. | ||
2046 | 2004 | <BLANKLINE> | ||
2047 | 2005 | |||
2048 | 2006 | """ | ||
2049 | 2007 | |||
2050 | 1772 | if sys.version_info > (2, 4): | 2008 | if sys.version_info > (2, 4): |
2051 | 1773 | def test_exit_codes(): | 2009 | def test_exit_codes(): |
2052 | 1774 | """ | 2010 | """ |
2053 | @@ -2367,6 +2603,7 @@ | |||
2054 | 2367 | 2603 | ||
2055 | 2368 | >>> ls('develop-eggs') | 2604 | >>> ls('develop-eggs') |
2056 | 2369 | - foo.egg-link | 2605 | - foo.egg-link |
2057 | 2606 | - z3c.recipe.scripts.egg-link | ||
2058 | 2370 | - zc.recipe.egg.egg-link | 2607 | - zc.recipe.egg.egg-link |
2059 | 2371 | 2608 | ||
2060 | 2372 | """ | 2609 | """ |
2061 | @@ -2654,6 +2891,47 @@ | |||
2062 | 2654 | 2891 | ||
2063 | 2655 | ###################################################################### | 2892 | ###################################################################### |
2064 | 2656 | 2893 | ||
2065 | 2894 | def make_py_with_system_install(make_py, sample_eggs): | ||
2066 | 2895 | py_path, site_packages_path = make_py() | ||
2067 | 2896 | create_sample_namespace_eggs(sample_eggs, site_packages_path) | ||
2068 | 2897 | return py_path | ||
2069 | 2898 | |||
2070 | 2899 | def create_sample_namespace_eggs(dest, site_packages_path=None): | ||
2071 | 2900 | from zc.buildout.testing import write, mkdir | ||
2072 | 2901 | for pkg, version in (('version', '1.0'), ('version', '1.1'), | ||
2073 | 2902 | ('fortune', '1.0')): | ||
2074 | 2903 | tmp = tempfile.mkdtemp() | ||
2075 | 2904 | try: | ||
2076 | 2905 | write(tmp, 'README.txt', '') | ||
2077 | 2906 | mkdir(tmp, 'src') | ||
2078 | 2907 | mkdir(tmp, 'src', 'tellmy') | ||
2079 | 2908 | write(tmp, 'src', 'tellmy', '__init__.py', | ||
2080 | 2909 | "__import__(" | ||
2081 | 2910 | "'pkg_resources').declare_namespace(__name__)\n") | ||
2082 | 2911 | mkdir(tmp, 'src', 'tellmy', pkg) | ||
2083 | 2912 | write(tmp, 'src', 'tellmy', pkg, | ||
2084 | 2913 | '__init__.py', '__version__=%r\n' % version) | ||
2085 | 2914 | write( | ||
2086 | 2915 | tmp, 'setup.py', | ||
2087 | 2916 | "from setuptools import setup\n" | ||
2088 | 2917 | "setup(\n" | ||
2089 | 2918 | " name='tellmy.%(pkg)s',\n" | ||
2090 | 2919 | " package_dir = {'': 'src'},\n" | ||
2091 | 2920 | " packages = ['tellmy', 'tellmy.%(pkg)s'],\n" | ||
2092 | 2921 | " install_requires = ['setuptools'],\n" | ||
2093 | 2922 | " namespace_packages=['tellmy'],\n" | ||
2094 | 2923 | " zip_safe=True, version=%(version)r,\n" | ||
2095 | 2924 | " author='bob', url='bob', author_email='bob')\n" | ||
2096 | 2925 | % locals() | ||
2097 | 2926 | ) | ||
2098 | 2927 | zc.buildout.testing.sdist(tmp, dest) | ||
2099 | 2928 | if (site_packages_path and pkg == 'version' and version == '1.1'): | ||
2100 | 2929 | # We install the 1.1 version in site packages the way a | ||
2101 | 2930 | # system packaging system (debs, rpms) would do it. | ||
2102 | 2931 | zc.buildout.testing.sys_install(tmp, site_packages_path) | ||
2103 | 2932 | finally: | ||
2104 | 2933 | shutil.rmtree(tmp) | ||
2105 | 2934 | |||
2106 | 2657 | def create_sample_eggs(test, executable=sys.executable): | 2935 | def create_sample_eggs(test, executable=sys.executable): |
2107 | 2658 | write = test.globs['write'] | 2936 | write = test.globs['write'] |
2108 | 2659 | dest = test.globs['sample_eggs'] | 2937 | dest = test.globs['sample_eggs'] |
2109 | @@ -2776,6 +3054,7 @@ | |||
2110 | 2776 | test.globs['sample_eggs']) | 3054 | test.globs['sample_eggs']) |
2111 | 2777 | test.globs['update_extdemo'] = lambda : add_source_dist(test, 1.5) | 3055 | test.globs['update_extdemo'] = lambda : add_source_dist(test, 1.5) |
2112 | 2778 | zc.buildout.testing.install_develop('zc.recipe.egg', test) | 3056 | zc.buildout.testing.install_develop('zc.recipe.egg', test) |
2113 | 3057 | zc.buildout.testing.install_develop('z3c.recipe.scripts', test) | ||
2114 | 2779 | 3058 | ||
2115 | 2780 | egg_parse = re.compile('([0-9a-zA-Z_.]+)-([0-9a-zA-Z_.]+)-py(\d[.]\d).egg$' | 3059 | egg_parse = re.compile('([0-9a-zA-Z_.]+)-([0-9a-zA-Z_.]+)-py(\d[.]\d).egg$' |
2116 | 2781 | ).match | 3060 | ).match |
2117 | @@ -2934,6 +3213,10 @@ | |||
2118 | 2934 | (re.compile('[-d] setuptools-\S+[.]egg'), 'setuptools.egg'), | 3213 | (re.compile('[-d] setuptools-\S+[.]egg'), 'setuptools.egg'), |
2119 | 2935 | (re.compile(r'\\[\\]?'), '/'), | 3214 | (re.compile(r'\\[\\]?'), '/'), |
2120 | 2936 | (re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'), | 3215 | (re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'), |
2121 | 3216 | # Normalize generate_script's Windows interpreter to UNIX: | ||
2122 | 3217 | (re.compile(r'\nimport subprocess\n'), '\n'), | ||
2123 | 3218 | (re.compile('subprocess\\.call\\(argv, env=environ\\)'), | ||
2124 | 3219 | 'os.execve(sys.executable, argv, environ)'), | ||
2125 | 2937 | ]+(sys.version_info < (2, 5) and [ | 3220 | ]+(sys.version_info < (2, 5) and [ |
2126 | 2938 | (re.compile('.*No module named runpy.*', re.S), ''), | 3221 | (re.compile('.*No module named runpy.*', re.S), ''), |
2127 | 2939 | (re.compile('.*usage: pdb.py scriptfile .*', re.S), ''), | 3222 | (re.compile('.*usage: pdb.py scriptfile .*', re.S), ''), |
2128 | @@ -3043,6 +3326,8 @@ | |||
2129 | 3043 | zc.buildout.testing.normalize_script, | 3326 | zc.buildout.testing.normalize_script, |
2130 | 3044 | normalize_bang, | 3327 | normalize_bang, |
2131 | 3045 | (re.compile('Downloading.*setuptools.*egg\n'), ''), | 3328 | (re.compile('Downloading.*setuptools.*egg\n'), ''), |
2132 | 3329 | (re.compile('options:'), 'Options:'), | ||
2133 | 3330 | (re.compile('usage:'), 'Usage:'), | ||
2134 | 3046 | ]), | 3331 | ]), |
2135 | 3047 | )) | 3332 | )) |
2136 | 3048 | 3333 | ||
2137 | 3049 | 3334 | ||
2138 | === modified file 'src/zc/buildout/testselectingpython.py' | |||
2139 | --- src/zc/buildout/testselectingpython.py 2009-11-27 13:20:19 +0000 | |||
2140 | +++ src/zc/buildout/testselectingpython.py 2010-02-23 20:41:17 +0000 | |||
2141 | @@ -11,7 +11,7 @@ | |||
2142 | 11 | # FOR A PARTICULAR PURPOSE. | 11 | # FOR A PARTICULAR PURPOSE. |
2143 | 12 | # | 12 | # |
2144 | 13 | ############################################################################## | 13 | ############################################################################## |
2146 | 14 | import os, re, sys, unittest | 14 | import os, re, subprocess, sys, textwrap, unittest |
2147 | 15 | from zope.testing import doctest, renormalizing | 15 | from zope.testing import doctest, renormalizing |
2148 | 16 | import zc.buildout.tests | 16 | import zc.buildout.tests |
2149 | 17 | import zc.buildout.testing | 17 | import zc.buildout.testing |
2150 | @@ -42,6 +42,33 @@ | |||
2151 | 42 | 42 | ||
2152 | 43 | def multi_python(test): | 43 | def multi_python(test): |
2153 | 44 | other_executable = zc.buildout.testing.find_python(other_version) | 44 | other_executable = zc.buildout.testing.find_python(other_version) |
2154 | 45 | command = textwrap.dedent('''\ | ||
2155 | 46 | try: | ||
2156 | 47 | import setuptools | ||
2157 | 48 | except ImportError: | ||
2158 | 49 | import sys | ||
2159 | 50 | sys.exit(1) | ||
2160 | 51 | ''') | ||
2161 | 52 | if subprocess.call([other_executable, '-c', command], | ||
2162 | 53 | env=os.environ): | ||
2163 | 54 | # the other executable does not have setuptools. Get setuptools. | ||
2164 | 55 | # We will do this using the same tools we are testing, for better or | ||
2165 | 56 | # worse. Alternatively, we could try using bootstrap. | ||
2166 | 57 | executable_dir = test.globs['tmpdir']('executable_dir') | ||
2167 | 58 | executable_parts = os.path.join(executable_dir, 'parts') | ||
2168 | 59 | test.globs['mkdir'](executable_parts) | ||
2169 | 60 | ws = zc.buildout.easy_install.install( | ||
2170 | 61 | ['setuptools'], executable_dir, | ||
2171 | 62 | index='http://www.python.org/pypi/', | ||
2172 | 63 | always_unzip=True, executable=other_executable) | ||
2173 | 64 | zc.buildout.easy_install.sitepackage_safe_scripts( | ||
2174 | 65 | executable_dir, ws, other_executable, executable_parts, | ||
2175 | 66 | reqs=['setuptools'], interpreter='py') | ||
2176 | 67 | original_executable = other_executable | ||
2177 | 68 | other_executable = os.path.join(executable_dir, 'py') | ||
2178 | 69 | assert not subprocess.call( | ||
2179 | 70 | [other_executable, '-c', command], env=os.environ), ( | ||
2180 | 71 | 'test set up failed') | ||
2181 | 45 | sample_eggs = test.globs['tmpdir']('sample_eggs') | 72 | sample_eggs = test.globs['tmpdir']('sample_eggs') |
2182 | 46 | os.mkdir(os.path.join(sample_eggs, 'index')) | 73 | os.mkdir(os.path.join(sample_eggs, 'index')) |
2183 | 47 | test.globs['sample_eggs'] = sample_eggs | 74 | test.globs['sample_eggs'] = sample_eggs |
2184 | 48 | 75 | ||
2185 | === modified file 'src/zc/buildout/update.txt' | |||
2186 | --- src/zc/buildout/update.txt 2009-11-06 22:33:23 +0000 | |||
2187 | +++ src/zc/buildout/update.txt 2010-02-23 20:41:17 +0000 | |||
2188 | @@ -81,6 +81,7 @@ | |||
2189 | 81 | Our buildout script has been updated to use the new eggs: | 81 | Our buildout script has been updated to use the new eggs: |
2190 | 82 | 82 | ||
2191 | 83 | >>> cat(sample_buildout, 'bin', 'buildout') | 83 | >>> cat(sample_buildout, 'bin', 'buildout') |
2192 | 84 | ... # doctest: +NORMALIZE_WHITESPACE | ||
2193 | 84 | #!/usr/local/bin/python2.4 | 85 | #!/usr/local/bin/python2.4 |
2194 | 85 | <BLANKLINE> | 86 | <BLANKLINE> |
2195 | 86 | import sys | 87 | import sys |
2196 | 87 | 88 | ||
2197 | === added directory 'z3c.recipe.scripts_' | |||
2198 | === added file 'z3c.recipe.scripts_/CHANGES.txt' | |||
2199 | --- z3c.recipe.scripts_/CHANGES.txt 1970-01-01 00:00:00 +0000 | |||
2200 | +++ z3c.recipe.scripts_/CHANGES.txt 2010-02-23 20:41:17 +0000 | |||
2201 | @@ -0,0 +1,7 @@ | |||
2202 | 1 | Change History | ||
2203 | 2 | ************** | ||
2204 | 3 | |||
2205 | 4 | 1.0.0 | ||
2206 | 5 | ===== | ||
2207 | 6 | |||
2208 | 7 | Initial public version. | ||
2209 | 0 | 8 | ||
2210 | === added file 'z3c.recipe.scripts_/README.txt' | |||
2211 | --- z3c.recipe.scripts_/README.txt 1970-01-01 00:00:00 +0000 | |||
2212 | +++ z3c.recipe.scripts_/README.txt 2010-02-23 20:41:17 +0000 | |||
2213 | @@ -0,0 +1,10 @@ | |||
2214 | 1 | ******************************** | ||
2215 | 2 | Buildout Script Recipe | ||
2216 | 3 | ******************************** | ||
2217 | 4 | |||
2218 | 5 | .. contents:: | ||
2219 | 6 | |||
2220 | 7 | The script recipe installs eggs into a buildout eggs directory, exactly | ||
2221 | 8 | like zc.recipe.egg, and then generates scripts in a buildout bin | ||
2222 | 9 | directory with egg paths baked into them. | ||
2223 | 10 | |||
2224 | 0 | 11 | ||
2225 | === added file 'z3c.recipe.scripts_/setup.py' | |||
2226 | --- z3c.recipe.scripts_/setup.py 1970-01-01 00:00:00 +0000 | |||
2227 | +++ z3c.recipe.scripts_/setup.py 2010-02-23 20:41:17 +0000 | |||
2228 | @@ -0,0 +1,76 @@ | |||
2229 | 1 | ############################################################################## | ||
2230 | 2 | # | ||
2231 | 3 | # Copyright (c) 2007 Zope Corporation and Contributors. | ||
2232 | 4 | # All Rights Reserved. | ||
2233 | 5 | # | ||
2234 | 6 | # This software is subject to the provisions of the Zope Public License, | ||
2235 | 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
2236 | 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
2237 | 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
2238 | 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
2239 | 11 | # FOR A PARTICULAR PURPOSE. | ||
2240 | 12 | # | ||
2241 | 13 | ############################################################################## | ||
2242 | 14 | """Setup for z3c.recipe.scripts package | ||
2243 | 15 | |||
2244 | 16 | $Id: setup.py 106736 2009-12-18 02:33:08Z gary $ | ||
2245 | 17 | """ | ||
2246 | 18 | |||
2247 | 19 | version = '1.0.0dev' | ||
2248 | 20 | |||
2249 | 21 | import os | ||
2250 | 22 | from setuptools import setup, find_packages | ||
2251 | 23 | |||
2252 | 24 | def read(*rnames): | ||
2253 | 25 | return open(os.path.join(os.path.dirname(__file__), *rnames)).read() | ||
2254 | 26 | |||
2255 | 27 | name = "z3c.recipe.scripts" | ||
2256 | 28 | setup( | ||
2257 | 29 | name = name, | ||
2258 | 30 | version = version, | ||
2259 | 31 | author = "Gary Poster", | ||
2260 | 32 | author_email = "gary.poster@canonical.com", | ||
2261 | 33 | description = "Recipe for installing Python scripts", | ||
2262 | 34 | long_description = ( | ||
2263 | 35 | read('README.txt') | ||
2264 | 36 | + '\n' + | ||
2265 | 37 | read('CHANGES.txt') | ||
2266 | 38 | + '\n' + | ||
2267 | 39 | 'Detailed Documentation\n' | ||
2268 | 40 | '**********************\n' | ||
2269 | 41 | + '\n' + | ||
2270 | 42 | read('src', 'z3c', 'recipe', 'scripts', 'README.txt') | ||
2271 | 43 | + '\n' + | ||
2272 | 44 | 'Download\n' | ||
2273 | 45 | '*********\n' | ||
2274 | 46 | ), | ||
2275 | 47 | keywords = "development build", | ||
2276 | 48 | classifiers = [ | ||
2277 | 49 | 'Development Status :: 5 - Production/Stable', | ||
2278 | 50 | 'Framework :: Buildout', | ||
2279 | 51 | 'Intended Audience :: Developers', | ||
2280 | 52 | 'License :: OSI Approved :: Zope Public License', | ||
2281 | 53 | 'Topic :: Software Development :: Build Tools', | ||
2282 | 54 | 'Topic :: Software Development :: Libraries :: Python Modules', | ||
2283 | 55 | ], | ||
2284 | 56 | url='http://cheeseshop.python.org/pypi/z3c.recipe.scripts', | ||
2285 | 57 | license = "ZPL 2.1", | ||
2286 | 58 | |||
2287 | 59 | packages = find_packages('src'), | ||
2288 | 60 | package_dir = {'':'src'}, | ||
2289 | 61 | namespace_packages = ['z3c', 'z3c.recipe'], | ||
2290 | 62 | install_requires = [ | ||
2291 | 63 | 'zc.buildout >=1.5.0dev', | ||
2292 | 64 | 'zc.recipe.egg >=1.2.3dev', | ||
2293 | 65 | 'setuptools'], | ||
2294 | 66 | tests_require = ['zope.testing'], | ||
2295 | 67 | test_suite = name+'.tests.test_suite', | ||
2296 | 68 | entry_points = {'zc.buildout': ['default = %s:Scripts' % name, | ||
2297 | 69 | 'script = %s:Scripts' % name, | ||
2298 | 70 | 'scripts = %s:Scripts' % name, | ||
2299 | 71 | 'interpreter = %s:Interpreter' % name, | ||
2300 | 72 | ] | ||
2301 | 73 | }, | ||
2302 | 74 | include_package_data = True, | ||
2303 | 75 | zip_safe=False, | ||
2304 | 76 | ) | ||
2305 | 0 | 77 | ||
2306 | === added directory 'z3c.recipe.scripts_/src' | |||
2307 | === added directory 'z3c.recipe.scripts_/src/z3c' | |||
2308 | === added file 'z3c.recipe.scripts_/src/z3c/__init__.py' | |||
2309 | --- z3c.recipe.scripts_/src/z3c/__init__.py 1970-01-01 00:00:00 +0000 | |||
2310 | +++ z3c.recipe.scripts_/src/z3c/__init__.py 2010-02-23 20:41:17 +0000 | |||
2311 | @@ -0,0 +1,1 @@ | |||
2312 | 1 | __import__('pkg_resources').declare_namespace(__name__) | ||
2313 | 0 | 2 | ||
2314 | === added directory 'z3c.recipe.scripts_/src/z3c/recipe' | |||
2315 | === added file 'z3c.recipe.scripts_/src/z3c/recipe/__init__.py' | |||
2316 | --- z3c.recipe.scripts_/src/z3c/recipe/__init__.py 1970-01-01 00:00:00 +0000 | |||
2317 | +++ z3c.recipe.scripts_/src/z3c/recipe/__init__.py 2010-02-23 20:41:17 +0000 | |||
2318 | @@ -0,0 +1,1 @@ | |||
2319 | 1 | __import__('pkg_resources').declare_namespace(__name__) | ||
2320 | 0 | 2 | ||
2321 | === added directory 'z3c.recipe.scripts_/src/z3c/recipe/scripts' | |||
2322 | === added file 'z3c.recipe.scripts_/src/z3c/recipe/scripts/README.txt' | |||
2323 | --- z3c.recipe.scripts_/src/z3c/recipe/scripts/README.txt 1970-01-01 00:00:00 +0000 | |||
2324 | +++ z3c.recipe.scripts_/src/z3c/recipe/scripts/README.txt 2010-02-23 20:41:17 +0000 | |||
2325 | @@ -0,0 +1,402 @@ | |||
2326 | 1 | Script and interpreter generation | ||
2327 | 2 | ================================= | ||
2328 | 3 | |||
2329 | 4 | This recipe is very similar to zc.recipe.egg, and if you are familiar with its | ||
2330 | 5 | options, you will be able to use this one easily. | ||
2331 | 6 | |||
2332 | 7 | The script and interpreter generation in this recipe are improved from | ||
2333 | 8 | those provided by zc.recipe.egg in two basic ways. | ||
2334 | 9 | |||
2335 | 10 | - The interpreter generated by the script supports all interpreter | ||
2336 | 11 | options, as opposed to the subset provided by zc.recipe.egg. | ||
2337 | 12 | |||
2338 | 13 | - Both scripts and interpreters from this recipe can optionally choose | ||
2339 | 14 | to include site-packages, and even sitecustomize. | ||
2340 | 15 | |||
2341 | 16 | The recipe takes several options. First, here's the list of the options | ||
2342 | 17 | that overlap from the standard zc.recipe.eggs scripts recipe. After | ||
2343 | 18 | this, we'll list the new options and describe them. | ||
2344 | 19 | |||
2345 | 20 | * eggs | ||
2346 | 21 | * find-links | ||
2347 | 22 | * index | ||
2348 | 23 | * python | ||
2349 | 24 | * extra-paths | ||
2350 | 25 | * entry-points | ||
2351 | 26 | * scripts | ||
2352 | 27 | * dependent-scripts | ||
2353 | 28 | * interpreter | ||
2354 | 29 | * arguments | ||
2355 | 30 | * initialization | ||
2356 | 31 | * relative-paths | ||
2357 | 32 | |||
2358 | 33 | In addition to these, the recipe offers these new options. They are | ||
2359 | 34 | introduced here, and described more in depth below. | ||
2360 | 35 | |||
2361 | 36 | add-site-packages | ||
2362 | 37 | You can choose to have the site-packages of the underlying Python | ||
2363 | 38 | available to your script or interpreter, in addition to the packages | ||
2364 | 39 | from your eggs. See the section on this option for motivations and | ||
2365 | 40 | warnings. | ||
2366 | 41 | |||
2367 | 42 | extends | ||
2368 | 43 | You can extend another section using this value. It is intended to be | ||
2369 | 44 | used by extending a section that uses this package's scripts recipe. | ||
2370 | 45 | In this manner, you can avoid repeating yourself. | ||
2371 | 46 | |||
2372 | 47 | exec-sitecustomize | ||
2373 | 48 | Normally the Python's real sitecustomize module is not processed. | ||
2374 | 49 | If you want it to be processed, set this value to 'true'. This will | ||
2375 | 50 | be honored irrespective of the setting for add-site-packages. | ||
2376 | 51 | |||
2377 | 52 | script-initialization | ||
2378 | 53 | The standard initialization code affects both an interpreter and scripts. | ||
2379 | 54 | The code in script-initialization is used only for the generated scripts. | ||
2380 | 55 | |||
2381 | 56 | Finally, the "interpreter" entry point ignores ``script-initialization``, | ||
2382 | 57 | ``scripts``, and ``arguments``, and provides yet another additional option. | ||
2383 | 58 | |||
2384 | 59 | name | ||
2385 | 60 | While, by default, the interpreter recipe takes the name of the | ||
2386 | 61 | section to be the desired interpreter name, you can specify the | ||
2387 | 62 | interpreter name here instead. | ||
2388 | 63 | |||
2389 | 64 | Script generation | ||
2390 | 65 | ----------------- | ||
2391 | 66 | |||
2392 | 67 | Generating a basic script looks virtually identical to using zc.recipe.egg. | ||
2393 | 68 | |||
2394 | 69 | (Note that the find-links and index values are typically not needed; they | ||
2395 | 70 | are included to help make this document run as a test successfully.) | ||
2396 | 71 | |||
2397 | 72 | >>> write(sample_buildout, 'buildout.cfg', | ||
2398 | 73 | ... """ | ||
2399 | 74 | ... [buildout] | ||
2400 | 75 | ... parts = demo | ||
2401 | 76 | ... | ||
2402 | 77 | ... [demo] | ||
2403 | 78 | ... recipe = z3c.recipe.scripts | ||
2404 | 79 | ... eggs = demo<0.3 | ||
2405 | 80 | ... find-links = %(server)s | ||
2406 | 81 | ... index = %(server)s/index | ||
2407 | 82 | ... """ % dict(server=link_server)) | ||
2408 | 83 | |||
2409 | 84 | >>> print system(buildout), | ||
2410 | 85 | Installing demo. | ||
2411 | 86 | Getting distribution for 'demo<0.3'. | ||
2412 | 87 | Got demo 0.2. | ||
2413 | 88 | Getting distribution for 'demoneeded'. | ||
2414 | 89 | Got demoneeded 1.2c1. | ||
2415 | 90 | Generated script '/sample-buildout/bin/demo'. | ||
2416 | 91 | |||
2417 | 92 | >>> print system(join(sample_buildout, 'bin', 'demo')), | ||
2418 | 93 | 2 2 | ||
2419 | 94 | |||
2420 | 95 | Interpreter generation | ||
2421 | 96 | ---------------------- | ||
2422 | 97 | |||
2423 | 98 | As with zc.recipe.egg, you can generate an interpreter with the default | ||
2424 | 99 | script recipe shown above by supplying the "interpreter" option. | ||
2425 | 100 | This example will create both an entry point script and an interpreter. | ||
2426 | 101 | |||
2427 | 102 | >>> write(sample_buildout, 'buildout.cfg', | ||
2428 | 103 | ... """ | ||
2429 | 104 | ... [buildout] | ||
2430 | 105 | ... parts = demo | ||
2431 | 106 | ... | ||
2432 | 107 | ... [demo] | ||
2433 | 108 | ... recipe = z3c.recipe.scripts | ||
2434 | 109 | ... eggs = demo<0.3 | ||
2435 | 110 | ... find-links = %(server)s | ||
2436 | 111 | ... index = %(server)s/index | ||
2437 | 112 | ... interpreter = py | ||
2438 | 113 | ... """ % dict(server=link_server)) | ||
2439 | 114 | |||
2440 | 115 | >>> print system(buildout), | ||
2441 | 116 | Uninstalling demo. | ||
2442 | 117 | Installing demo. | ||
2443 | 118 | Generated script '/sample-buildout/bin/demo'. | ||
2444 | 119 | Generated interpreter '/sample-buildout/bin/py'. | ||
2445 | 120 | |||
2446 | 121 | You can also generate an interpreter alone with the ``interpreter`` recipe. | ||
2447 | 122 | |||
2448 | 123 | >>> write(sample_buildout, 'buildout.cfg', | ||
2449 | 124 | ... """ | ||
2450 | 125 | ... [buildout] | ||
2451 | 126 | ... parts = py | ||
2452 | 127 | ... | ||
2453 | 128 | ... [py] | ||
2454 | 129 | ... recipe = z3c.recipe.scripts:interpreter | ||
2455 | 130 | ... eggs = demo<0.3 | ||
2456 | 131 | ... find-links = %(server)s | ||
2457 | 132 | ... index = %(server)s/index | ||
2458 | 133 | ... """ % dict(server=link_server)) | ||
2459 | 134 | |||
2460 | 135 | >>> print system(buildout), | ||
2461 | 136 | Uninstalling demo. | ||
2462 | 137 | Installing py. | ||
2463 | 138 | Generated interpreter '/sample-buildout/bin/py'. | ||
2464 | 139 | |||
2465 | 140 | In both cases, the bin/py script works by restarting Python after | ||
2466 | 141 | specifying a special path in PYTHONPATH. This example shows the UNIX version; | ||
2467 | 142 | the Windows version actually uses subprocess instead. | ||
2468 | 143 | |||
2469 | 144 | >>> cat(sample_buildout, 'bin', 'py') # doctest: +NORMALIZE_WHITESPACE | ||
2470 | 145 | #!/usr/bin/python2.4 -S | ||
2471 | 146 | <BLANKLINE> | ||
2472 | 147 | import os | ||
2473 | 148 | import sys | ||
2474 | 149 | <BLANKLINE> | ||
2475 | 150 | argv = [sys.executable] + sys.argv[1:] | ||
2476 | 151 | environ = os.environ.copy() | ||
2477 | 152 | path = '/sample-buildout/parts/py' | ||
2478 | 153 | if environ.get('PYTHONPATH'): | ||
2479 | 154 | path = os.pathsep.join([path, environ['PYTHONPATH']]) | ||
2480 | 155 | environ['PYTHONPATH'] = path | ||
2481 | 156 | os.execve(sys.executable, argv, environ) | ||
2482 | 157 | |||
2483 | 158 | The path is a directory that contains two files: our own site.py and | ||
2484 | 159 | sitecustomize.py. The site.py is modified from the underlying Python's | ||
2485 | 160 | site.py, and is responsible for setting up our paths. The | ||
2486 | 161 | sitecustomize.py is responsible for running the initialization code | ||
2487 | 162 | provided. | ||
2488 | 163 | |||
2489 | 164 | >>> ls(sample_buildout, 'parts', 'py') | ||
2490 | 165 | - site.py | ||
2491 | 166 | - sitecustomize.py | ||
2492 | 167 | |||
2493 | 168 | Here's an example of using the generated interpreter. | ||
2494 | 169 | |||
2495 | 170 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2496 | 171 | ... ' -c "import sys, pprint; pprint.pprint(sys.path[-2:])"') | ||
2497 | 172 | ['/sample-buildout/eggs/demo-0.2-pyN.N.egg', | ||
2498 | 173 | '/sample-buildout/eggs/demoneeded-1.2c1-pyN.N.egg'] | ||
2499 | 174 | <BLANKLINE> | ||
2500 | 175 | |||
2501 | 176 | Including site-packages and sitecustomize | ||
2502 | 177 | ----------------------------------------- | ||
2503 | 178 | |||
2504 | 179 | As introduced above, this recipe supports including site packages. This has | ||
2505 | 180 | some advantages and some serious dangers. | ||
2506 | 181 | |||
2507 | 182 | A typical reason to include site-packages is that it is easier to | ||
2508 | 183 | install one or more dependencies in your Python than it is with | ||
2509 | 184 | buildout. Some packages, such as lxml or Python PostgreSQL integration, | ||
2510 | 185 | have dependencies that can be much easier to build and/or install using | ||
2511 | 186 | other mechanisms, such as your operating system's package manager. By | ||
2512 | 187 | installing some core packages into your Python's site-packages, this can | ||
2513 | 188 | significantly simplify some application installations. | ||
2514 | 189 | |||
2515 | 190 | However, doing this has a significant danger. One of the primary goals | ||
2516 | 191 | of buildout is to provide repeatability. Some packages (one of the | ||
2517 | 192 | better known Python openid packages, for instance) change their behavior | ||
2518 | 193 | depending on what packages are available. If Python curl bindings are | ||
2519 | 194 | available, these may be preferred by the library. If a certain XML | ||
2520 | 195 | package is installed, it may be preferred by the library. These hidden | ||
2521 | 196 | choices may cause small or large behavior differences. The fact that | ||
2522 | 197 | they can be rarely encountered can actually make it worse: you forget | ||
2523 | 198 | that this might be a problem, and debugging the differences can be | ||
2524 | 199 | difficult. If you allow site-packages to be included in your buildout, | ||
2525 | 200 | and the Python you use is not managed precisely by your application (for | ||
2526 | 201 | instance, it is a system Python), you open yourself up to these | ||
2527 | 202 | possibilities. Don't be unaware of the dangers. | ||
2528 | 203 | |||
2529 | 204 | To show off these features, we need to use buildout with a Python | ||
2530 | 205 | executable with some extra paths to show ``add-site-packages``; and one | ||
2531 | 206 | guaranteed to have a sitecustomize module to show | ||
2532 | 207 | ``exec-sitecustomize``. We'll make one using a test fixture called | ||
2533 | 208 | ``make_py``. The os.environ change below will go into the sitecustomize, | ||
2534 | 209 | and the site_packages_path will be in the Python's path. | ||
2535 | 210 | |||
2536 | 211 | >>> py_path, site_packages_path = make_py(initialization='''\ | ||
2537 | 212 | ... import os | ||
2538 | 213 | ... os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2539 | 214 | ... ''') | ||
2540 | 215 | >>> print site_packages_path | ||
2541 | 216 | /executable_buildout/site-packages | ||
2542 | 217 | |||
2543 | 218 | Now let's take a look at add-site-packages. | ||
2544 | 219 | |||
2545 | 220 | >>> write(sample_buildout, 'buildout.cfg', | ||
2546 | 221 | ... """ | ||
2547 | 222 | ... [buildout] | ||
2548 | 223 | ... parts = py | ||
2549 | 224 | ... executable = %(py_path)s | ||
2550 | 225 | ... | ||
2551 | 226 | ... [py] | ||
2552 | 227 | ... recipe = z3c.recipe.scripts:interpreter | ||
2553 | 228 | ... add-site-packages = true | ||
2554 | 229 | ... eggs = demo<0.3 | ||
2555 | 230 | ... find-links = %(server)s | ||
2556 | 231 | ... index = %(server)s/index | ||
2557 | 232 | ... """ % dict(server=link_server, py_path=py_path)) | ||
2558 | 233 | |||
2559 | 234 | >>> print system(buildout), | ||
2560 | 235 | Uninstalling py. | ||
2561 | 236 | Installing py. | ||
2562 | 237 | Generated interpreter '/sample-buildout/bin/py'. | ||
2563 | 238 | |||
2564 | 239 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2565 | 240 | ... ''' -c "import sys, pprint; pprint.pprint(sys.path)"''') | ||
2566 | 241 | ... # doctest: +ELLIPSIS | ||
2567 | 242 | ['', | ||
2568 | 243 | '/sample-buildout/parts/py', | ||
2569 | 244 | ..., | ||
2570 | 245 | '/sample-buildout/eggs/demo-0.2-pyN.N.egg', | ||
2571 | 246 | '/sample-buildout/eggs/demoneeded-1.2c1-pyN.N.egg', | ||
2572 | 247 | '/executable_buildout/eggs/setuptools-X-pyN.N.egg', | ||
2573 | 248 | '/executable_buildout/site-packages'] | ||
2574 | 249 | <BLANKLINE> | ||
2575 | 250 | |||
2576 | 251 | Next we will use the exec-sitecustomize option. It simply copies | ||
2577 | 252 | Python's underlying sitecustomize module, if it exists, to the local | ||
2578 | 253 | version. The os.environ change shown above in the make_py call will go | ||
2579 | 254 | into the sitecustomize. | ||
2580 | 255 | |||
2581 | 256 | >>> write(sample_buildout, 'buildout.cfg', | ||
2582 | 257 | ... """ | ||
2583 | 258 | ... [buildout] | ||
2584 | 259 | ... parts = py | ||
2585 | 260 | ... executable = %(py_path)s | ||
2586 | 261 | ... | ||
2587 | 262 | ... [py] | ||
2588 | 263 | ... recipe = z3c.recipe.scripts:interpreter | ||
2589 | 264 | ... exec-sitecustomize = true | ||
2590 | 265 | ... eggs = demo<0.3 | ||
2591 | 266 | ... find-links = %(server)s | ||
2592 | 267 | ... index = %(server)s/index | ||
2593 | 268 | ... """ % dict(server=link_server, py_path=py_path)) | ||
2594 | 269 | |||
2595 | 270 | >>> print system(buildout), | ||
2596 | 271 | Uninstalling py. | ||
2597 | 272 | Installing py. | ||
2598 | 273 | Generated interpreter '/sample-buildout/bin/py'. | ||
2599 | 274 | |||
2600 | 275 | >>> cat(sample_buildout, 'parts', 'py', 'sitecustomize.py') | ||
2601 | 276 | ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS | ||
2602 | 277 | <BLANKLINE> | ||
2603 | 278 | # The following is from | ||
2604 | 279 | # /executable_buildout/parts/py/sitecustomize.py | ||
2605 | 280 | ... | ||
2606 | 281 | import os | ||
2607 | 282 | os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2608 | 283 | |||
2609 | 284 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2610 | 285 | ... ''' -c "import os; print os.environ['zc.buildout']"''') | ||
2611 | 286 | foo bar baz shazam | ||
2612 | 287 | <BLANKLINE> | ||
2613 | 288 | |||
2614 | 289 | Options | ||
2615 | 290 | ------- | ||
2616 | 291 | |||
2617 | 292 | We'll focus now on the options that are different than zc.recipe.egg. | ||
2618 | 293 | |||
2619 | 294 | Let's look at the ``extends`` option first. | ||
2620 | 295 | |||
2621 | 296 | >>> write(sample_buildout, 'buildout.cfg', | ||
2622 | 297 | ... """ | ||
2623 | 298 | ... [buildout] | ||
2624 | 299 | ... parts = demo python | ||
2625 | 300 | ... | ||
2626 | 301 | ... [demo] | ||
2627 | 302 | ... recipe = z3c.recipe.scripts | ||
2628 | 303 | ... eggs = demo<0.3 | ||
2629 | 304 | ... find-links = %(server)s | ||
2630 | 305 | ... index = %(server)s/index | ||
2631 | 306 | ... | ||
2632 | 307 | ... [python] | ||
2633 | 308 | ... recipe = z3c.recipe.scripts:interpreter | ||
2634 | 309 | ... extends = demo | ||
2635 | 310 | ... initialization = | ||
2636 | 311 | ... import os | ||
2637 | 312 | ... os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2638 | 313 | ... """ % dict(server=link_server)) | ||
2639 | 314 | |||
2640 | 315 | That makes it easier to specify some initialization for the interpreter | ||
2641 | 316 | that is different than a script, while duplicating other configuration. | ||
2642 | 317 | |||
2643 | 318 | Now let's put it in action. | ||
2644 | 319 | |||
2645 | 320 | >>> print system(buildout), | ||
2646 | 321 | Uninstalling py. | ||
2647 | 322 | Installing demo. | ||
2648 | 323 | Generated script '/sample-buildout/bin/demo'. | ||
2649 | 324 | Installing python. | ||
2650 | 325 | Generated interpreter '/sample-buildout/bin/python'. | ||
2651 | 326 | |||
2652 | 327 | >>> print system(join(sample_buildout, 'bin', 'python') + | ||
2653 | 328 | ... ' -c "import sys, pprint; pprint.pprint(sys.path[-2:])"') | ||
2654 | 329 | ['/sample-buildout/eggs/demo-0.2-pyN.N.egg', | ||
2655 | 330 | '/sample-buildout/eggs/demoneeded-1.2c1-pyN.N.egg'] | ||
2656 | 331 | <BLANKLINE> | ||
2657 | 332 | >>> print system(join(sample_buildout, 'bin', 'python') + | ||
2658 | 333 | ... ''' -c "import os; print os.environ['zc.buildout']"'''), | ||
2659 | 334 | foo bar baz shazam | ||
2660 | 335 | |||
2661 | 336 | Note that the parts/py directory has been cleaned up, and parts/python has | ||
2662 | 337 | been created. | ||
2663 | 338 | |||
2664 | 339 | >>> ls(sample_buildout, 'parts') | ||
2665 | 340 | d demo | ||
2666 | 341 | d python | ||
2667 | 342 | |||
2668 | 343 | If you want to have initialization that only affects scripts, not the | ||
2669 | 344 | interpreter, you can use script-initialization. Here's a demonstration. | ||
2670 | 345 | |||
2671 | 346 | >>> write(sample_buildout, 'buildout.cfg', | ||
2672 | 347 | ... """ | ||
2673 | 348 | ... [buildout] | ||
2674 | 349 | ... parts = demo | ||
2675 | 350 | ... | ||
2676 | 351 | ... [demo] | ||
2677 | 352 | ... recipe = z3c.recipe.scripts | ||
2678 | 353 | ... eggs = demo<0.3 | ||
2679 | 354 | ... find-links = %(server)s | ||
2680 | 355 | ... index = %(server)s/index | ||
2681 | 356 | ... interpreter = py | ||
2682 | 357 | ... script-initialization = | ||
2683 | 358 | ... print "Hi from the script" | ||
2684 | 359 | ... """ % dict(server=link_server)) | ||
2685 | 360 | |||
2686 | 361 | >>> print system(buildout), | ||
2687 | 362 | Uninstalling python. | ||
2688 | 363 | Uninstalling demo. | ||
2689 | 364 | Installing demo. | ||
2690 | 365 | Generated script '/sample-buildout/bin/demo'. | ||
2691 | 366 | Generated interpreter '/sample-buildout/bin/py'. | ||
2692 | 367 | |||
2693 | 368 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2694 | 369 | ... ''' -c "print 'Hi from the interpreter'"'''), | ||
2695 | 370 | Hi from the interpreter | ||
2696 | 371 | |||
2697 | 372 | >>> print system(join(sample_buildout, 'bin', 'demo')), | ||
2698 | 373 | Hi from the script | ||
2699 | 374 | 2 2 | ||
2700 | 375 | |||
2701 | 376 | The last new option is ``name``. This simply changes the name of the | ||
2702 | 377 | interpreter, so that you are not forced to use the name of the section. | ||
2703 | 378 | |||
2704 | 379 | >>> write(sample_buildout, 'buildout.cfg', | ||
2705 | 380 | ... """ | ||
2706 | 381 | ... [buildout] | ||
2707 | 382 | ... parts = interpreter | ||
2708 | 383 | ... | ||
2709 | 384 | ... [interpreter] | ||
2710 | 385 | ... name = python2 | ||
2711 | 386 | ... recipe = z3c.recipe.scripts:interpreter | ||
2712 | 387 | ... eggs = demo<0.3 | ||
2713 | 388 | ... find-links = %(server)s | ||
2714 | 389 | ... index = %(server)s/index | ||
2715 | 390 | ... """ % dict(server=link_server)) | ||
2716 | 391 | |||
2717 | 392 | >>> print system(buildout), | ||
2718 | 393 | Uninstalling demo. | ||
2719 | 394 | Installing interpreter. | ||
2720 | 395 | Generated interpreter '/sample-buildout/bin/python2'. | ||
2721 | 396 | |||
2722 | 397 | >>> print system(join(sample_buildout, 'bin', 'python2') + | ||
2723 | 398 | ... ' -c "print 42"') | ||
2724 | 399 | 42 | ||
2725 | 400 | <BLANKLINE> | ||
2726 | 401 | |||
2727 | 402 | The other options all identical to zc.recipe.egg. | ||
2728 | 0 | 403 | ||
2729 | === added file 'z3c.recipe.scripts_/src/z3c/recipe/scripts/__init__.py' | |||
2730 | --- z3c.recipe.scripts_/src/z3c/recipe/scripts/__init__.py 1970-01-01 00:00:00 +0000 | |||
2731 | +++ z3c.recipe.scripts_/src/z3c/recipe/scripts/__init__.py 2010-02-23 20:41:17 +0000 | |||
2732 | @@ -0,0 +1,1 @@ | |||
2733 | 1 | from z3c.recipe.scripts.scripts import Scripts, Interpreter | ||
2734 | 0 | 2 | ||
2735 | === added file 'z3c.recipe.scripts_/src/z3c/recipe/scripts/scripts.py' | |||
2736 | --- z3c.recipe.scripts_/src/z3c/recipe/scripts/scripts.py 1970-01-01 00:00:00 +0000 | |||
2737 | +++ z3c.recipe.scripts_/src/z3c/recipe/scripts/scripts.py 2010-02-23 20:41:17 +0000 | |||
2738 | @@ -0,0 +1,101 @@ | |||
2739 | 1 | ############################################################################## | ||
2740 | 2 | # | ||
2741 | 3 | # Copyright (c) 2009-2010 Zope Corporation and Contributors. | ||
2742 | 4 | # All Rights Reserved. | ||
2743 | 5 | # | ||
2744 | 6 | # This software is subject to the provisions of the Zope Public License, | ||
2745 | 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
2746 | 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
2747 | 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
2748 | 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
2749 | 11 | # FOR A PARTICULAR PURPOSE. | ||
2750 | 12 | # | ||
2751 | 13 | ############################################################################## | ||
2752 | 14 | """Install scripts from eggs. | ||
2753 | 15 | """ | ||
2754 | 16 | import os | ||
2755 | 17 | import zc.buildout | ||
2756 | 18 | import zc.buildout.easy_install | ||
2757 | 19 | from zc.recipe.egg.egg import ScriptBase | ||
2758 | 20 | |||
2759 | 21 | |||
2760 | 22 | class Base(ScriptBase): | ||
2761 | 23 | |||
2762 | 24 | def __init__(self, buildout, name, options): | ||
2763 | 25 | if 'extends' in options: | ||
2764 | 26 | options.update(buildout[options['extends']]) | ||
2765 | 27 | super(Base, self).__init__(buildout, name, options) | ||
2766 | 28 | self.default_eggs = '' # Disables feature from zc.recipe.egg. | ||
2767 | 29 | b_options = buildout['buildout'] | ||
2768 | 30 | options['parts-directory'] = os.path.join( | ||
2769 | 31 | b_options['parts-directory'], self.name) | ||
2770 | 32 | |||
2771 | 33 | value = options.setdefault( | ||
2772 | 34 | 'add-site-packages', | ||
2773 | 35 | b_options.get('add-site-packages', 'false')) | ||
2774 | 36 | if value not in ('true', 'false'): | ||
2775 | 37 | raise zc.buildout.UserError( | ||
2776 | 38 | "Invalid value for add-site-packages option: %s" % | ||
2777 | 39 | (value,)) | ||
2778 | 40 | self.add_site_packages = (value == 'true') | ||
2779 | 41 | |||
2780 | 42 | value = options.setdefault( | ||
2781 | 43 | 'exec-sitecustomize', | ||
2782 | 44 | b_options.get('exec-sitecustomize', 'false')) | ||
2783 | 45 | if value not in ('true', 'false'): | ||
2784 | 46 | raise zc.buildout.UserError( | ||
2785 | 47 | "Invalid value for exec-sitecustomize option: %s" % | ||
2786 | 48 | (value,)) | ||
2787 | 49 | self.exec_sitecustomize = (value == 'true') | ||
2788 | 50 | |||
2789 | 51 | |||
2790 | 52 | class Interpreter(Base): | ||
2791 | 53 | |||
2792 | 54 | def __init__(self, buildout, name, options): | ||
2793 | 55 | super(Interpreter, self).__init__(buildout, name, options) | ||
2794 | 56 | |||
2795 | 57 | options.setdefault('name', name) | ||
2796 | 58 | |||
2797 | 59 | def install(self): | ||
2798 | 60 | reqs, ws = self.working_set() | ||
2799 | 61 | options = self.options | ||
2800 | 62 | generated = [] | ||
2801 | 63 | if not os.path.exists(options['parts-directory']): | ||
2802 | 64 | os.mkdir(options['parts-directory']) | ||
2803 | 65 | generated.append(options['parts-directory']) | ||
2804 | 66 | generated.extend(zc.buildout.easy_install.sitepackage_safe_scripts( | ||
2805 | 67 | options['bin-directory'], ws, options['executable'], | ||
2806 | 68 | options['parts-directory'], | ||
2807 | 69 | interpreter=options['name'], | ||
2808 | 70 | extra_paths=self.extra_paths, | ||
2809 | 71 | initialization=options.get('initialization', ''), | ||
2810 | 72 | add_site_packages=self.add_site_packages, | ||
2811 | 73 | exec_sitecustomize=self.exec_sitecustomize, | ||
2812 | 74 | relative_paths=self._relative_paths, | ||
2813 | 75 | )) | ||
2814 | 76 | return generated | ||
2815 | 77 | |||
2816 | 78 | update = install | ||
2817 | 79 | |||
2818 | 80 | |||
2819 | 81 | class Scripts(Base): | ||
2820 | 82 | |||
2821 | 83 | def _install(self, reqs, ws, scripts): | ||
2822 | 84 | options = self.options | ||
2823 | 85 | generated = [] | ||
2824 | 86 | if not os.path.exists(options['parts-directory']): | ||
2825 | 87 | os.mkdir(options['parts-directory']) | ||
2826 | 88 | generated.append(options['parts-directory']) | ||
2827 | 89 | generated.extend(zc.buildout.easy_install.sitepackage_safe_scripts( | ||
2828 | 90 | options['bin-directory'], ws, options['executable'], | ||
2829 | 91 | options['parts-directory'], reqs=reqs, scripts=scripts, | ||
2830 | 92 | interpreter=options.get('interpreter'), | ||
2831 | 93 | extra_paths=self.extra_paths, | ||
2832 | 94 | initialization=options.get('initialization', ''), | ||
2833 | 95 | add_site_packages=self.add_site_packages, | ||
2834 | 96 | exec_sitecustomize=self.exec_sitecustomize, | ||
2835 | 97 | relative_paths=self._relative_paths, | ||
2836 | 98 | script_arguments=options.get('arguments', ''), | ||
2837 | 99 | script_initialization=options.get('script-initialization', '') | ||
2838 | 100 | )) | ||
2839 | 101 | return generated | ||
2840 | 0 | 102 | ||
2841 | === added file 'z3c.recipe.scripts_/src/z3c/recipe/scripts/tests.py' | |||
2842 | --- z3c.recipe.scripts_/src/z3c/recipe/scripts/tests.py 1970-01-01 00:00:00 +0000 | |||
2843 | +++ z3c.recipe.scripts_/src/z3c/recipe/scripts/tests.py 2010-02-23 20:41:17 +0000 | |||
2844 | @@ -0,0 +1,293 @@ | |||
2845 | 1 | ############################################################################## | ||
2846 | 2 | # | ||
2847 | 3 | # Copyright (c) 2006 Zope Corporation and Contributors. | ||
2848 | 4 | # All Rights Reserved. | ||
2849 | 5 | # | ||
2850 | 6 | # This software is subject to the provisions of the Zope Public License, | ||
2851 | 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | ||
2852 | 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | ||
2853 | 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
2854 | 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | ||
2855 | 11 | # FOR A PARTICULAR PURPOSE. | ||
2856 | 12 | # | ||
2857 | 13 | ############################################################################## | ||
2858 | 14 | |||
2859 | 15 | import os, re, shutil, sys | ||
2860 | 16 | import zc.buildout.tests | ||
2861 | 17 | import zc.buildout.testselectingpython | ||
2862 | 18 | import zc.buildout.testing | ||
2863 | 19 | |||
2864 | 20 | import unittest | ||
2865 | 21 | from zope.testing import doctest, renormalizing | ||
2866 | 22 | |||
2867 | 23 | # We do not explicitly test the recipe support for the ``eggs``, | ||
2868 | 24 | # ``find-links``, and ``index`` options because they are used for most or | ||
2869 | 25 | # all of the examples. The README tests ``extends``, | ||
2870 | 26 | # ``include-site-customization`` and ``name``. That leaves ``python``, | ||
2871 | 27 | # ``extra-paths``, ``initialization``, ``relative-paths``, and | ||
2872 | 28 | # ``add-site-packages``. | ||
2873 | 29 | |||
2874 | 30 | def supports_python_option(): | ||
2875 | 31 | """ | ||
2876 | 32 | This simply shows that the ``python`` option can specify another section to | ||
2877 | 33 | find the ``executable``. (The ``python`` option defaults to looking in the | ||
2878 | 34 | ``buildout`` section.) We do this by creating a custom Python that will have | ||
2879 | 35 | some initialization that we can look for. | ||
2880 | 36 | |||
2881 | 37 | >>> py_path, site_packages_path = make_py(initialization=''' | ||
2882 | 38 | ... import os | ||
2883 | 39 | ... os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2884 | 40 | ... ''') | ||
2885 | 41 | |||
2886 | 42 | >>> write(sample_buildout, 'buildout.cfg', | ||
2887 | 43 | ... ''' | ||
2888 | 44 | ... [buildout] | ||
2889 | 45 | ... parts = py | ||
2890 | 46 | ... | ||
2891 | 47 | ... [custom_python] | ||
2892 | 48 | ... executable = %(py_path)s | ||
2893 | 49 | ... | ||
2894 | 50 | ... [py] | ||
2895 | 51 | ... recipe = z3c.recipe.scripts:interpreter | ||
2896 | 52 | ... exec-sitecustomize = true | ||
2897 | 53 | ... eggs = demo<0.3 | ||
2898 | 54 | ... find-links = %(server)s | ||
2899 | 55 | ... index = %(server)s/index | ||
2900 | 56 | ... python = custom_python | ||
2901 | 57 | ... ''' % dict(server=link_server, py_path=py_path)) | ||
2902 | 58 | |||
2903 | 59 | >>> print system(buildout), | ||
2904 | 60 | Installing py. | ||
2905 | 61 | Getting distribution for 'demo<0.3'. | ||
2906 | 62 | Got demo 0.2. | ||
2907 | 63 | Getting distribution for 'demoneeded'. | ||
2908 | 64 | Got demoneeded 1.2c1. | ||
2909 | 65 | Generated interpreter '/sample-buildout/bin/py'. | ||
2910 | 66 | |||
2911 | 67 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2912 | 68 | ... ''' -c "import os; print os.environ['zc.buildout']"'''), | ||
2913 | 69 | foo bar baz shazam | ||
2914 | 70 | """ | ||
2915 | 71 | |||
2916 | 72 | def interpreter_recipe_supports_extra_paths_option(): | ||
2917 | 73 | """ | ||
2918 | 74 | This shows that specifying extra-paths will affect sys.path. | ||
2919 | 75 | |||
2920 | 76 | This recipe will not add paths that do not exist, so we create them. | ||
2921 | 77 | |||
2922 | 78 | >>> mkdir(sample_buildout, 'foo') | ||
2923 | 79 | >>> mkdir(sample_buildout, 'foo', 'bar') | ||
2924 | 80 | >>> mkdir(sample_buildout, 'spam') | ||
2925 | 81 | |||
2926 | 82 | >>> write(sample_buildout, 'buildout.cfg', | ||
2927 | 83 | ... ''' | ||
2928 | 84 | ... [buildout] | ||
2929 | 85 | ... parts = py | ||
2930 | 86 | ... | ||
2931 | 87 | ... [py] | ||
2932 | 88 | ... recipe = z3c.recipe.scripts:interpreter | ||
2933 | 89 | ... find-links = %(server)s | ||
2934 | 90 | ... index = %(server)s/index | ||
2935 | 91 | ... extra-paths = | ||
2936 | 92 | ... ${buildout:directory}/foo/bar | ||
2937 | 93 | ... ${buildout:directory}/spam | ||
2938 | 94 | ... ''' % dict(server=link_server)) | ||
2939 | 95 | |||
2940 | 96 | >>> print system(buildout), | ||
2941 | 97 | Installing py. | ||
2942 | 98 | Generated interpreter '/sample-buildout/bin/py'. | ||
2943 | 99 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2944 | 100 | ... ''' -c "import sys;print 'path' + ' '.join(sys.path)"''') | ||
2945 | 101 | ... # doctest:+ELLIPSIS | ||
2946 | 102 | path.../foo/bar /sample-buildout/spam... | ||
2947 | 103 | |||
2948 | 104 | """ | ||
2949 | 105 | |||
2950 | 106 | def interpreter_recipe_supports_initialization_option(): | ||
2951 | 107 | """ | ||
2952 | 108 | This simply shows that the ``initialization`` option can specify code to | ||
2953 | 109 | run on initialization. | ||
2954 | 110 | |||
2955 | 111 | >>> write(sample_buildout, 'buildout.cfg', | ||
2956 | 112 | ... ''' | ||
2957 | 113 | ... [buildout] | ||
2958 | 114 | ... parts = py | ||
2959 | 115 | ... | ||
2960 | 116 | ... [py] | ||
2961 | 117 | ... recipe = z3c.recipe.scripts:interpreter | ||
2962 | 118 | ... initialization = | ||
2963 | 119 | ... import os | ||
2964 | 120 | ... os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2965 | 121 | ... eggs = demo<0.3 | ||
2966 | 122 | ... find-links = %(server)s | ||
2967 | 123 | ... index = %(server)s/index | ||
2968 | 124 | ... ''' % dict(server=link_server)) | ||
2969 | 125 | |||
2970 | 126 | >>> print system(buildout), | ||
2971 | 127 | Installing py. | ||
2972 | 128 | Getting distribution for 'demo<0.3'. | ||
2973 | 129 | Got demo 0.2. | ||
2974 | 130 | Getting distribution for 'demoneeded'. | ||
2975 | 131 | Got demoneeded 1.2c1. | ||
2976 | 132 | Generated interpreter '/sample-buildout/bin/py'. | ||
2977 | 133 | |||
2978 | 134 | >>> cat(sample_buildout, 'parts', 'py', 'sitecustomize.py') | ||
2979 | 135 | ... # doctest: +NORMALIZE_WHITESPACE | ||
2980 | 136 | <BLANKLINE> | ||
2981 | 137 | import os | ||
2982 | 138 | os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2983 | 139 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
2984 | 140 | ... ''' -c "import os; print os.environ['zc.buildout']"'''), | ||
2985 | 141 | foo bar baz shazam | ||
2986 | 142 | |||
2987 | 143 | This also works with the exec-sitecustomize option, processing local | ||
2988 | 144 | initialization, and then the Python's initialization. We show this with a | ||
2989 | 145 | custom Python. | ||
2990 | 146 | |||
2991 | 147 | >>> py_path, site_packages_path = make_py(initialization=''' | ||
2992 | 148 | ... import os | ||
2993 | 149 | ... os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
2994 | 150 | ... ''') | ||
2995 | 151 | |||
2996 | 152 | >>> write(sample_buildout, 'buildout.cfg', | ||
2997 | 153 | ... ''' | ||
2998 | 154 | ... [buildout] | ||
2999 | 155 | ... parts = py | ||
3000 | 156 | ... | ||
3001 | 157 | ... [custom_python] | ||
3002 | 158 | ... executable = %(py_path)s | ||
3003 | 159 | ... | ||
3004 | 160 | ... [py] | ||
3005 | 161 | ... recipe = z3c.recipe.scripts:interpreter | ||
3006 | 162 | ... initialization = | ||
3007 | 163 | ... import os | ||
3008 | 164 | ... os.environ['zc.recipe.egg'] = 'baLOOba' | ||
3009 | 165 | ... exec-sitecustomize = true | ||
3010 | 166 | ... eggs = demo<0.3 | ||
3011 | 167 | ... find-links = %(server)s | ||
3012 | 168 | ... index = %(server)s/index | ||
3013 | 169 | ... python = custom_python | ||
3014 | 170 | ... ''' % dict(server=link_server, py_path=py_path)) | ||
3015 | 171 | |||
3016 | 172 | >>> print system(buildout), | ||
3017 | 173 | Uninstalling py. | ||
3018 | 174 | Installing py. | ||
3019 | 175 | Generated interpreter '/sample-buildout/bin/py'. | ||
3020 | 176 | |||
3021 | 177 | >>> cat(sample_buildout, 'parts', 'py', 'sitecustomize.py') | ||
3022 | 178 | ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS | ||
3023 | 179 | <BLANKLINE> | ||
3024 | 180 | import os | ||
3025 | 181 | os.environ['zc.recipe.egg'] = 'baLOOba' | ||
3026 | 182 | <BLANKLINE> | ||
3027 | 183 | # The following is from | ||
3028 | 184 | # /executable_buildout/parts/py/sitecustomize.py | ||
3029 | 185 | ... | ||
3030 | 186 | import os | ||
3031 | 187 | os.environ['zc.buildout'] = 'foo bar baz shazam' | ||
3032 | 188 | |||
3033 | 189 | >>> print system(join(sample_buildout, 'bin', 'py') + ' -c ' + | ||
3034 | 190 | ... '''"import os; print os.environ['zc.recipe.egg']"'''), | ||
3035 | 191 | baLOOba | ||
3036 | 192 | >>> print system(join(sample_buildout, 'bin', 'py') + | ||
3037 | 193 | ... ''' -c "import os; print os.environ['zc.buildout']"'''), | ||
3038 | 194 | foo bar baz shazam | ||
3039 | 195 | |||
3040 | 196 | """ | ||
3041 | 197 | |||
3042 | 198 | def interpreter_recipe_supports_relative_paths_option(): | ||
3043 | 199 | """ | ||
3044 | 200 | This shows that the relative-paths option affects the code for inserting | ||
3045 | 201 | paths into sys.path. | ||
3046 | 202 | |||
3047 | 203 | >>> write(sample_buildout, 'buildout.cfg', | ||
3048 | 204 | ... ''' | ||
3049 | 205 | ... [buildout] | ||
3050 | 206 | ... parts = py | ||
3051 | 207 | ... | ||
3052 | 208 | ... [py] | ||
3053 | 209 | ... recipe = z3c.recipe.scripts:interpreter | ||
3054 | 210 | ... find-links = %(server)s | ||
3055 | 211 | ... index = %(server)s/index | ||
3056 | 212 | ... relative-paths = true | ||
3057 | 213 | ... extra-paths = | ||
3058 | 214 | ... /foo/bar | ||
3059 | 215 | ... ${buildout:directory}/spam | ||
3060 | 216 | ... ''' % dict(server=link_server)) | ||
3061 | 217 | |||
3062 | 218 | >>> print system(buildout), | ||
3063 | 219 | Installing py. | ||
3064 | 220 | Generated interpreter '/sample-buildout/bin/py'. | ||
3065 | 221 | |||
3066 | 222 | Let's look at the site.py that was generated: | ||
3067 | 223 | |||
3068 | 224 | >>> import sys | ||
3069 | 225 | >>> sys.stdout.write('#'); cat(sample_buildout, 'parts', 'py', 'site.py') | ||
3070 | 226 | ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS | ||
3071 | 227 | #... | ||
3072 | 228 | def addsitepackages(known_paths): | ||
3073 | 229 | "..." | ||
3074 | 230 | join = os.path.join | ||
3075 | 231 | base = os.path.dirname(os.path.abspath(os.path.realpath(__file__))) | ||
3076 | 232 | base = os.path.dirname(base) | ||
3077 | 233 | base = os.path.dirname(base) | ||
3078 | 234 | buildout_paths = [ | ||
3079 | 235 | '/foo/bar', | ||
3080 | 236 | join(base, 'spam') | ||
3081 | 237 | ]... | ||
3082 | 238 | |||
3083 | 239 | |||
3084 | 240 | """ | ||
3085 | 241 | |||
3086 | 242 | def setUp(test): | ||
3087 | 243 | zc.buildout.tests.easy_install_SetUp(test) | ||
3088 | 244 | zc.buildout.testing.install_develop('zc.recipe.egg', test) | ||
3089 | 245 | zc.buildout.testing.install_develop('z3c.recipe.scripts', test) | ||
3090 | 246 | |||
3091 | 247 | def setUpSelecting(test): | ||
3092 | 248 | zc.buildout.testselectingpython.setup(test) | ||
3093 | 249 | zc.buildout.testing.install_develop('zc.recipe.egg', test) | ||
3094 | 250 | zc.buildout.testing.install_develop('z3c.recipe.scripts', test) | ||
3095 | 251 | |||
3096 | 252 | def test_suite(): | ||
3097 | 253 | suite = unittest.TestSuite(( | ||
3098 | 254 | doctest.DocFileSuite( | ||
3099 | 255 | 'README.txt', | ||
3100 | 256 | setUp=setUp, tearDown=zc.buildout.testing.buildoutTearDown, | ||
3101 | 257 | checker=renormalizing.RENormalizing([ | ||
3102 | 258 | zc.buildout.testing.normalize_path, | ||
3103 | 259 | zc.buildout.testing.normalize_endings, | ||
3104 | 260 | zc.buildout.testing.normalize_script, | ||
3105 | 261 | zc.buildout.testing.normalize_egg_py, | ||
3106 | 262 | zc.buildout.tests.normalize_bang, | ||
3107 | 263 | (re.compile(r'zc.buildout(-\S+)?[.]egg(-link)?'), | ||
3108 | 264 | 'zc.buildout.egg'), | ||
3109 | 265 | (re.compile('[-d] setuptools-[^-]+-'), 'setuptools-X-'), | ||
3110 | 266 | (re.compile(r'setuptools-[\w.]+-py'), 'setuptools-X-py'), | ||
3111 | 267 | (re.compile(r'eggs\\\\demo'), 'eggs/demo'), | ||
3112 | 268 | (re.compile(r'[a-zA-Z]:\\\\foo\\\\bar'), '/foo/bar'), | ||
3113 | 269 | (re.compile(r'\#!\S+\bpython\S*'), '#!/usr/bin/python'), | ||
3114 | 270 | # Normalize generate_script's Windows interpreter to UNIX: | ||
3115 | 271 | (re.compile(r'\nimport subprocess\n'), '\n'), | ||
3116 | 272 | (re.compile('subprocess\\.call\\(argv, env=environ\\)'), | ||
3117 | 273 | 'os.execve(sys.executable, argv, environ)'), | ||
3118 | 274 | ]) | ||
3119 | 275 | ), | ||
3120 | 276 | doctest.DocTestSuite( | ||
3121 | 277 | setUp=setUp, | ||
3122 | 278 | tearDown=zc.buildout.testing.buildoutTearDown, | ||
3123 | 279 | checker=renormalizing.RENormalizing([ | ||
3124 | 280 | zc.buildout.testing.normalize_path, | ||
3125 | 281 | zc.buildout.testing.normalize_endings, | ||
3126 | 282 | zc.buildout.testing.normalize_egg_py, | ||
3127 | 283 | (re.compile(r'[a-zA-Z]:\\\\foo\\\\bar'), '/foo/bar'), | ||
3128 | 284 | ]), | ||
3129 | 285 | ), | ||
3130 | 286 | |||
3131 | 287 | )) | ||
3132 | 288 | |||
3133 | 289 | return suite | ||
3134 | 290 | |||
3135 | 291 | if __name__ == '__main__': | ||
3136 | 292 | unittest.main(defaultTest='test_suite') | ||
3137 | 293 | |||
3138 | 0 | 294 | ||
3139 | === modified file 'zc.recipe.egg_/setup.py' | |||
3140 | --- zc.recipe.egg_/setup.py 2009-08-06 13:49:47 +0000 | |||
3141 | +++ zc.recipe.egg_/setup.py 2010-02-23 20:41:17 +0000 | |||
3142 | @@ -16,7 +16,7 @@ | |||
3143 | 16 | $Id$ | 16 | $Id$ |
3144 | 17 | """ | 17 | """ |
3145 | 18 | 18 | ||
3147 | 19 | version = '0' | 19 | version = '1.2.3dev' |
3148 | 20 | 20 | ||
3149 | 21 | import os | 21 | import os |
3150 | 22 | from setuptools import setup, find_packages | 22 | from setuptools import setup, find_packages |
3151 | @@ -66,7 +66,7 @@ | |||
3152 | 66 | package_dir = {'':'src'}, | 66 | package_dir = {'':'src'}, |
3153 | 67 | namespace_packages = ['zc', 'zc.recipe'], | 67 | namespace_packages = ['zc', 'zc.recipe'], |
3154 | 68 | install_requires = [ | 68 | install_requires = [ |
3156 | 69 | 'zc.buildout >=1.2.0', | 69 | 'zc.buildout >=1.5.0dev', |
3157 | 70 | 'setuptools'], | 70 | 'setuptools'], |
3158 | 71 | tests_require = ['zope.testing'], | 71 | tests_require = ['zope.testing'], |
3159 | 72 | test_suite = name+'.tests.test_suite', | 72 | test_suite = name+'.tests.test_suite', |
3160 | 73 | 73 | ||
3161 | === modified file 'zc.recipe.egg_/src/zc/recipe/egg/README.txt' | |||
3162 | --- zc.recipe.egg_/src/zc/recipe/egg/README.txt 2010-02-23 20:41:17 +0000 | |||
3163 | +++ zc.recipe.egg_/src/zc/recipe/egg/README.txt 2010-02-23 20:41:17 +0000 | |||
3164 | @@ -154,6 +154,8 @@ | |||
3165 | 154 | interpreter | 154 | interpreter |
3166 | 155 | The name of a script to generate that allows access to a Python | 155 | The name of a script to generate that allows access to a Python |
3167 | 156 | interpreter that has the path set based on the eggs installed. | 156 | interpreter that has the path set based on the eggs installed. |
3168 | 157 | (See the ``z3c.recipe.scripts`` recipe for a more full-featured | ||
3169 | 158 | interpreter.) | ||
3170 | 157 | 159 | ||
3171 | 158 | extra-paths | 160 | extra-paths |
3172 | 159 | Extra paths to include in a generated script. | 161 | Extra paths to include in a generated script. |
3173 | @@ -577,7 +579,7 @@ | |||
3174 | 577 | - demo | 579 | - demo |
3175 | 578 | - other | 580 | - other |
3176 | 579 | 581 | ||
3178 | 580 | >>> cat(sample_buildout, 'bin', 'other') | 582 | >>> cat(sample_buildout, 'bin', 'other') # doctest: +NORMALIZE_WHITESPACE |
3179 | 581 | #!/usr/local/bin/python2.4 | 583 | #!/usr/local/bin/python2.4 |
3180 | 582 | <BLANKLINE> | 584 | <BLANKLINE> |
3181 | 583 | import sys | 585 | import sys |
3182 | @@ -640,3 +642,4 @@ | |||
3183 | 640 | Uninstalling bigdemo. | 642 | Uninstalling bigdemo. |
3184 | 641 | Installing demo. | 643 | Installing demo. |
3185 | 642 | Generated script '/sample-buildout/bin/foo'. | 644 | Generated script '/sample-buildout/bin/foo'. |
3186 | 645 | |||
3187 | 643 | 646 | ||
3188 | === modified file 'zc.recipe.egg_/src/zc/recipe/egg/api.txt' | |||
3189 | --- zc.recipe.egg_/src/zc/recipe/egg/api.txt 2010-02-23 20:41:17 +0000 | |||
3190 | +++ zc.recipe.egg_/src/zc/recipe/egg/api.txt 2010-02-23 20:41:17 +0000 | |||
3191 | @@ -117,6 +117,7 @@ | |||
3192 | 117 | extras = other | 117 | extras = other |
3193 | 118 | find-links = http://localhost:27071/ | 118 | find-links = http://localhost:27071/ |
3194 | 119 | index = http://localhost:27071/index | 119 | index = http://localhost:27071/index |
3195 | 120 | python = buildout | ||
3196 | 120 | recipe = sample | 121 | recipe = sample |
3197 | 121 | 122 | ||
3198 | 122 | If we use the extra-paths option: | 123 | If we use the extra-paths option: |
3199 | 123 | 124 | ||
3200 | === modified file 'zc.recipe.egg_/src/zc/recipe/egg/custom.txt' | |||
3201 | --- zc.recipe.egg_/src/zc/recipe/egg/custom.txt 2010-02-23 20:41:17 +0000 | |||
3202 | +++ zc.recipe.egg_/src/zc/recipe/egg/custom.txt 2010-02-23 20:41:17 +0000 | |||
3203 | @@ -150,6 +150,7 @@ | |||
3204 | 150 | 150 | ||
3205 | 151 | >>> ls(sample_buildout, 'develop-eggs') | 151 | >>> ls(sample_buildout, 'develop-eggs') |
3206 | 152 | d extdemo-1.4-py2.4-unix-i686.egg | 152 | d extdemo-1.4-py2.4-unix-i686.egg |
3207 | 153 | - z3c.recipe.scripts.egg-link | ||
3208 | 153 | - zc.recipe.egg.egg-link | 154 | - zc.recipe.egg.egg-link |
3209 | 154 | 155 | ||
3210 | 155 | Note that no scripts or dependencies are installed. To install | 156 | Note that no scripts or dependencies are installed. To install |
3211 | @@ -231,6 +232,7 @@ | |||
3212 | 231 | >>> ls(sample_buildout, 'develop-eggs') | 232 | >>> ls(sample_buildout, 'develop-eggs') |
3213 | 232 | - demo.egg-link | 233 | - demo.egg-link |
3214 | 233 | d extdemo-1.4-py2.4-unix-i686.egg | 234 | d extdemo-1.4-py2.4-unix-i686.egg |
3215 | 235 | - z3c.recipe.scripts.egg-link | ||
3216 | 234 | - zc.recipe.egg.egg-link | 236 | - zc.recipe.egg.egg-link |
3217 | 235 | 237 | ||
3218 | 236 | But if we run the buildout in the default on-line and newest modes, we | 238 | But if we run the buildout in the default on-line and newest modes, we |
3219 | @@ -248,6 +250,7 @@ | |||
3220 | 248 | - demo.egg-link | 250 | - demo.egg-link |
3221 | 249 | d extdemo-1.4-py2.4-linux-i686.egg | 251 | d extdemo-1.4-py2.4-linux-i686.egg |
3222 | 250 | d extdemo-1.5-py2.4-linux-i686.egg | 252 | d extdemo-1.5-py2.4-linux-i686.egg |
3223 | 253 | - z3c.recipe.scripts.egg-link | ||
3224 | 251 | - zc.recipe.egg.egg-link | 254 | - zc.recipe.egg.egg-link |
3225 | 252 | 255 | ||
3226 | 253 | Controlling the version used | 256 | Controlling the version used |
3227 | @@ -287,6 +290,7 @@ | |||
3228 | 287 | >>> ls(sample_buildout, 'develop-eggs') | 290 | >>> ls(sample_buildout, 'develop-eggs') |
3229 | 288 | - demo.egg-link | 291 | - demo.egg-link |
3230 | 289 | d extdemo-1.4-py2.4-linux-i686.egg | 292 | d extdemo-1.4-py2.4-linux-i686.egg |
3231 | 293 | - z3c.recipe.scripts.egg-link | ||
3232 | 290 | - zc.recipe.egg.egg-link | 294 | - zc.recipe.egg.egg-link |
3233 | 291 | 295 | ||
3234 | 292 | 296 | ||
3235 | @@ -553,6 +557,7 @@ | |||
3236 | 553 | >>> ls('develop-eggs') | 557 | >>> ls('develop-eggs') |
3237 | 554 | - demo.egg-link | 558 | - demo.egg-link |
3238 | 555 | - extdemo.egg-link | 559 | - extdemo.egg-link |
3239 | 560 | - z3c.recipe.scripts.egg-link | ||
3240 | 556 | - zc.recipe.egg.egg-link | 561 | - zc.recipe.egg.egg-link |
3241 | 557 | 562 | ||
3242 | 558 | and the extdemo now has a built extension: | 563 | and the extdemo now has a built extension: |
3243 | 559 | 564 | ||
3244 | === modified file 'zc.recipe.egg_/src/zc/recipe/egg/egg.py' | |||
3245 | --- zc.recipe.egg_/src/zc/recipe/egg/egg.py 2010-02-23 20:41:17 +0000 | |||
3246 | +++ zc.recipe.egg_/src/zc/recipe/egg/egg.py 2010-02-23 20:41:17 +0000 | |||
3247 | @@ -19,11 +19,12 @@ | |||
3248 | 19 | import logging, os, re, zipfile | 19 | import logging, os, re, zipfile |
3249 | 20 | import zc.buildout.easy_install | 20 | import zc.buildout.easy_install |
3250 | 21 | 21 | ||
3251 | 22 | |||
3252 | 22 | class Eggs(object): | 23 | class Eggs(object): |
3253 | 23 | 24 | ||
3254 | 24 | def __init__(self, buildout, name, options): | 25 | def __init__(self, buildout, name, options): |
3255 | 25 | self.buildout = buildout | 26 | self.buildout = buildout |
3257 | 26 | self.name = name | 27 | self.name = self.default_eggs = name |
3258 | 27 | self.options = options | 28 | self.options = options |
3259 | 28 | b_options = buildout['buildout'] | 29 | b_options = buildout['buildout'] |
3260 | 29 | links = options.get('find-links', b_options['find-links']) | 30 | links = options.get('find-links', b_options['find-links']) |
3261 | @@ -52,7 +53,7 @@ | |||
3262 | 52 | # verify that this is None, 'true' or 'false' | 53 | # verify that this is None, 'true' or 'false' |
3263 | 53 | get_bool(options, 'unzip') | 54 | get_bool(options, 'unzip') |
3264 | 54 | 55 | ||
3266 | 55 | python = options.get('python', b_options['python']) | 56 | python = options.setdefault('python', b_options['python']) |
3267 | 56 | options['executable'] = buildout[python]['executable'] | 57 | options['executable'] = buildout[python]['executable'] |
3268 | 57 | 58 | ||
3269 | 58 | def working_set(self, extra=()): | 59 | def working_set(self, extra=()): |
3270 | @@ -65,15 +66,16 @@ | |||
3271 | 65 | 66 | ||
3272 | 66 | distributions = [ | 67 | distributions = [ |
3273 | 67 | r.strip() | 68 | r.strip() |
3275 | 68 | for r in options.get('eggs', self.name).split('\n') | 69 | for r in options.get('eggs', self.default_eggs).split('\n') |
3276 | 69 | if r.strip()] | 70 | if r.strip()] |
3277 | 70 | orig_distributions = distributions[:] | 71 | orig_distributions = distributions[:] |
3278 | 71 | distributions.extend(extra) | 72 | distributions.extend(extra) |
3279 | 72 | 73 | ||
3281 | 73 | if self.buildout['buildout'].get('offline') == 'true': | 74 | if b_options.get('offline') == 'true': |
3282 | 74 | ws = zc.buildout.easy_install.working_set( | 75 | ws = zc.buildout.easy_install.working_set( |
3283 | 75 | distributions, options['executable'], | 76 | distributions, options['executable'], |
3285 | 76 | [options['develop-eggs-directory'], options['eggs-directory']] | 77 | [options['develop-eggs-directory'], |
3286 | 78 | options['eggs-directory']], | ||
3287 | 77 | ) | 79 | ) |
3288 | 78 | else: | 80 | else: |
3289 | 79 | kw = {} | 81 | kw = {} |
3290 | @@ -85,7 +87,7 @@ | |||
3291 | 85 | index=self.index, | 87 | index=self.index, |
3292 | 86 | executable=options['executable'], | 88 | executable=options['executable'], |
3293 | 87 | path=[options['develop-eggs-directory']], | 89 | path=[options['develop-eggs-directory']], |
3295 | 88 | newest=self.buildout['buildout'].get('newest') == 'true', | 90 | newest=b_options.get('newest') == 'true', |
3296 | 89 | allow_hosts=self.allow_hosts, | 91 | allow_hosts=self.allow_hosts, |
3297 | 90 | **kw) | 92 | **kw) |
3298 | 91 | 93 | ||
3299 | @@ -97,16 +99,19 @@ | |||
3300 | 97 | 99 | ||
3301 | 98 | update = install | 100 | update = install |
3302 | 99 | 101 | ||
3304 | 100 | class Scripts(Eggs): | 102 | |
3305 | 103 | class ScriptBase(Eggs): | ||
3306 | 101 | 104 | ||
3307 | 102 | def __init__(self, buildout, name, options): | 105 | def __init__(self, buildout, name, options): |
3311 | 103 | super(Scripts, self).__init__(buildout, name, options) | 106 | super(ScriptBase, self).__init__(buildout, name, options) |
3312 | 104 | 107 | ||
3313 | 105 | options['bin-directory'] = buildout['buildout']['bin-directory'] | 108 | b_options = buildout['buildout'] |
3314 | 109 | |||
3315 | 110 | options['bin-directory'] = b_options['bin-directory'] | ||
3316 | 106 | options['_b'] = options['bin-directory'] # backward compat. | 111 | options['_b'] = options['bin-directory'] # backward compat. |
3317 | 107 | 112 | ||
3318 | 108 | self.extra_paths = [ | 113 | self.extra_paths = [ |
3320 | 109 | os.path.join(buildout['buildout']['directory'], p.strip()) | 114 | os.path.join(b_options['directory'], p.strip()) |
3321 | 110 | for p in options.get('extra-paths', '').split('\n') | 115 | for p in options.get('extra-paths', '').split('\n') |
3322 | 111 | if p.strip() | 116 | if p.strip() |
3323 | 112 | ] | 117 | ] |
3324 | @@ -115,11 +120,9 @@ | |||
3325 | 115 | 120 | ||
3326 | 116 | 121 | ||
3327 | 117 | relative_paths = options.get( | 122 | relative_paths = options.get( |
3331 | 118 | 'relative-paths', | 123 | 'relative-paths', b_options.get('relative-paths', 'false')) |
3329 | 119 | buildout['buildout'].get('relative-paths', 'false') | ||
3330 | 120 | ) | ||
3332 | 121 | if relative_paths == 'true': | 124 | if relative_paths == 'true': |
3334 | 122 | options['buildout-directory'] = buildout['buildout']['directory'] | 125 | options['buildout-directory'] = b_options['directory'] |
3335 | 123 | self._relative_paths = options['buildout-directory'] | 126 | self._relative_paths = options['buildout-directory'] |
3336 | 124 | else: | 127 | else: |
3337 | 125 | self._relative_paths = '' | 128 | self._relative_paths = '' |
3338 | @@ -128,12 +131,13 @@ | |||
3339 | 128 | parse_entry_point = re.compile( | 131 | parse_entry_point = re.compile( |
3340 | 129 | '([^=]+)=(\w+(?:[.]\w+)*):(\w+(?:[.]\w+)*)$' | 132 | '([^=]+)=(\w+(?:[.]\w+)*):(\w+(?:[.]\w+)*)$' |
3341 | 130 | ).match | 133 | ).match |
3342 | 134 | |||
3343 | 131 | def install(self): | 135 | def install(self): |
3344 | 132 | reqs, ws = self.working_set() | 136 | reqs, ws = self.working_set() |
3345 | 133 | options = self.options | 137 | options = self.options |
3346 | 134 | 138 | ||
3347 | 135 | scripts = options.get('scripts') | 139 | scripts = options.get('scripts') |
3349 | 136 | if scripts or scripts is None: | 140 | if scripts or scripts is None or options.get('interpreter'): |
3350 | 137 | if scripts is not None: | 141 | if scripts is not None: |
3351 | 138 | scripts = scripts.split() | 142 | scripts = scripts.split() |
3352 | 139 | scripts = dict([ | 143 | scripts = dict([ |
3353 | @@ -157,22 +161,32 @@ | |||
3354 | 157 | name = dist.project_name | 161 | name = dist.project_name |
3355 | 158 | if name != 'setuptools' and name not in reqs: | 162 | if name != 'setuptools' and name not in reqs: |
3356 | 159 | reqs.append(name) | 163 | reqs.append(name) |
3369 | 160 | 164 | return self._install(reqs, ws, scripts) | |
3358 | 161 | return zc.buildout.easy_install.scripts( | ||
3359 | 162 | reqs, ws, options['executable'], | ||
3360 | 163 | options['bin-directory'], | ||
3361 | 164 | scripts=scripts, | ||
3362 | 165 | extra_paths=self.extra_paths, | ||
3363 | 166 | interpreter=options.get('interpreter'), | ||
3364 | 167 | initialization=options.get('initialization', ''), | ||
3365 | 168 | arguments=options.get('arguments', ''), | ||
3366 | 169 | relative_paths=self._relative_paths, | ||
3367 | 170 | ) | ||
3368 | 171 | |||
3370 | 172 | return () | 165 | return () |
3371 | 173 | 166 | ||
3372 | 174 | update = install | 167 | update = install |
3373 | 175 | 168 | ||
3374 | 169 | def _install(self, reqs, ws, scripts): | ||
3375 | 170 | # Subclasses implement this. | ||
3376 | 171 | raise NotImplementedError() | ||
3377 | 172 | |||
3378 | 173 | |||
3379 | 174 | class Scripts(ScriptBase): | ||
3380 | 175 | |||
3381 | 176 | def _install(self, reqs, ws, scripts): | ||
3382 | 177 | options = self.options | ||
3383 | 178 | return zc.buildout.easy_install.scripts( | ||
3384 | 179 | reqs, ws, options['executable'], | ||
3385 | 180 | options['bin-directory'], | ||
3386 | 181 | scripts=scripts, | ||
3387 | 182 | extra_paths=self.extra_paths, | ||
3388 | 183 | interpreter=options.get('interpreter'), | ||
3389 | 184 | initialization=options.get('initialization', ''), | ||
3390 | 185 | arguments=options.get('arguments', ''), | ||
3391 | 186 | relative_paths=self._relative_paths | ||
3392 | 187 | ) | ||
3393 | 188 | |||
3394 | 189 | |||
3395 | 176 | def get_bool(options, name, default=False): | 190 | def get_bool(options, name, default=False): |
3396 | 177 | value = options.get(name) | 191 | value = options.get(name) |
3397 | 178 | if not value: | 192 | if not value: |
3398 | 179 | 193 | ||
3399 | === modified file 'zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt' | |||
3400 | --- zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt 2010-02-23 20:41:17 +0000 | |||
3401 | +++ zc.recipe.egg_/src/zc/recipe/egg/selecting-python.txt 2010-02-23 20:41:17 +0000 | |||
3402 | @@ -35,7 +35,7 @@ | |||
3403 | 35 | ... index = http://www.python.org/pypi/ | 35 | ... index = http://www.python.org/pypi/ |
3404 | 36 | ... | 36 | ... |
3405 | 37 | ... [python2.4] | 37 | ... [python2.4] |
3407 | 38 | ... executable = %(python23)s | 38 | ... executable = %(python24)s |
3408 | 39 | ... | 39 | ... |
3409 | 40 | ... [demo] | 40 | ... [demo] |
3410 | 41 | ... recipe = zc.recipe.egg | 41 | ... recipe = zc.recipe.egg |
3411 | @@ -43,7 +43,7 @@ | |||
3412 | 43 | ... find-links = %(server)s | 43 | ... find-links = %(server)s |
3413 | 44 | ... python = python2.4 | 44 | ... python = python2.4 |
3414 | 45 | ... interpreter = py-demo | 45 | ... interpreter = py-demo |
3416 | 46 | ... """ % dict(server=link_server, python23=other_executable)) | 46 | ... """ % dict(server=link_server, python24=other_executable)) |
3417 | 47 | 47 | ||
3418 | 48 | Now, if we run the buildout: | 48 | Now, if we run the buildout: |
3419 | 49 | 49 | ||
3420 | @@ -55,8 +55,6 @@ | |||
3421 | 55 | Getting distribution for 'demo<0.3'. | 55 | Getting distribution for 'demo<0.3'. |
3422 | 56 | Got demo 0.2. | 56 | Got demo 0.2. |
3423 | 57 | Getting distribution for 'demoneeded'. | 57 | Getting distribution for 'demoneeded'. |
3424 | 58 | Getting distribution for 'setuptools'. | ||
3425 | 59 | Got setuptools 0.6. | ||
3426 | 60 | Got demoneeded 1.2c1. | 58 | Got demoneeded 1.2c1. |
3427 | 61 | Generated script '/sample-buildout/bin/demo'. | 59 | Generated script '/sample-buildout/bin/demo'. |
3428 | 62 | Generated interpreter '/sample-buildout/bin/py-demo'. | 60 | Generated interpreter '/sample-buildout/bin/py-demo'. |
3429 | @@ -66,7 +64,6 @@ | |||
3430 | 66 | >>> ls(sample_buildout, 'eggs') | 64 | >>> ls(sample_buildout, 'eggs') |
3431 | 67 | - demo-0.2-py2.4.egg | 65 | - demo-0.2-py2.4.egg |
3432 | 68 | - demoneeded-1.2c1-py2.4.egg | 66 | - demoneeded-1.2c1-py2.4.egg |
3433 | 69 | d setuptools-0.6-py2.4.egg | ||
3434 | 70 | d setuptools-0.6-py2.5.egg | 67 | d setuptools-0.6-py2.5.egg |
3435 | 71 | - zc.buildout-1.0-py2.5.egg | 68 | - zc.buildout-1.0-py2.5.egg |
3436 | 72 | 69 |
This is a completely reworked approach to using zc.buildout with a system Python (or any Python with a populated site packages directory), deprecating my previous attempt.
The basic approach revolves around writing our own site.py and sitecustomize.py that are responsible for setting up paths and any other environment changes desired. Then if the path to these rewritten files are included (via PYTHONPATH, or with -S followed by sys.path manipulation and ``import site``) you have the environment set up, using the standard Python mechanism.
Similarly to my past work, the point that got complicated was namespace packages. The solution shown here is straightforward, as you can see in the easy_install.txt discussion.
However, if you try to use a system Python, you expose its packages using ``add-site- packages= true``, and you use eggs with namespace packages, it will incur a startup cost for generated scripts that is quadratically related to the number of eggs added by zc.buildout, because of code in pkg_resources. (We also have that same behavior now in Launchpad.) While I did a number of experiments to try and bypass the inefficiencies of the pkg_resources code, I discarded them, because Launchpad is using pkg_resources and expects it to be fully set up. Other code may be the same. The right solution is to fix pkg_resources, in Setuptools or Distribute, at which point the approach I have in this branch should still work, and be much faster.
I did punt a bit in the Windows version of the new interpreter script: I could not get the standard Windows path hacks to work for sys.exec*. I used subprocess instead for Windows, and commented this section heavily.
Obviously, this is a huge branch. I did not see a good way of subdividing it beyond what I already did (~gary/ zc.buildout/ python- support- 1-cleanup, ~gary/zc. buildout/ python- support- 2-bootstrap, and ~gary/zc. buildout/ python- support- 3-options) . I did consider the following subdivision:
- First, you could look at the changes to easy_install.py and easy_install.txt. This is the heart of the changes. It refactors the existing script generation into bits I could reuse more easily, and then makes a new version script generation function (``generated_ scripts` `) that works as I describe above.
- Second, you could look at zc.recipe.egg and z3c.recipe.scripts. zc.recipe.egg is refactored so I could reuse bits more easily. z3c.recipe.scripts is a brand new recipe that uses generate_scripts, as described above.
- Finally, you could look at the new tests and test changes in zc.buildout. They use zc.recipe.egg and z3c.recipe.scripts to test that system installs can work as desired- -essentially showing that all of the earlier work does in fact produce a system that does what we want.
I didn't feel that separating them made a lot of sense, because you could not test whether generate_scripts actually did what we wanted until we had z3c.recipe.scripts also available. The changes are all interrelated.
Something to note is that zc.recipe.egg does not have any changed behavior. Also, it is not safe to use with a system Python. If you want that behavior for scripts and interpreters, use z3c.rec...