Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.buildout
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kirill Smelkov
slapos.buildout
Commits
98b7c55c
Commit
98b7c55c
authored
Feb 20, 2010
by
Gary Poster
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
support limiting packages from site-packages
parent
e73c70bb
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
628 additions
and
130 deletions
+628
-130
.bzrignore
.bzrignore
+9
-0
src/zc/buildout/easy_install.py
src/zc/buildout/easy_install.py
+215
-95
src/zc/buildout/easy_install.txt
src/zc/buildout/easy_install.txt
+72
-0
src/zc/buildout/testing.py
src/zc/buildout/testing.py
+33
-6
src/zc/buildout/tests.py
src/zc/buildout/tests.py
+299
-29
No files found.
.bzrignore
0 → 100644
View file @
98b7c55c
.installed.cfg
bin
build
develop-eggs
eggs
parts
src/zc.buildout.egg-info
z3c.recipe.scripts_/src/z3c.recipe.scripts.egg-info
zc.recipe.egg_/src/zc.recipe.egg.egg-info
src/zc/buildout/easy_install.py
View file @
98b7c55c
...
@@ -19,6 +19,7 @@ installed.
...
@@ -19,6 +19,7 @@ installed.
"""
"""
import
distutils.errors
import
distutils.errors
import
fnmatch
import
glob
import
glob
import
logging
import
logging
import
os
import
os
...
@@ -67,6 +68,64 @@ buildout_and_setuptools_path = [
...
@@ -67,6 +68,64 @@ buildout_and_setuptools_path = [
pkg_resources
.
Requirement
.
parse
(
'zc.buildout'
)).
location
,
pkg_resources
.
Requirement
.
parse
(
'zc.buildout'
)).
location
,
]
]
def
_get_system_paths
(
executable
):
"""return lists of standard lib and site paths for executable.
"""
# We want to get a list of the site packages, which is not easy.
# The canonical way to do this is to use
# distutils.sysconfig.get_python_lib(), but that only returns a
# single path, which does not reflect reality for many system
# Pythons, which have multiple additions. Instead, we start Python
# with -S, which does not import site.py and set up the extra paths
# like site-packages or (Ubuntu/Debian) dist-packages and
# python-support. We then compare that sys.path with the normal one
# (minus user packages if this is Python 2.6, because we don't
# support those (yet?). The set of the normal one minus the set of
# the ones in ``python -S`` is the set of packages that are
# effectively site-packages.
#
# The given executable might not be the current executable, so it is
# appropriate to do another subprocess to figure out what the
# additional site-package paths are. Moreover, even if this
# executable *is* the current executable, this code might be run in
# the context of code that has manipulated the sys.path--for
# instance, to add local zc.buildout or setuptools eggs.
def
get_sys_path
(
*
args
,
**
kwargs
):
cmd
=
[
executable
]
cmd
.
extend
(
args
)
cmd
.
extend
([
"-c"
,
"import sys, os;"
"print repr([os.path.normpath(p) for p in sys.path if p])"
])
# Windows needs some (as yet to be determined) part of the real env.
env
=
os
.
environ
.
copy
()
env
.
update
(
kwargs
)
_proc
=
subprocess
.
Popen
(
cmd
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
,
env
=
env
)
stdout
,
stderr
=
_proc
.
communicate
();
if
_proc
.
returncode
:
raise
RuntimeError
(
'error trying to get system packages:
\
n
%s'
%
(
stderr
,))
res
=
eval
(
stdout
.
strip
())
try
:
res
.
remove
(
'.'
)
except
ValueError
:
pass
return
res
stdlib
=
get_sys_path
(
'-S'
)
# stdlib only
no_user_paths
=
get_sys_path
(
PYTHONNOUSERSITE
=
'x'
)
site_paths
=
[
p
for
p
in
no_user_paths
if
p
not
in
stdlib
]
return
(
stdlib
,
site_paths
)
def
_get_version_info
(
executable
):
cmd
=
[
executable
,
'-Sc'
,
'import sys; print repr(sys.version_info)'
]
_proc
=
subprocess
.
Popen
(
cmd
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
)
stdout
,
stderr
=
_proc
.
communicate
();
if
_proc
.
returncode
:
raise
RuntimeError
(
'error trying to get system packages:
\
n
%s'
%
(
stderr
,))
return
eval
(
stdout
.
strip
())
class
IncompatibleVersionError
(
zc
.
buildout
.
UserError
):
class
IncompatibleVersionError
(
zc
.
buildout
.
UserError
):
"""A specified version is incompatible with a given requirement.
"""A specified version is incompatible with a given requirement.
...
@@ -109,7 +168,12 @@ class AllowHostsPackageIndex(setuptools.package_index.PackageIndex):
...
@@ -109,7 +168,12 @@ class AllowHostsPackageIndex(setuptools.package_index.PackageIndex):
_indexes = {}
_indexes = {}
def _get_index(executable, index_url, find_links, allow_hosts=('
*
',)):
def _get_index(executable, index_url, find_links, allow_hosts=('
*
',),
path=None):
# If path is None, the index will use sys.path. If you provide an empty
# path ([]), it will complain uselessly about missing index pages for
# packages found in the paths that you expect to use. Therefore, this path
# is always the same as the _env path in the Installer.
key = executable, index_url, tuple(find_links)
key = executable, index_url, tuple(find_links)
index = _indexes.get(key)
index = _indexes.get(key)
if index is not None:
if index is not None:
...
@@ -118,7 +182,8 @@ def _get_index(executable, index_url, find_links, allow_hosts=('*',)):
...
@@ -118,7 +182,8 @@ def _get_index(executable, index_url, find_links, allow_hosts=('*',)):
if index_url is None:
if index_url is None:
index_url = default_index_url
index_url = default_index_url
index = AllowHostsPackageIndex(
index = AllowHostsPackageIndex(
index_url, hosts=allow_hosts, python=_get_version(executable)
index_url, hosts=allow_hosts, search_path=path,
python=_get_version(executable)
)
)
if find_links:
if find_links:
...
@@ -192,6 +257,8 @@ class Installer:
...
@@ -192,6 +257,8 @@ class Installer:
_use_dependency_links
=
True
_use_dependency_links
=
True
_allow_picked_versions
=
True
_allow_picked_versions
=
True
_always_unzip
=
False
_always_unzip
=
False
_include_site_packages
=
True
_allowed_eggs_from_site_packages
=
(
'*'
,)
def
__init__
(
self
,
def
__init__
(
self
,
dest
=
None
,
dest
=
None
,
...
@@ -203,6 +270,8 @@ class Installer:
...
@@ -203,6 +270,8 @@ class Installer:
newest
=
True
,
newest
=
True
,
versions
=
None
,
versions
=
None
,
use_dependency_links
=
None
,
use_dependency_links
=
None
,
include_site_packages
=
None
,
allowed_eggs_from_site_packages
=
None
,
allow_hosts
=
(
'*'
,)
allow_hosts
=
(
'*'
,)
):
):
self
.
_dest
=
dest
self
.
_dest
=
dest
...
@@ -225,7 +294,28 @@ class Installer:
...
@@ -225,7 +294,28 @@ class Installer:
self
.
_executable
=
executable
self
.
_executable
=
executable
if
always_unzip
is
not
None
:
if
always_unzip
is
not
None
:
self
.
_always_unzip
=
always_unzip
self
.
_always_unzip
=
always_unzip
path
=
(
path
and
path
[:]
or
[])
+
buildout_and_setuptools_path
path
=
(
path
and
path
[:]
or
[])
if
include_site_packages
is
not
None
:
self
.
_include_site_packages
=
include_site_packages
if
allowed_eggs_from_site_packages
is
not
None
:
self
.
_allowed_eggs_from_site_packages
=
tuple
(
allowed_eggs_from_site_packages
)
stdlib
,
self
.
_site_packages
=
_get_system_paths
(
executable
)
version_info
=
_get_version_info
(
executable
)
if
version_info
==
sys
.
version_info
:
# Maybe we can add the buildout and setuptools path. If we
# are including site_packages, we only have to include the extra
# bits here, so we don't duplicate. On the other hand, if we
# are not including site_packages, we only want to include the
# parts that are not in site_packages, so the code is the same.
path
.
extend
(
set
(
buildout_and_setuptools_path
).
difference
(
self
.
_site_packages
))
if
self
.
_include_site_packages
:
path
.
extend
(
self
.
_site_packages
)
# else we could try to still include the buildout_and_setuptools_path
# if the elements are not in site_packages, but we're not bothering
# with this optimization for now, in the name of code simplicity.
if
dest
is
not
None
and
dest
not
in
path
:
if
dest
is
not
None
and
dest
not
in
path
:
path
.
insert
(
0
,
dest
)
path
.
insert
(
0
,
dest
)
self
.
_path
=
path
self
.
_path
=
path
...
@@ -234,13 +324,42 @@ class Installer:
...
@@ -234,13 +324,42 @@ class Installer:
self
.
_newest
=
newest
self
.
_newest
=
newest
self
.
_env
=
pkg_resources
.
Environment
(
path
,
self
.
_env
=
pkg_resources
.
Environment
(
path
,
python
=
_get_version
(
executable
))
python
=
_get_version
(
executable
))
self
.
_index
=
_get_index
(
executable
,
index
,
links
,
self
.
_allow_hosts
)
self
.
_index
=
_get_index
(
executable
,
index
,
links
,
self
.
_allow_hosts
,
self
.
_path
)
if
versions
is
not
None
:
if
versions
is
not
None
:
self
.
_versions
=
versions
self
.
_versions
=
versions
_allowed_eggs_from_site_packages_regex
=
None
def
allow_site_package_egg
(
self
,
name
):
if
(
not
self
.
_include_site_packages
or
not
self
.
_allowed_eggs_from_site_packages
):
# If the answer is a blanket "no," perform a shortcut.
return
False
if
self
.
_allowed_eggs_from_site_packages_regex
is
None
:
pattern
=
'(%s)'
%
(
'|'
.
join
(
fnmatch
.
translate
(
name
)
for
name
in
self
.
_allowed_eggs_from_site_packages
),
)
self
.
_allowed_eggs_from_site_packages_regex
=
re
.
compile
(
pattern
)
return
bool
(
self
.
_allowed_eggs_from_site_packages_regex
.
match
(
name
))
def
_satisfied
(
self
,
req
,
source
=
None
):
def
_satisfied
(
self
,
req
,
source
=
None
):
dists
=
[
dist
for
dist
in
self
.
_env
[
req
.
project_name
]
if
dist
in
req
]
# We get all distributions that match the given requirement. If we are
# not supposed to include site-packages for the given egg, we also
# filter those out. Even if include_site_packages is False and so we
# have excluded site packages from the _env's paths (see
# Installer.__init__), we need to do the filtering here because an
# .egg-link, such as one for setuptools or zc.buildout installed by
# zc.buildout.buildout.Buildout.bootstrap, can indirectly include a
# path in our _site_packages.
dists
=
[
dist
for
dist
in
self
.
_env
[
req
.
project_name
]
if
(
dist
in
req
and
(
dist
.
location
not
in
self
.
_site_packages
or
self
.
allow_site_package_egg
(
dist
.
project_name
))
)
]
if
not
dists
:
if
not
dists
:
logger
.
debug
(
'We have no distributions for %s that satisfies %r.'
,
logger
.
debug
(
'We have no distributions for %s that satisfies %r.'
,
req
.
project_name
,
str
(
req
))
req
.
project_name
,
str
(
req
))
...
@@ -441,13 +560,21 @@ class Installer:
...
@@ -441,13 +560,21 @@ class Installer:
# Nothing is available.
# Nothing is available.
return
None
return
None
# Filter the available dists for the requirement and source flag
# Filter the available dists for the requirement and source flag. If
dists
=
[
dist
for
dist
in
index
[
requirement
.
project_name
]
# we are not supposed to include site-packages for the given egg, we
if
((
dist
in
requirement
)
# also filter those out. Even if include_site_packages is False and so
and
# we have excluded site packages from the _env's paths (see
((
not
source
)
or
# Installer.__init__), we need to do the filtering here because an
(
dist
.
precedence
==
pkg_resources
.
SOURCE_DIST
)
# .egg-link, such as one for setuptools or zc.buildout installed by
)
# zc.buildout.buildout.Buildout.bootstrap, can indirectly include a
# path in our _site_packages.
dists
=
[
dist
for
dist
in
index
[
requirement
.
project_name
]
if
(
dist
in
requirement
and
(
dist
.
location
not
in
self
.
_site_packages
or
self
.
allow_site_package_egg
(
dist
.
project_name
))
and
(
(
not
source
)
or
(
dist
.
precedence
==
pkg_resources
.
SOURCE_DIST
))
)
)
]
]
...
@@ -608,7 +735,7 @@ class Installer:
...
@@ -608,7 +735,7 @@ class Installer:
self
.
_links
.
append
(
link
)
self
.
_links
.
append
(
link
)
self
.
_index
=
_get_index
(
self
.
_executable
,
self
.
_index
=
_get_index
(
self
.
_executable
,
self
.
_index_url
,
self
.
_links
,
self
.
_index_url
,
self
.
_links
,
self
.
_allow_hosts
)
self
.
_allow_hosts
,
self
.
_path
)
for
dist
in
dists
:
for
dist
in
dists
:
# Check whether we picked a version and, if we did, report it:
# Check whether we picked a version and, if we did, report it:
...
@@ -689,35 +816,52 @@ class Installer:
...
@@ -689,35 +816,52 @@ class Installer:
self
.
_maybe_add_setuptools
(
ws
,
dist
)
self
.
_maybe_add_setuptools
(
ws
,
dist
)
# OK, we have the requested distributions and they're in the working
# OK, we have the requested distributions and they're in the working
# set, but they may have unmet requirements. We'll simply keep
# set, but they may have unmet requirements. We'll resolve these
# trying to resolve requirements, adding missing requirements as they
# requirements. This is code modified from
# are reported.
# pkg_resources.WorkingSet.resolve. We can't reuse that code directly
#
# because we have to constrain our requirements (see
# Note that we don't pass in the environment, because we want
# versions_section_ignored_for_dependency_in_favor_of_site_packages in
# zc.buildout.tests).
requirements
.
reverse
()
# Set up the stack.
processed
=
{}
# This is a set of processed requirements.
best
=
{}
# This is a mapping of key -> dist.
# Note that we don't use the existing environment, because we want
# to look for new eggs unless what we have is the best that
# to look for new eggs unless what we have is the best that
# matches the requirement.
# matches the requirement.
while
1
:
env
=
pkg_resources
.
Environment
(
ws
.
entries
)
while
requirements
:
# Process dependencies breadth-first.
req
=
self
.
_constrain
(
requirements
.
pop
(
0
))
if
req
in
processed
:
# Ignore cyclic or redundant dependencies.
continue
dist
=
best
.
get
(
req
.
key
)
if
dist
is
None
:
# Find the best distribution and add it to the map.
dist
=
ws
.
by_key
.
get
(
req
.
key
)
if
dist
is
None
:
try
:
try
:
ws
.
resolve
(
requirement
s
)
dist
=
best
[
req
.
key
]
=
env
.
best_match
(
req
,
w
s
)
except
pkg_resources
.
DistributionNotFound
,
err
:
except
pkg_resources
.
VersionConflict
,
err
:
[
requirement
]
=
err
raise
VersionConflict
(
err
,
ws
)
requirement
=
self
.
_constrain
(
requirement
)
if
dist
is
None
:
if
destination
:
if
destination
:
logger
.
debug
(
'Getting required %r'
,
str
(
requirement
))
logger
.
debug
(
'Getting required %r'
,
str
(
req
))
else
:
else
:
logger
.
debug
(
'Adding required %r'
,
str
(
requirement
))
logger
.
debug
(
'Adding required %r'
,
str
(
req
))
_log_requirement
(
ws
,
requirement
)
_log_requirement
(
ws
,
req
)
for
dist
in
self
.
_get_dist
(
req
,
for
dist
in
self
.
_get_dist
(
requirement
,
ws
,
self
.
_always_unzip
ws
,
self
.
_always_unzip
):
):
ws
.
add
(
dist
)
ws
.
add
(
dist
)
self
.
_maybe_add_setuptools
(
ws
,
dist
)
self
.
_maybe_add_setuptools
(
ws
,
dist
)
except
pkg_resources
.
VersionConflict
,
err
:
if
dist
not
in
req
:
raise
VersionConflict
(
err
,
ws
)
# Oops, the "best" so far conflicts with a dependency.
else
:
raise
VersionConflict
(
break
pkg_resources
.
VersionConflict
(
dist
,
req
),
ws
)
requirements
.
extend
(
dist
.
requires
(
req
.
extras
)[::
-
1
])
processed
[
req
]
=
True
if
dist
.
location
in
self
.
_site_packages
:
logger
.
debug
(
'Egg from site-packages: %s'
,
dist
)
return
ws
return
ws
def
build
(
self
,
spec
,
build_ext
):
def
build
(
self
,
spec
,
build_ext
):
...
@@ -812,6 +956,18 @@ def prefer_final(setting=None):
...
@@ -812,6 +956,18 @@ def prefer_final(setting=None):
Installer
.
_prefer_final
=
bool
(
setting
)
Installer
.
_prefer_final
=
bool
(
setting
)
return
old
return
old
def
include_site_packages
(
setting
=
None
):
old
=
Installer
.
_include_site_packages
if
setting
is
not
None
:
Installer
.
_include_site_packages
=
bool
(
setting
)
return
old
def
allowed_eggs_from_site_packages
(
setting
=
None
):
old
=
Installer
.
_allowed_eggs_from_site_packages
if
setting
is
not
None
:
Installer
.
_allowed_eggs_from_site_packages
=
tuple
(
setting
)
return
old
def
use_dependency_links
(
setting
=
None
):
def
use_dependency_links
(
setting
=
None
):
old
=
Installer
.
_use_dependency_links
old
=
Installer
.
_use_dependency_links
if
setting
is
not
None
:
if
setting
is
not
None
:
...
@@ -834,9 +990,13 @@ def install(specs, dest,
...
@@ -834,9 +990,13 @@ def install(specs, dest,
links
=
(),
index
=
None
,
links
=
(),
index
=
None
,
executable
=
sys
.
executable
,
always_unzip
=
None
,
executable
=
sys
.
executable
,
always_unzip
=
None
,
path
=
None
,
working_set
=
None
,
newest
=
True
,
versions
=
None
,
path
=
None
,
working_set
=
None
,
newest
=
True
,
versions
=
None
,
use_dependency_links
=
None
,
allow_hosts
=
(
'*'
,)):
use_dependency_links
=
None
,
include_site_packages
=
None
,
allowed_eggs_from_site_packages
=
None
,
allow_hosts
=
(
'*'
,)):
installer
=
Installer
(
dest
,
links
,
index
,
executable
,
always_unzip
,
path
,
installer
=
Installer
(
dest
,
links
,
index
,
executable
,
always_unzip
,
path
,
newest
,
versions
,
use_dependency_links
,
newest
,
versions
,
use_dependency_links
,
include_site_packages
=
include_site_packages
,
allowed_eggs_from_site_packages
=
allowed_eggs_from_site_packages
,
allow_hosts
=
allow_hosts
)
allow_hosts
=
allow_hosts
)
return
installer
.
install
(
specs
,
working_set
)
return
installer
.
install
(
specs
,
working_set
)
...
@@ -844,9 +1004,14 @@ def install(specs, dest,
...
@@ -844,9 +1004,14 @@ def install(specs, dest,
def
build
(
spec
,
dest
,
build_ext
,
def
build
(
spec
,
dest
,
build_ext
,
links
=
(),
index
=
None
,
links
=
(),
index
=
None
,
executable
=
sys
.
executable
,
executable
=
sys
.
executable
,
path
=
None
,
newest
=
True
,
versions
=
None
,
allow_hosts
=
(
'*'
,)):
path
=
None
,
newest
=
True
,
versions
=
None
,
include_site_packages
=
None
,
allowed_eggs_from_site_packages
=
None
,
allow_hosts
=
(
'*'
,)):
installer
=
Installer
(
dest
,
links
,
index
,
executable
,
True
,
path
,
newest
,
installer
=
Installer
(
dest
,
links
,
index
,
executable
,
True
,
path
,
newest
,
versions
,
allow_hosts
=
allow_hosts
)
versions
,
include_site_packages
=
include_site_packages
,
allowed_eggs_from_site_packages
=
allowed_eggs_from_site_packages
,
allow_hosts
=
allow_hosts
)
return
installer
.
build
(
spec
,
build_ext
)
return
installer
.
build
(
spec
,
build_ext
)
...
@@ -941,9 +1106,12 @@ def develop(setup, dest,
...
@@ -941,9 +1106,12 @@ def develop(setup, dest,
undo
.
reverse
()
undo
.
reverse
()
[
f
()
for
f
in
undo
]
[
f
()
for
f
in
undo
]
def
working_set
(
specs
,
executable
,
path
,
include_site_packages
=
None
,
def
working_set
(
specs
,
executable
,
path
):
allowed_eggs_from_site_packages
=
None
):
return
install
(
specs
,
None
,
executable
=
executable
,
path
=
path
)
return
install
(
specs
,
None
,
executable
=
executable
,
path
=
path
,
include_site_packages
=
include_site_packages
,
allowed_eggs_from_site_packages
=
allowed_eggs_from_site_packages
)
############################################################################
############################################################################
# Script generation functions
# Script generation functions
...
@@ -1276,54 +1444,6 @@ if _interactive:
...
@@ -1276,54 +1444,6 @@ if _interactive:
# These are used only by the newer ``generate_scripts`` function.
# These are used only by the newer ``generate_scripts`` function.
def
_get_system_paths
(
executable
):
"""return lists of standard lib and site paths for executable.
"""
# We want to get a list of the site packages, which is not easy.
# The canonical way to do this is to use
# distutils.sysconfig.get_python_lib(), but that only returns a
# single path, which does not reflect reality for many system
# Pythons, which have multiple additions. Instead, we start Python
# with -S, which does not import site.py and set up the extra paths
# like site-packages or (Ubuntu/Debian) dist-packages and
# python-support. We then compare that sys.path with the normal one
# (minus user packages if this is Python 2.6, because we don't
# support those (yet?). The set of the normal one minus the set of
# the ones in ``python -S`` is the set of packages that are
# effectively site-packages.
#
# The given executable might not be the current executable, so it is
# appropriate to do another subprocess to figure out what the
# additional site-package paths are. Moreover, even if this
# executable *is* the current executable, this code might be run in
# the context of code that has manipulated the sys.path--for
# instance, to add local zc.buildout or setuptools eggs.
def
get_sys_path
(
*
args
,
**
kwargs
):
cmd
=
[
executable
]
cmd
.
extend
(
args
)
cmd
.
extend
([
"-c"
,
"import sys, os;"
"print repr([os.path.normpath(p) for p in sys.path if p])"
])
# Windows needs some (as yet to be determined) part of the real env.
env
=
os
.
environ
.
copy
()
env
.
update
(
kwargs
)
_proc
=
subprocess
.
Popen
(
cmd
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
,
env
=
env
)
stdout
,
stderr
=
_proc
.
communicate
();
if
_proc
.
returncode
:
raise
RuntimeError
(
'error trying to get system packages:
\
n
%s'
%
(
stderr
,))
res
=
eval
(
stdout
.
strip
())
try
:
res
.
remove
(
'.'
)
except
ValueError
:
pass
return
res
stdlib
=
get_sys_path
(
'-S'
)
# stdlib only
no_user_paths
=
get_sys_path
(
PYTHONNOUSERSITE
=
'x'
)
site_paths
=
[
p
for
p
in
no_user_paths
if
p
not
in
stdlib
]
return
(
stdlib
,
site_paths
)
def
_get_module_file
(
executable
,
name
):
def
_get_module_file
(
executable
,
name
):
"""Return a module's file path.
"""Return a module's file path.
...
...
src/zc/buildout/easy_install.txt
View file @
98b7c55c
...
@@ -89,6 +89,14 @@ use_dependency_links
...
@@ -89,6 +89,14 @@ use_dependency_links
for using dependency_links in preference to other
for using dependency_links in preference to other
locations. Defaults to true.
locations. Defaults to true.
include_site_packages
A flag indicating whether Python's non-standard-library packages should
be available for finding dependencies. Defaults to true.
Paths outside of Python's standard library--or more precisely, those that
are not included when Python is started with the -S argument--are loosely
referred to as "site-packages" here.
relative_paths
relative_paths
Adjust egg paths so they are relative to the script path. This
Adjust egg paths so they are relative to the script path. This
allows scripts to work when scripts and eggs are moved, as long as
allows scripts to work when scripts and eggs are moved, as long as
...
@@ -399,6 +407,68 @@ dictionary:
...
@@ -399,6 +407,68 @@ dictionary:
>>> [d.version for d in ws]
>>> [d.version for d in ws]
['0.3', '1.1']
['0.3', '1.1']
Dependencies in Site Packages
-----------------------------
Paths outside of Python's standard library--or more precisely, those that are
not included when Python is started with the -S argument--are loosely referred
to as "site-packages" here. These site-packages are searched by default for
distributions. This can be disabled, so that, for instance, a system Python
can be used with buildout, cleaned of any packages installed by a user or
system package manager.
The default behavior can be controlled and introspected using
zc.buildout.easy_install.include_site_packages.
>>> zc.buildout.easy_install.include_site_packages()
True
Here's an example of using a Python executable that includes our dependencies.
Our "py_path" will have the "demoneeded," and "demo" packages available.
We'll simply be asking for "demoneeded" here, but without any external
index or links.
>>> from zc.buildout.tests import create_sample_sys_install
>>> py_path, site_packages_path = make_py()
>>> create_sample_sys_install(site_packages_path)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None)
>>> [dist.project_name for dist in workingset]
['demoneeded']
That worked fine. Let's try again with site packages not allowed. We'll
change the policy by changing the default. Notice that the function for
changing the default value returns the previous value.
>>> zc.buildout.easy_install.include_site_packages(False)
True
>>> zc.buildout.easy_install.include_site_packages()
False
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None)
Traceback (most recent call last):
...
MissingDistribution: Couldn't find a distribution for 'demoneeded'.
>>> zc.buildout.easy_install.clear_index_cache()
Now we'll reset the default.
>>> zc.buildout.easy_install.include_site_packages(True)
False
>>> zc.buildout.easy_install.include_site_packages()
True
Dependency links
Dependency links
----------------
----------------
...
@@ -1259,6 +1329,7 @@ call to another text fixture to create.
...
@@ -1259,6 +1329,7 @@ call to another text fixture to create.
>>> namespace_eggs = tmpdir('namespace_eggs')
>>> namespace_eggs = tmpdir('namespace_eggs')
>>> create_sample_namespace_eggs(namespace_eggs)
>>> create_sample_namespace_eggs(namespace_eggs)
>>> reset_interpreter()
>>> ws = zc.buildout.easy_install.install(
>>> ws = zc.buildout.easy_install.install(
... ['demo', 'tellmy.fortune'], join(interpreter_dir, 'eggs'),
... ['demo', 'tellmy.fortune'], join(interpreter_dir, 'eggs'),
... links=[link_server, namespace_eggs], index=link_server+'index/')
... links=[link_server, namespace_eggs], index=link_server+'index/')
...
@@ -1319,6 +1390,7 @@ The most complex that this function gets is if you use namespace packages,
...
@@ -1319,6 +1390,7 @@ The most complex that this function gets is if you use namespace packages,
include site-packages, and use relative paths. For completeness, we'll look
include site-packages, and use relative paths. For completeness, we'll look
at that result.
at that result.
>>> reset_interpreter()
>>> generated = zc.buildout.easy_install.generate_scripts(
>>> generated = zc.buildout.easy_install.generate_scripts(
... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir,
... interpreter_bin_dir, ws, sys.executable, interpreter_parts_dir,
... interpreter='py', add_site_packages=True,
... interpreter='py', add_site_packages=True,
...
...
src/zc/buildout/testing.py
View file @
98b7c55c
...
@@ -222,11 +222,37 @@ def wait_until(label, func, *args, **kw):
...
@@ -222,11 +222,37 @@ def wait_until(label, func, *args, **kw):
time
.
sleep
(
0.01
)
time
.
sleep
(
0.01
)
raise
ValueError
(
'Timed out waiting for: '
+
label
)
raise
ValueError
(
'Timed out waiting for: '
+
label
)
def
get_installer_values
():
"""Get the current values for the easy_install module.
This is necessary because instantiating a Buildout will force the
Buildout's values on the installer.
Returns a dict of names-values suitable for set_installer_values."""
names
=
(
'default_versions'
,
'download_cache'
,
'install_from_cache'
,
'prefer_final'
,
'include_site_packages'
,
'allowed_eggs_from_site_packages'
,
'use_dependency_links'
,
'allow_picked_versions'
,
'always_unzip'
)
values
=
{}
for
name
in
names
:
values
[
name
]
=
getattr
(
zc
.
buildout
.
easy_install
,
name
)()
return
values
def
set_installer_values
(
values
):
"""Set the given values on the installer."""
for
name
,
value
in
values
.
items
():
getattr
(
zc
.
buildout
.
easy_install
,
name
)(
value
)
def
make_buildout
():
def
make_buildout
():
# Create a basic buildout.cfg to avoid a warning from buildout:
"""Make a buildout that uses this version of zc.buildout."""
# Create a basic buildout.cfg to avoid a warning from buildout.
open
(
'buildout.cfg'
,
'w'
).
write
(
open
(
'buildout.cfg'
,
'w'
).
write
(
"[buildout]
\
n
parts =
\
n
"
"[buildout]
\
n
parts =
\
n
"
)
)
# Get state of installer defaults so we can reinstate them (instantiating
# a Buildout will force the Buildout's defaults on the installer).
installer_values
=
get_installer_values
()
# Use the buildout bootstrap command to create a buildout
# Use the buildout bootstrap command to create a buildout
zc
.
buildout
.
buildout
.
Buildout
(
zc
.
buildout
.
buildout
.
Buildout
(
'buildout.cfg'
,
'buildout.cfg'
,
...
@@ -234,20 +260,23 @@ def make_buildout():
...
@@ -234,20 +260,23 @@ def make_buildout():
# trick bootstrap into putting the buildout develop egg
# trick bootstrap into putting the buildout develop egg
# in the eggs dir.
# in the eggs dir.
(
'buildout'
,
'develop-eggs-directory'
,
'eggs'
),
(
'buildout'
,
'develop-eggs-directory'
,
'eggs'
),
]
],
user_defaults
=
False
,
).
bootstrap
([])
).
bootstrap
([])
# Create the develop-eggs dir, which didn't get created the usual
# Create the develop-eggs dir, which didn't get created the usual
# way due to the trick above:
# way due to the trick above:
os
.
mkdir
(
'develop-eggs'
)
os
.
mkdir
(
'develop-eggs'
)
# Reinstate the default values of the installer.
set_installer_values
(
installer_values
)
def
buildoutSetUp
(
test
):
def
buildoutSetUp
(
test
):
test
.
globs
[
'__tear_downs'
]
=
__tear_downs
=
[]
test
.
globs
[
'__tear_downs'
]
=
__tear_downs
=
[]
test
.
globs
[
'register_teardown'
]
=
register_teardown
=
__tear_downs
.
append
test
.
globs
[
'register_teardown'
]
=
register_teardown
=
__tear_downs
.
append
prefer_final
=
zc
.
buildout
.
easy_install
.
prefer_final
()
installer_values
=
get_installer_values
()
register_teardown
(
register_teardown
(
lambda
:
zc
.
buildout
.
easy_install
.
prefer_final
(
prefer_final
)
lambda
:
set_installer_values
(
installer_values
)
)
)
here
=
os
.
getcwd
()
here
=
os
.
getcwd
()
...
@@ -367,8 +396,6 @@ def buildoutSetUp(test):
...
@@ -367,8 +396,6 @@ def buildoutSetUp(test):
make_py
=
make_py
make_py
=
make_py
))
))
zc
.
buildout
.
easy_install
.
prefer_final
(
prefer_final
)
def
buildoutTearDown
(
test
):
def
buildoutTearDown
(
test
):
for
f
in
test
.
globs
[
'__tear_downs'
]:
for
f
in
test
.
globs
[
'__tear_downs'
]:
f
()
f
()
...
...
src/zc/buildout/tests.py
View file @
98b7c55c
...
@@ -385,6 +385,64 @@ buildout will tell us who's asking for something that we can't find.
...
@@ -385,6 +385,64 @@ buildout will tell us who's asking for something that we can't find.
Error: Couldn't find a distribution for 'demoneeded'.
Error: Couldn't find a distribution for 'demoneeded'.
"""
"""
def
show_eggs_from_site_packages
():
"""
Sometimes you want to know what eggs are coming from site-packages. This
might be for a diagnostic, or so that you can get a starting value for the
allowed-eggs-from-site-packages option. The -v flag will also include this
information.
Our "py_path" has the "demoneeded," "demo"
packages available. We'll ask for "bigdemo," which will get both of them.
Here's our set up.
>>> py_path, site_packages_path = make_py()
>>> create_sample_sys_install(site_packages_path)
>>> write('buildout.cfg',
... '''
... [buildout]
... parts = eggs
... prefer-final = true
... find-links = %(link_server)s
...
... [primed_python]
... executable = %(py_path)s
...
... [eggs]
... recipe = zc.recipe.egg:eggs
... python = primed_python
... eggs = bigdemo
... ''' % globals())
Now here is the output. The lines that begin with "Egg from site-packages:"
indicate the eggs from site-packages that have been selected. You'll see
we have two: demo 0.3 and demoneeded 1.1.
>>> print system(py_path+" "+buildout+" -v")
Installing 'zc.buildout', 'setuptools'.
We have a develop egg: zc.buildout V
We have the best distribution that satisfies 'setuptools'.
Picked: setuptools = V
Installing 'zc.recipe.egg'.
We have a develop egg: zc.recipe.egg V
Installing eggs.
Installing 'bigdemo'.
We have no distributions for bigdemo that satisfies 'bigdemo'.
Getting distribution for 'bigdemo'.
Got bigdemo 0.1.
Picked: bigdemo = 0.1
Getting required 'demo'
required by bigdemo 0.1.
We have a develop egg: demo V
Egg from site-packages: demo 0.3
Getting required 'demoneeded'
required by demo 0.3.
We have a develop egg: demoneeded V
Egg from site-packages: demoneeded 1.1
<BLANKLINE>
"""
def
test_comparing_saved_options_with_funny_characters
():
def
test_comparing_saved_options_with_funny_characters
():
"""
"""
...
@@ -2003,6 +2061,197 @@ Now the install works correctly, as seen here.
...
@@ -2003,6 +2061,197 @@ Now the install works correctly, as seen here.
"""
"""
def
isolated_include_site_packages
():
"""
This is an isolated test of the include_site_packages functionality, passing
the argument directly to install, overriding a default.
Our "py_path" has the "demoneeded" and "demo" packages available. We'll
simply be asking for "demoneeded" here.
>>> py_path, site_packages_path = make_py()
>>> create_sample_sys_install(site_packages_path)
>>> zc.buildout.easy_install.include_site_packages(False)
True
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None, include_site_packages=True)
>>> [dist.project_name for dist in workingset]
['demoneeded']
That worked fine. Let's try again with site packages not allowed (and
reversing the default).
>>> zc.buildout.easy_install.include_site_packages(True)
False
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None, include_site_packages=False)
Traceback (most recent call last):
...
MissingDistribution: Couldn't find a distribution for 'demoneeded'.
That's a failure, as expected.
Now we explore an important edge case.
Some system Pythons include setuptools (and other Python packages) in their
site-packages (or equivalent) using a .egg-info directory. The pkg_resources
module (from setuptools) considers a package installed using .egg-info to be a
develop egg.
zc.buildout.buildout.Buildout.bootstrap will make setuptools and zc.buildout
available to the buildout via the eggs directory, for normal eggs; or the
develop-eggs directory, for develop-eggs.
If setuptools or zc.buildout is found in site-packages and considered by
pkg_resources to be a develop egg, then the bootstrap code will use a .egg-link
in the local develop-eggs, pointing to site-packages, in its entirety. Because
develop-eggs must always be available for searching for distributions, this
indirectly brings site-packages back into the search path for distributions.
Because of this, we have to take special care that we still exclude
site-packages even in this case. See the comments about site packages in the
Installer._satisfied and Installer._obtain methods for the implementation
(as of this writing).
In this demonstration, we insert a link to the "demoneeded" distribution
in our develop-eggs, which would bring the package back in, except for
the special care we have taken to exclude it.
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> mkdir(example_dest, 'develop-eggs')
>>> write(example_dest, 'develop-eggs', 'demoneeded.egg-link',
... site_packages_path)
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[],
... path=[join(example_dest, 'develop-eggs')],
... executable=py_path,
... index=None, include_site_packages=False)
Traceback (most recent call last):
...
MissingDistribution: Couldn't find a distribution for 'demoneeded'.
The MissingDistribution error shows that buildout correctly excluded the
"site-packages" source even though it was indirectly included in the path
via a .egg-link file.
"""
def
allowed_eggs_from_site_packages
():
"""
Sometimes you need or want to control what eggs from site-packages are used.
The allowed-eggs-from-site-packages option allows you to specify a whitelist of
project names that may be included from site-packages. You can use globs to
specify the value. It defaults to a single value of '*', indicating that any
package may come from site-packages.
This option interacts with include-site-packages in the following ways.
If include-site-packages is true, then allowed-eggs-from-site-packages filters
what eggs from site-packages may be chosen. If allowed-eggs-from-site-packages
is an empty list, then no eggs from site-packages are chosen, but site-packages
will still be included at the end of path lists.
If include-site-packages is false, allowed-eggs-from-site-packages is
irrelevant.
This test shows the interaction with the zc.buildout.easy_install API. Another
test below (allow_site_package_eggs_option) shows using it with a buildout.cfg.
Our "py_path" has the "demoneeded" and "demo" packages available. We'll
simply be asking for "demoneeded" here.
>>> py_path, site_packages_path = make_py()
>>> create_sample_sys_install(site_packages_path)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None,
... allowed_eggs_from_site_packages=['demoneeded', 'other'])
>>> [dist.project_name for dist in workingset]
['demoneeded']
That worked fine. It would work fine for a glob too.
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None,
... allowed_eggs_from_site_packages=['?emon*', 'other'])
>>> [dist.project_name for dist in workingset]
['demoneeded']
But now let's try again with 'demoneeded' not allowed.
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None,
... allowed_eggs_from_site_packages=['demo'])
Traceback (most recent call last):
...
MissingDistribution: Couldn't find a distribution for 'demoneeded'.
Here's the same, but with an empty list.
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None,
... allowed_eggs_from_site_packages=[])
Traceback (most recent call last):
...
MissingDistribution: Couldn't find a distribution for 'demoneeded'.
Of course, this doesn't stop us from getting a package from elsewhere. Here,
we add a link server.
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, executable=py_path,
... links=[link_server], index=link_server+'index/',
... allowed_eggs_from_site_packages=['other'])
>>> [dist.project_name for dist in workingset]
['demoneeded']
>>> [dist.location for dist in workingset]
['/site-packages-example-install/demoneeded-1.1-py2.6.egg']
Finally, here's an example of an interaction: we say that it is OK to
allow the "demoneeded" egg to come from site-packages, but we don't
include-site-packages.
>>> zc.buildout.easy_install.clear_index_cache()
>>> rmdir(example_dest)
>>> example_dest = tmpdir('site-packages-example-install')
>>> workingset = zc.buildout.easy_install.install(
... ['demoneeded'], example_dest, links=[], executable=py_path,
... index=None, include_site_packages=False,
... allowed_eggs_from_site_packages=['demoneeded'])
Traceback (most recent call last):
...
MissingDistribution: Couldn't find a distribution for 'demoneeded'.
"""
if
sys
.
version_info
>
(
2
,
4
):
if
sys
.
version_info
>
(
2
,
4
):
def
test_exit_codes
():
def
test_exit_codes
():
"""
"""
...
@@ -2930,44 +3179,29 @@ def create_sample_namespace_eggs(dest, site_packages_path=None):
...
@@ -2930,44 +3179,29 @@ def create_sample_namespace_eggs(dest, site_packages_path=None):
finally
:
finally
:
shutil
.
rmtree
(
tmp
)
shutil
.
rmtree
(
tmp
)
def
create_sample_eggs
(
test
,
executable
=
sys
.
executable
):
def
_write_eggrecipedemoneeded
(
tmp
,
minor_version
,
suffix
=
''
):
write
=
test
.
globs
[
'write'
]
from
zc.buildout.testing
import
write
dest
=
test
.
globs
[
'sample_eggs'
]
tmp
=
tempfile
.
mkdtemp
()
try
:
write
(
tmp
,
'README.txt'
,
''
)
write
(
tmp
,
'README.txt'
,
''
)
write
(
tmp
,
'eggrecipedemoneeded.py'
,
for
i
in
(
0
,
1
,
2
):
'y=%s
\
n
def f():
\
n
pass'
%
minor_version
)
write
(
tmp
,
'eggrecipedemoneeded.py'
,
'y=%s
\
n
def f():
\
n
pass'
%
i
)
c1
=
i
==
2
and
'c1'
or
''
write
(
write
(
tmp
,
'setup.py'
,
tmp
,
'setup.py'
,
"from setuptools import setup
\
n
"
"from setuptools import setup
\
n
"
"setup(name='demoneeded', py_modules=['eggrecipedemoneeded'],"
"setup(name='demoneeded', py_modules=['eggrecipedemoneeded'],"
" zip_safe=True, version='1.%s%s', author='bob', url='bob', "
" zip_safe=True, version='1.%s%s', author='bob', url='bob', "
"author_email='bob')
\
n
"
"author_email='bob')
\
n
"
%
(
i
,
c1
)
%
(
minor_version
,
suffix
)
)
)
zc
.
buildout
.
testing
.
sdist
(
tmp
,
dest
)
write
(
def
_write_eggrecipedemo
(
tmp
,
minor_version
,
suffix
=
''
):
tmp
,
'setup.py'
,
from
zc.buildout.testing
import
write
"from setuptools import setup
\
n
"
write
(
tmp
,
'README.txt'
,
''
)
"setup(name='other', zip_safe=False, version='1.0', "
"py_modules=['eggrecipedemoneeded'])
\
n
"
)
zc
.
buildout
.
testing
.
bdist_egg
(
tmp
,
executable
,
dest
)
os
.
remove
(
os
.
path
.
join
(
tmp
,
'eggrecipedemoneeded.py'
))
for
i
in
(
1
,
2
,
3
,
4
):
write
(
write
(
tmp
,
'eggrecipedemo.py'
,
tmp
,
'eggrecipedemo.py'
,
'import eggrecipedemoneeded
\
n
'
'import eggrecipedemoneeded
\
n
'
'x=%s
\
n
'
'x=%s
\
n
'
'def main(): print x, eggrecipedemoneeded.y
\
n
'
'def main(): print x, eggrecipedemoneeded.y
\
n
'
%
i
)
%
minor_version
)
c1
=
i
==
4
and
'c1'
or
''
write
(
write
(
tmp
,
'setup.py'
,
tmp
,
'setup.py'
,
"from setuptools import setup
\
n
"
"from setuptools import setup
\
n
"
...
@@ -2975,10 +3209,46 @@ def create_sample_eggs(test, executable=sys.executable):
...
@@ -2975,10 +3209,46 @@ def create_sample_eggs(test, executable=sys.executable):
" install_requires = 'demoneeded',"
" install_requires = 'demoneeded',"
" entry_points={'console_scripts': "
" entry_points={'console_scripts': "
"['demo = eggrecipedemo:main']},"
"['demo = eggrecipedemo:main']},"
" zip_safe=True, version='0.%s%s')
\
n
"
%
(
i
,
c1
)
" zip_safe=True, version='0.%s%s')
\
n
"
%
(
minor_version
,
suffix
)
)
def
create_sample_sys_install
(
site_packages_path
):
for
creator
,
minor_version
in
(
(
_write_eggrecipedemoneeded
,
1
),
(
_write_eggrecipedemo
,
3
)):
# Write the files and install in site_packages_path.
tmp
=
tempfile
.
mkdtemp
()
try
:
creator
(
tmp
,
minor_version
)
zc
.
buildout
.
testing
.
sys_install
(
tmp
,
site_packages_path
)
finally
:
shutil
.
rmtree
(
tmp
)
def
create_sample_eggs
(
test
,
executable
=
sys
.
executable
):
from
zc.buildout.testing
import
write
dest
=
test
.
globs
[
'sample_eggs'
]
tmp
=
tempfile
.
mkdtemp
()
try
:
for
i
in
(
0
,
1
,
2
):
suffix
=
i
==
2
and
'c1'
or
''
_write_eggrecipedemoneeded
(
tmp
,
i
,
suffix
)
zc
.
buildout
.
testing
.
sdist
(
tmp
,
dest
)
write
(
tmp
,
'setup.py'
,
"from setuptools import setup
\
n
"
"setup(name='other', zip_safe=False, version='1.0', "
"py_modules=['eggrecipedemoneeded'])
\
n
"
)
)
zc
.
buildout
.
testing
.
bdist_egg
(
tmp
,
executable
,
dest
)
zc
.
buildout
.
testing
.
bdist_egg
(
tmp
,
executable
,
dest
)
os
.
remove
(
os
.
path
.
join
(
tmp
,
'eggrecipedemoneeded.py'
))
for
i
in
(
1
,
2
,
3
,
4
):
suffix
=
i
==
4
and
'c1'
or
''
_write_eggrecipedemo
(
tmp
,
i
,
suffix
)
zc
.
buildout
.
testing
.
bdist_egg
(
tmp
,
executable
,
dest
)
write
(
tmp
,
'eggrecipebigdemo.py'
,
'import eggrecipedemo'
)
write
(
tmp
,
'eggrecipebigdemo.py'
,
'import eggrecipedemo'
)
write
(
write
(
tmp
,
'setup.py'
,
tmp
,
'setup.py'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment