We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[scm] Expressions not expanded when "auto" mode
Conan Version: 1.5.2

I just played around some more with the scm feature and found a (for me as user) surprising side effect. As soon as either the "url" or the "revision" field is in "auto" mode, not only the respective field is rewritten but also other fields in the scm structure are rewritten to their current value. For standard strings it does not matter, however, as soon as a function is called to provide the value, this becomes visible.

In more detail, exporting a recipe with the following scm structure yields the expected result and exactly the same structure is found in the conan cache.

    scm = {
        "type": "git",
        "subfolder": "repo",
        "username": tools.get_env("MY_MAGIC_USERNAME", "error"),
        "password": tools.get_env("MY_MAGIC_PASSWORD", "error"),
        "url": "http://example.com",
        "revision": "1234"
    }

However, as soon as I set, for example, the revision to "auto", all fields are evaluated and exported as their actual value at export time. This can then look something like the following:

    scm = {"password": "error",
           "revision": "1e25d838c88afaa6f190d12ea9dff9de36450f0c",
           "subfolder": "repo",
           "type": "git",
           "url": "http://example.com",
           "username": "error"}

What bothers me is that the behaviour feels a little bit inconsistent which makes me wonder if this is a bug or a feature?

On the one hand, I dislike that it happens as side effect of capturing the url/revision. On the other hand, I like the possibility to capture values at export time into the recipe. Having a generic measure like this would, for example, permit to easily implement #3150 inside the recipe without further conan support. I imagine something like an `expand` function, that can be used to initialize arbitrary properties, and is replaced with its result when the recipe is exported. Implementing #3150 would then look something like the following where `get_revision_from_branch` is defined as free function in the recipe.

    scm = {
        "type": "git",
        "url": "http://example.com",
        "revision": expand(get_revision_from_branch("feature/foo"))
    }

Similarly, #3160 could be extended to the following in order to remove the `get_version` call when consuming the recipe.

    version = expand(get_version())

What do you guys think about the idea? Is it worthwhile pursuing and can it be implemented at all?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `conans/client/cmd/export.py`
Content:
```
1 import ast
2 import os
3 import shutil
4 
5 import six
6 
7 from conans.client.cmd.export_linter import conan_linter
8 from conans.client.file_copier import FileCopier
9 from conans.client.output import ScopedOutput
10 from conans.errors import ConanException
11 from conans.model.manifest import FileTreeManifest
12 from conans.model.scm import SCM, get_scm_data
13 from conans.paths import CONANFILE, CONAN_MANIFEST
14 from conans.search.search import search_recipes
15 from conans.util.files import is_dirty, load, mkdir, rmdir, save, set_dirty
16 from conans.util.log import logger
17 
18 
19 def export_alias(reference, target_reference, client_cache):
20     if reference.name != target_reference.name:
21         raise ConanException("An alias can only be defined to a package with the same name")
22     conanfile = """
23 from conans import ConanFile
24 
25 class AliasConanfile(ConanFile):
26     alias = "%s"
27 """ % str(target_reference)
28 
29     export_path = client_cache.export(reference)
30     mkdir(export_path)
31     save(os.path.join(export_path, CONANFILE), conanfile)
32     mkdir(client_cache.export_sources(reference))
33     digest = FileTreeManifest.create(export_path)
34     digest.save(export_path)
35 
36 
37 def cmd_export(conanfile_path, conanfile, reference, keep_source, output, client_cache,
38                hook_manager):
39     """ Export the recipe
40     param conanfile_path: the original source directory of the user containing a
41                        conanfile.py
42     """
43     hook_manager.execute("pre_export", conanfile=conanfile, conanfile_path=conanfile_path,
44                          reference=reference)
45     logger.debug("EXPORT: %s" % conanfile_path)
46     output.highlight("Exporting package recipe")
47 
48     conan_linter(conanfile_path, output)
49     # Maybe a platform check could be added, but depends on disk partition
50     conan_ref_str = str(reference)
51     refs = search_recipes(client_cache, conan_ref_str, ignorecase=True)
52     if refs and reference not in refs:
53         raise ConanException("Cannot export package with same name but different case\n"
54                              "You exported '%s' but already existing '%s'"
55                              % (conan_ref_str, " ".join(str(s) for s in refs)))
56 
57     with client_cache.conanfile_write_lock(reference):
58         _export_conanfile(conanfile_path, conanfile.output, client_cache, conanfile, reference,
59                           keep_source)
60     conanfile_cache_path = client_cache.conanfile(reference)
61     hook_manager.execute("post_export", conanfile=conanfile, conanfile_path=conanfile_cache_path,
62                            reference=reference)
63 
64 
65 def _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, output, paths, conan_ref):
66 
67     scm_src_file = paths.scm_folder(conan_ref)
68     if os.path.exists(scm_src_file):
69         os.unlink(scm_src_file)
70 
71     scm_data = get_scm_data(conanfile)
72     captured_revision = scm_data.capture_revision if scm_data else False
73 
74     if not scm_data or not (scm_data.capture_origin or scm_data.capture_revision):
75         return None, captured_revision
76 
77     scm = SCM(scm_data, conanfile_dir, output)
78 
79     if scm_data.url == "auto":
80         origin = scm.get_qualified_remote_url()
81         if not origin:
82             raise ConanException("Repo origin cannot be deduced by 'auto'")
83         if scm.is_local_repository():
84             output.warn("Repo origin looks like a local path: %s" % origin)
85         output.success("Repo origin deduced by 'auto': %s" % origin)
86         scm_data.url = origin
87     if scm_data.revision == "auto":
88         if not scm.is_pristine():
89             output.warn("Repo status is not pristine: there might be modified files")
90         scm_data.revision = scm.get_revision()
91         output.success("Revision deduced by 'auto': %s" % scm_data.revision)
92 
93     # Generate the scm_folder.txt file pointing to the src_path
94     src_path = scm.get_repo_root()
95     save(scm_src_file, src_path.replace("\\", "/"))
96     _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"),
97                                    scm_data)
98 
99     return scm_data, captured_revision
100 
101 
102 def _replace_scm_data_in_conanfile(conanfile_path, scm_data):
103     # Parsing and replacing the SCM field
104     content = load(conanfile_path)
105     headers = []
106 
107     if six.PY2:
108         # Workaround for https://bugs.python.org/issue22221
109         lines_without_headers = []
110         lines = content.splitlines(True)
111         for line in lines:
112             if not lines_without_headers and line.startswith("#"):
113                 headers.append(line)
114             else:
115                 lines_without_headers.append(line)
116         content = ''.join(lines_without_headers)
117 
118     lines = content.splitlines(True)
119     tree = ast.parse(content)
120     to_replace = []
121     for i_body, item in enumerate(tree.body):
122         if isinstance(item, ast.ClassDef):
123             statements = item.body
124             for i, stmt in enumerate(item.body):
125                 if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1:
126                     if isinstance(stmt.targets[0], ast.Name) and stmt.targets[0].id == "scm":
127                         try:
128                             if i + 1 == len(statements):  # Last statement in my ClassDef
129                                 if i_body + 1 == len(tree.body):  # Last statement over all
130                                     next_line = len(lines)
131                                 else:
132                                     next_line = tree.body[i_body+1].lineno - 1
133                             else:
134                                 next_line = statements[i+1].lineno - 1
135                         except IndexError:
136                             next_line = stmt.lineno
137                         replace = [line for line in lines[(stmt.lineno-1):next_line]
138                                    if line.strip()]
139                         to_replace.append("".join(replace).lstrip())
140                         break
141     if len(to_replace) != 1:
142         raise ConanException("The conanfile.py defines more than one class level 'scm' attribute")
143 
144     new_text = "scm = " + ",\n          ".join(str(scm_data).split(",")) + "\n"
145     content = content.replace(to_replace[0], new_text)
146     content = content if not headers else ''.join(headers) + content
147     save(conanfile_path, content)
148 
149 
150 def _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):
151 
152     exports_folder = client_cache.export(conan_ref)
153     exports_source_folder = client_cache.export_sources(conan_ref, conanfile.short_paths)
154 
155     previous_digest = _init_export_folder(exports_folder, exports_source_folder)
156     origin_folder = os.path.dirname(conanfile_path)
157     export_recipe(conanfile, origin_folder, exports_folder, output)
158     export_source(conanfile, origin_folder, exports_source_folder, output)
159     shutil.copy2(conanfile_path, os.path.join(exports_folder, CONANFILE))
160 
161     scm_data, captured_revision = _capture_export_scm_data(conanfile,
162                                                            os.path.dirname(conanfile_path),
163                                                            exports_folder,
164                                                            output, client_cache, conan_ref)
165 
166     digest = FileTreeManifest.create(exports_folder, exports_source_folder)
167 
168     if previous_digest and previous_digest == digest:
169         output.info("The stored package has not changed")
170         modified_recipe = False
171         digest = previous_digest  # Use the old one, keep old timestamp
172     else:
173         output.success('A new %s version was exported' % CONANFILE)
174         output.info('Folder: %s' % exports_folder)
175         modified_recipe = True
176 
177     digest.save(exports_folder)
178 
179     revision = scm_data.revision if scm_data and captured_revision else digest.summary_hash
180     with client_cache.update_metadata(conan_ref) as metadata:
181         # Note that there is no time set, the time will come from the remote
182         metadata.recipe.revision = revision
183 
184     # FIXME: Conan 2.0 Clear the registry entry if the recipe has changed
185     source = client_cache.source(conan_ref, conanfile.short_paths)
186     remove = False
187     if is_dirty(source):
188         output.info("Source folder is corrupted, forcing removal")
189         remove = True
190     elif modified_recipe and not keep_source and os.path.exists(source):
191         output.info("Package recipe modified in export, forcing source folder removal")
192         output.info("Use the --keep-source, -k option to skip it")
193         remove = True
194     if remove:
195         output.info("Removing 'source' folder, this can take a while for big packages")
196         try:
197             # remove only the internal
198             rmdir(source)
199         except BaseException as e:
200             output.error("Unable to delete source folder. "
201                          "Will be marked as corrupted for deletion")
202             output.warn(str(e))
203             set_dirty(source)
204 
205 
206 def _init_export_folder(destination_folder, destination_src_folder):
207     previous_digest = None
208     try:
209         if os.path.exists(destination_folder):
210             if os.path.exists(os.path.join(destination_folder, CONAN_MANIFEST)):
211                 previous_digest = FileTreeManifest.load(destination_folder)
212             # Maybe here we want to invalidate cache
213             rmdir(destination_folder)
214         os.makedirs(destination_folder)
215     except Exception as e:
216         raise ConanException("Unable to create folder %s\n%s" % (destination_folder, str(e)))
217     try:
218         if os.path.exists(destination_src_folder):
219             rmdir(destination_src_folder)
220         os.makedirs(destination_src_folder)
221     except Exception as e:
222         raise ConanException("Unable to create folder %s\n%s" % (destination_src_folder, str(e)))
223     return previous_digest
224 
225 
226 def _classify_patterns(patterns):
227     patterns = patterns or []
228     included, excluded = [], []
229     for p in patterns:
230         if p.startswith("!"):
231             excluded.append(p[1:])
232         else:
233             included.append(p)
234     return included, excluded
235 
236 
237 def export_source(conanfile, origin_folder, destination_source_folder, output):
238     if isinstance(conanfile.exports_sources, str):
239         conanfile.exports_sources = (conanfile.exports_sources, )
240 
241     included_sources, excluded_sources = _classify_patterns(conanfile.exports_sources)
242     copier = FileCopier(origin_folder, destination_source_folder)
243     for pattern in included_sources:
244         copier(pattern, links=True, excludes=excluded_sources)
245     package_output = ScopedOutput("%s exports_sources" % output.scope, output)
246     copier.report(package_output)
247 
248 
249 def export_recipe(conanfile, origin_folder, destination_folder, output):
250     if isinstance(conanfile.exports, str):
251         conanfile.exports = (conanfile.exports, )
252 
253     included_exports, excluded_exports = _classify_patterns(conanfile.exports)
254 
255     try:
256         os.unlink(os.path.join(origin_folder, CONANFILE + 'c'))
257     except OSError:
258         pass
259 
260     copier = FileCopier(origin_folder, destination_folder)
261     for pattern in included_exports:
262         copier(pattern, links=True, excludes=excluded_exports)
263     package_output = ScopedOutput("%s exports" % output.scope, output)
264     copier.report(package_output)
265
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py
--- a/conans/client/cmd/export.py
+++ b/conans/client/cmd/export.py
@@ -69,12 +69,17 @@
         os.unlink(scm_src_file)
 
     scm_data = get_scm_data(conanfile)
-    captured_revision = scm_data.capture_revision if scm_data else False
+    if not scm_data:
+        return None, False
 
-    if not scm_data or not (scm_data.capture_origin or scm_data.capture_revision):
-        return None, captured_revision
+    # Resolve SCMData in the user workspace (someone may want to access CVS or import some py)
+    captured_revision = scm_data.capture_revision
 
     scm = SCM(scm_data, conanfile_dir, output)
+    if scm_data.capture_origin or scm_data.capture_revision:
+        # Generate the scm_folder.txt file pointing to the src_path
+        src_path = scm.get_repo_root()
+        save(scm_src_file, src_path.replace("\\", "/"))
 
     if scm_data.url == "auto":
         origin = scm.get_qualified_remote_url()
@@ -84,17 +89,14 @@
             output.warn("Repo origin looks like a local path: %s" % origin)
         output.success("Repo origin deduced by 'auto': %s" % origin)
         scm_data.url = origin
+
     if scm_data.revision == "auto":
         if not scm.is_pristine():
             output.warn("Repo status is not pristine: there might be modified files")
         scm_data.revision = scm.get_revision()
         output.success("Revision deduced by 'auto': %s" % scm_data.revision)
 
-    # Generate the scm_folder.txt file pointing to the src_path
-    src_path = scm.get_repo_root()
-    save(scm_src_file, src_path.replace("\\", "/"))
-    _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"),
-                                   scm_data)
+    _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"), scm_data)
 
     return scm_data, captured_revision
 
@@ -146,7 +148,6 @@
     content = content if not headers else ''.join(headers) + content
     save(conanfile_path, content)
 
-
 def _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):
 
     exports_folder = client_cache.export(conan_ref)

{"golden_diff": "diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py\n--- a/conans/client/cmd/export.py\n+++ b/conans/client/cmd/export.py\n@@ -69,12 +69,17 @@\n         os.unlink(scm_src_file)\n \n     scm_data = get_scm_data(conanfile)\n-    captured_revision = scm_data.capture_revision if scm_data else False\n+    if not scm_data:\n+        return None, False\n \n-    if not scm_data or not (scm_data.capture_origin or scm_data.capture_revision):\n-        return None, captured_revision\n+    # Resolve SCMData in the user workspace (someone may want to access CVS or import some py)\n+    captured_revision = scm_data.capture_revision\n \n     scm = SCM(scm_data, conanfile_dir, output)\n+    if scm_data.capture_origin or scm_data.capture_revision:\n+        # Generate the scm_folder.txt file pointing to the src_path\n+        src_path = scm.get_repo_root()\n+        save(scm_src_file, src_path.replace(\"\\\\\", \"/\"))\n \n     if scm_data.url == \"auto\":\n         origin = scm.get_qualified_remote_url()\n@@ -84,17 +89,14 @@\n             output.warn(\"Repo origin looks like a local path: %s\" % origin)\n         output.success(\"Repo origin deduced by 'auto': %s\" % origin)\n         scm_data.url = origin\n+\n     if scm_data.revision == \"auto\":\n         if not scm.is_pristine():\n             output.warn(\"Repo status is not pristine: there might be modified files\")\n         scm_data.revision = scm.get_revision()\n         output.success(\"Revision deduced by 'auto': %s\" % scm_data.revision)\n \n-    # Generate the scm_folder.txt file pointing to the src_path\n-    src_path = scm.get_repo_root()\n-    save(scm_src_file, src_path.replace(\"\\\\\", \"/\"))\n-    _replace_scm_data_in_conanfile(os.path.join(destination_folder, \"conanfile.py\"),\n-                                   scm_data)\n+    _replace_scm_data_in_conanfile(os.path.join(destination_folder, \"conanfile.py\"), scm_data)\n \n     return scm_data, captured_revision\n \n@@ -146,7 +148,6 @@\n     content = content if not headers else ''.join(headers) + content\n     save(conanfile_path, content)\n \n-\n def _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):\n \n     exports_folder = client_cache.export(conan_ref)\n", "issue": "[scm] Expressions not expanded when \"auto\" mode\nConan Version: 1.5.2\r\n\r\nI just played around some more with the scm feature and found a (for me as user) surprising side effect. As soon as either the \"url\" or the \"revision\" field is in \"auto\" mode, not only the respective field is rewritten but also other fields in the scm structure are rewritten to their current value. For standard strings it does not matter, however, as soon as a function is called to provide the value, this becomes visible.\r\n\r\nIn more detail, exporting a recipe with the following scm structure yields the expected result and exactly the same structure is found in the conan cache.\r\n\r\n    scm = {\r\n        \"type\": \"git\",\r\n        \"subfolder\": \"repo\",\r\n        \"username\": tools.get_env(\"MY_MAGIC_USERNAME\", \"error\"),\r\n        \"password\": tools.get_env(\"MY_MAGIC_PASSWORD\", \"error\"),\r\n        \"url\": \"http://example.com\",\r\n        \"revision\": \"1234\"\r\n    }\r\n\r\nHowever, as soon as I set, for example, the revision to \"auto\", all fields are evaluated and exported as their actual value at export time. This can then look something like the following:\r\n\r\n    scm = {\"password\": \"error\",\r\n           \"revision\": \"1e25d838c88afaa6f190d12ea9dff9de36450f0c\",\r\n           \"subfolder\": \"repo\",\r\n           \"type\": \"git\",\r\n           \"url\": \"http://example.com\",\r\n           \"username\": \"error\"}\r\n\r\nWhat bothers me is that the behaviour feels a little bit inconsistent which makes me wonder if this is a bug or a feature?\r\n\r\nOn the one hand, I dislike that it happens as side effect of capturing the url/revision. On the other hand, I like the possibility to capture values at export time into the recipe. Having a generic measure like this would, for example, permit to easily implement #3150 inside the recipe without further conan support. I imagine something like an `expand` function, that can be used to initialize arbitrary properties, and is replaced with its result when the recipe is exported. Implementing #3150 would then look something like the following where `get_revision_from_branch` is defined as free function in the recipe.\r\n\r\n    scm = {\r\n        \"type\": \"git\",\r\n        \"url\": \"http://example.com\",\r\n        \"revision\": expand(get_revision_from_branch(\"feature/foo\"))\r\n    }\r\n\r\nSimilarly, #3160 could be extended to the following in order to remove the `get_version` call when consuming the recipe.\r\n\r\n    version = expand(get_version())\r\n\r\nWhat do you guys think about the idea? Is it worthwhile pursuing and can it be implemented at all?\n", "before_files": [{"content": "import ast\nimport os\nimport shutil\n\nimport six\n\nfrom conans.client.cmd.export_linter import conan_linter\nfrom conans.client.file_copier import FileCopier\nfrom conans.client.output import ScopedOutput\nfrom conans.errors import ConanException\nfrom conans.model.manifest import FileTreeManifest\nfrom conans.model.scm import SCM, get_scm_data\nfrom conans.paths import CONANFILE, CONAN_MANIFEST\nfrom conans.search.search import search_recipes\nfrom conans.util.files import is_dirty, load, mkdir, rmdir, save, set_dirty\nfrom conans.util.log import logger\n\n\ndef export_alias(reference, target_reference, client_cache):\n    if reference.name != target_reference.name:\n        raise ConanException(\"An alias can only be defined to a package with the same name\")\n    conanfile = \"\"\"\nfrom conans import ConanFile\n\nclass AliasConanfile(ConanFile):\n    alias = \"%s\"\n\"\"\" % str(target_reference)\n\n    export_path = client_cache.export(reference)\n    mkdir(export_path)\n    save(os.path.join(export_path, CONANFILE), conanfile)\n    mkdir(client_cache.export_sources(reference))\n    digest = FileTreeManifest.create(export_path)\n    digest.save(export_path)\n\n\ndef cmd_export(conanfile_path, conanfile, reference, keep_source, output, client_cache,\n               hook_manager):\n    \"\"\" Export the recipe\n    param conanfile_path: the original source directory of the user containing a\n                       conanfile.py\n    \"\"\"\n    hook_manager.execute(\"pre_export\", conanfile=conanfile, conanfile_path=conanfile_path,\n                         reference=reference)\n    logger.debug(\"EXPORT: %s\" % conanfile_path)\n    output.highlight(\"Exporting package recipe\")\n\n    conan_linter(conanfile_path, output)\n    # Maybe a platform check could be added, but depends on disk partition\n    conan_ref_str = str(reference)\n    refs = search_recipes(client_cache, conan_ref_str, ignorecase=True)\n    if refs and reference not in refs:\n        raise ConanException(\"Cannot export package with same name but different case\\n\"\n                             \"You exported '%s' but already existing '%s'\"\n                             % (conan_ref_str, \" \".join(str(s) for s in refs)))\n\n    with client_cache.conanfile_write_lock(reference):\n        _export_conanfile(conanfile_path, conanfile.output, client_cache, conanfile, reference,\n                          keep_source)\n    conanfile_cache_path = client_cache.conanfile(reference)\n    hook_manager.execute(\"post_export\", conanfile=conanfile, conanfile_path=conanfile_cache_path,\n                           reference=reference)\n\n\ndef _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, output, paths, conan_ref):\n\n    scm_src_file = paths.scm_folder(conan_ref)\n    if os.path.exists(scm_src_file):\n        os.unlink(scm_src_file)\n\n    scm_data = get_scm_data(conanfile)\n    captured_revision = scm_data.capture_revision if scm_data else False\n\n    if not scm_data or not (scm_data.capture_origin or scm_data.capture_revision):\n        return None, captured_revision\n\n    scm = SCM(scm_data, conanfile_dir, output)\n\n    if scm_data.url == \"auto\":\n        origin = scm.get_qualified_remote_url()\n        if not origin:\n            raise ConanException(\"Repo origin cannot be deduced by 'auto'\")\n        if scm.is_local_repository():\n            output.warn(\"Repo origin looks like a local path: %s\" % origin)\n        output.success(\"Repo origin deduced by 'auto': %s\" % origin)\n        scm_data.url = origin\n    if scm_data.revision == \"auto\":\n        if not scm.is_pristine():\n            output.warn(\"Repo status is not pristine: there might be modified files\")\n        scm_data.revision = scm.get_revision()\n        output.success(\"Revision deduced by 'auto': %s\" % scm_data.revision)\n\n    # Generate the scm_folder.txt file pointing to the src_path\n    src_path = scm.get_repo_root()\n    save(scm_src_file, src_path.replace(\"\\\\\", \"/\"))\n    _replace_scm_data_in_conanfile(os.path.join(destination_folder, \"conanfile.py\"),\n                                   scm_data)\n\n    return scm_data, captured_revision\n\n\ndef _replace_scm_data_in_conanfile(conanfile_path, scm_data):\n    # Parsing and replacing the SCM field\n    content = load(conanfile_path)\n    headers = []\n\n    if six.PY2:\n        # Workaround for https://bugs.python.org/issue22221\n        lines_without_headers = []\n        lines = content.splitlines(True)\n        for line in lines:\n            if not lines_without_headers and line.startswith(\"#\"):\n                headers.append(line)\n            else:\n                lines_without_headers.append(line)\n        content = ''.join(lines_without_headers)\n\n    lines = content.splitlines(True)\n    tree = ast.parse(content)\n    to_replace = []\n    for i_body, item in enumerate(tree.body):\n        if isinstance(item, ast.ClassDef):\n            statements = item.body\n            for i, stmt in enumerate(item.body):\n                if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1:\n                    if isinstance(stmt.targets[0], ast.Name) and stmt.targets[0].id == \"scm\":\n                        try:\n                            if i + 1 == len(statements):  # Last statement in my ClassDef\n                                if i_body + 1 == len(tree.body):  # Last statement over all\n                                    next_line = len(lines)\n                                else:\n                                    next_line = tree.body[i_body+1].lineno - 1\n                            else:\n                                next_line = statements[i+1].lineno - 1\n                        except IndexError:\n                            next_line = stmt.lineno\n                        replace = [line for line in lines[(stmt.lineno-1):next_line]\n                                   if line.strip()]\n                        to_replace.append(\"\".join(replace).lstrip())\n                        break\n    if len(to_replace) != 1:\n        raise ConanException(\"The conanfile.py defines more than one class level 'scm' attribute\")\n\n    new_text = \"scm = \" + \",\\n          \".join(str(scm_data).split(\",\")) + \"\\n\"\n    content = content.replace(to_replace[0], new_text)\n    content = content if not headers else ''.join(headers) + content\n    save(conanfile_path, content)\n\n\ndef _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):\n\n    exports_folder = client_cache.export(conan_ref)\n    exports_source_folder = client_cache.export_sources(conan_ref, conanfile.short_paths)\n\n    previous_digest = _init_export_folder(exports_folder, exports_source_folder)\n    origin_folder = os.path.dirname(conanfile_path)\n    export_recipe(conanfile, origin_folder, exports_folder, output)\n    export_source(conanfile, origin_folder, exports_source_folder, output)\n    shutil.copy2(conanfile_path, os.path.join(exports_folder, CONANFILE))\n\n    scm_data, captured_revision = _capture_export_scm_data(conanfile,\n                                                           os.path.dirname(conanfile_path),\n                                                           exports_folder,\n                                                           output, client_cache, conan_ref)\n\n    digest = FileTreeManifest.create(exports_folder, exports_source_folder)\n\n    if previous_digest and previous_digest == digest:\n        output.info(\"The stored package has not changed\")\n        modified_recipe = False\n        digest = previous_digest  # Use the old one, keep old timestamp\n    else:\n        output.success('A new %s version was exported' % CONANFILE)\n        output.info('Folder: %s' % exports_folder)\n        modified_recipe = True\n\n    digest.save(exports_folder)\n\n    revision = scm_data.revision if scm_data and captured_revision else digest.summary_hash\n    with client_cache.update_metadata(conan_ref) as metadata:\n        # Note that there is no time set, the time will come from the remote\n        metadata.recipe.revision = revision\n\n    # FIXME: Conan 2.0 Clear the registry entry if the recipe has changed\n    source = client_cache.source(conan_ref, conanfile.short_paths)\n    remove = False\n    if is_dirty(source):\n        output.info(\"Source folder is corrupted, forcing removal\")\n        remove = True\n    elif modified_recipe and not keep_source and os.path.exists(source):\n        output.info(\"Package recipe modified in export, forcing source folder removal\")\n        output.info(\"Use the --keep-source, -k option to skip it\")\n        remove = True\n    if remove:\n        output.info(\"Removing 'source' folder, this can take a while for big packages\")\n        try:\n            # remove only the internal\n            rmdir(source)\n        except BaseException as e:\n            output.error(\"Unable to delete source folder. \"\n                         \"Will be marked as corrupted for deletion\")\n            output.warn(str(e))\n            set_dirty(source)\n\n\ndef _init_export_folder(destination_folder, destination_src_folder):\n    previous_digest = None\n    try:\n        if os.path.exists(destination_folder):\n            if os.path.exists(os.path.join(destination_folder, CONAN_MANIFEST)):\n                previous_digest = FileTreeManifest.load(destination_folder)\n            # Maybe here we want to invalidate cache\n            rmdir(destination_folder)\n        os.makedirs(destination_folder)\n    except Exception as e:\n        raise ConanException(\"Unable to create folder %s\\n%s\" % (destination_folder, str(e)))\n    try:\n        if os.path.exists(destination_src_folder):\n            rmdir(destination_src_folder)\n        os.makedirs(destination_src_folder)\n    except Exception as e:\n        raise ConanException(\"Unable to create folder %s\\n%s\" % (destination_src_folder, str(e)))\n    return previous_digest\n\n\ndef _classify_patterns(patterns):\n    patterns = patterns or []\n    included, excluded = [], []\n    for p in patterns:\n        if p.startswith(\"!\"):\n            excluded.append(p[1:])\n        else:\n            included.append(p)\n    return included, excluded\n\n\ndef export_source(conanfile, origin_folder, destination_source_folder, output):\n    if isinstance(conanfile.exports_sources, str):\n        conanfile.exports_sources = (conanfile.exports_sources, )\n\n    included_sources, excluded_sources = _classify_patterns(conanfile.exports_sources)\n    copier = FileCopier(origin_folder, destination_source_folder)\n    for pattern in included_sources:\n        copier(pattern, links=True, excludes=excluded_sources)\n    package_output = ScopedOutput(\"%s exports_sources\" % output.scope, output)\n    copier.report(package_output)\n\n\ndef export_recipe(conanfile, origin_folder, destination_folder, output):\n    if isinstance(conanfile.exports, str):\n        conanfile.exports = (conanfile.exports, )\n\n    included_exports, excluded_exports = _classify_patterns(conanfile.exports)\n\n    try:\n        os.unlink(os.path.join(origin_folder, CONANFILE + 'c'))\n    except OSError:\n        pass\n\n    copier = FileCopier(origin_folder, destination_folder)\n    for pattern in included_exports:\n        copier(pattern, links=True, excludes=excluded_exports)\n    package_output = ScopedOutput(\"%s exports\" % output.scope, output)\n    copier.report(package_output)\n", "path": "conans/client/cmd/export.py"}], "after_files": [{"content": "import ast\nimport os\nimport shutil\n\nimport six\n\nfrom conans.client.cmd.export_linter import conan_linter\nfrom conans.client.file_copier import FileCopier\nfrom conans.client.output import ScopedOutput\nfrom conans.errors import ConanException\nfrom conans.model.manifest import FileTreeManifest\nfrom conans.model.scm import SCM, get_scm_data\nfrom conans.paths import CONANFILE, CONAN_MANIFEST\nfrom conans.search.search import search_recipes\nfrom conans.util.files import is_dirty, load, mkdir, rmdir, save, set_dirty\nfrom conans.util.log import logger\n\n\ndef export_alias(reference, target_reference, client_cache):\n    if reference.name != target_reference.name:\n        raise ConanException(\"An alias can only be defined to a package with the same name\")\n    conanfile = \"\"\"\nfrom conans import ConanFile\n\nclass AliasConanfile(ConanFile):\n    alias = \"%s\"\n\"\"\" % str(target_reference)\n\n    export_path = client_cache.export(reference)\n    mkdir(export_path)\n    save(os.path.join(export_path, CONANFILE), conanfile)\n    mkdir(client_cache.export_sources(reference))\n    digest = FileTreeManifest.create(export_path)\n    digest.save(export_path)\n\n\ndef cmd_export(conanfile_path, conanfile, reference, keep_source, output, client_cache,\n               hook_manager):\n    \"\"\" Export the recipe\n    param conanfile_path: the original source directory of the user containing a\n                       conanfile.py\n    \"\"\"\n    hook_manager.execute(\"pre_export\", conanfile=conanfile, conanfile_path=conanfile_path,\n                         reference=reference)\n    logger.debug(\"EXPORT: %s\" % conanfile_path)\n    output.highlight(\"Exporting package recipe\")\n\n    conan_linter(conanfile_path, output)\n    # Maybe a platform check could be added, but depends on disk partition\n    conan_ref_str = str(reference)\n    refs = search_recipes(client_cache, conan_ref_str, ignorecase=True)\n    if refs and reference not in refs:\n        raise ConanException(\"Cannot export package with same name but different case\\n\"\n                             \"You exported '%s' but already existing '%s'\"\n                             % (conan_ref_str, \" \".join(str(s) for s in refs)))\n\n    with client_cache.conanfile_write_lock(reference):\n        _export_conanfile(conanfile_path, conanfile.output, client_cache, conanfile, reference,\n                          keep_source)\n    conanfile_cache_path = client_cache.conanfile(reference)\n    hook_manager.execute(\"post_export\", conanfile=conanfile, conanfile_path=conanfile_cache_path,\n                           reference=reference)\n\n\ndef _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, output, paths, conan_ref):\n\n    scm_src_file = paths.scm_folder(conan_ref)\n    if os.path.exists(scm_src_file):\n        os.unlink(scm_src_file)\n\n    scm_data = get_scm_data(conanfile)\n    if not scm_data:\n        return None, False\n\n    # Resolve SCMData in the user workspace (someone may want to access CVS or import some py)\n    captured_revision = scm_data.capture_revision\n\n    scm = SCM(scm_data, conanfile_dir, output)\n    if scm_data.capture_origin or scm_data.capture_revision:\n        # Generate the scm_folder.txt file pointing to the src_path\n        src_path = scm.get_repo_root()\n        save(scm_src_file, src_path.replace(\"\\\\\", \"/\"))\n\n    if scm_data.url == \"auto\":\n        origin = scm.get_qualified_remote_url()\n        if not origin:\n            raise ConanException(\"Repo origin cannot be deduced by 'auto'\")\n        if scm.is_local_repository():\n            output.warn(\"Repo origin looks like a local path: %s\" % origin)\n        output.success(\"Repo origin deduced by 'auto': %s\" % origin)\n        scm_data.url = origin\n\n    if scm_data.revision == \"auto\":\n        if not scm.is_pristine():\n            output.warn(\"Repo status is not pristine: there might be modified files\")\n        scm_data.revision = scm.get_revision()\n        output.success(\"Revision deduced by 'auto': %s\" % scm_data.revision)\n\n    _replace_scm_data_in_conanfile(os.path.join(destination_folder, \"conanfile.py\"), scm_data)\n\n    return scm_data, captured_revision\n\n\ndef _replace_scm_data_in_conanfile(conanfile_path, scm_data):\n    # Parsing and replacing the SCM field\n    content = load(conanfile_path)\n    headers = []\n\n    if six.PY2:\n        # Workaround for https://bugs.python.org/issue22221\n        lines_without_headers = []\n        lines = content.splitlines(True)\n        for line in lines:\n            if not lines_without_headers and line.startswith(\"#\"):\n                headers.append(line)\n            else:\n                lines_without_headers.append(line)\n        content = ''.join(lines_without_headers)\n\n    lines = content.splitlines(True)\n    tree = ast.parse(content)\n    to_replace = []\n    for i_body, item in enumerate(tree.body):\n        if isinstance(item, ast.ClassDef):\n            statements = item.body\n            for i, stmt in enumerate(item.body):\n                if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1:\n                    if isinstance(stmt.targets[0], ast.Name) and stmt.targets[0].id == \"scm\":\n                        try:\n                            if i + 1 == len(statements):  # Last statement in my ClassDef\n                                if i_body + 1 == len(tree.body):  # Last statement over all\n                                    next_line = len(lines)\n                                else:\n                                    next_line = tree.body[i_body+1].lineno - 1\n                            else:\n                                next_line = statements[i+1].lineno - 1\n                        except IndexError:\n                            next_line = stmt.lineno\n                        replace = [line for line in lines[(stmt.lineno-1):next_line]\n                                   if line.strip()]\n                        to_replace.append(\"\".join(replace).lstrip())\n                        break\n    if len(to_replace) != 1:\n        raise ConanException(\"The conanfile.py defines more than one class level 'scm' attribute\")\n\n    new_text = \"scm = \" + \",\\n          \".join(str(scm_data).split(\",\")) + \"\\n\"\n    content = content.replace(to_replace[0], new_text)\n    content = content if not headers else ''.join(headers) + content\n    save(conanfile_path, content)\n\ndef _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):\n\n    exports_folder = client_cache.export(conan_ref)\n    exports_source_folder = client_cache.export_sources(conan_ref, conanfile.short_paths)\n\n    previous_digest = _init_export_folder(exports_folder, exports_source_folder)\n    origin_folder = os.path.dirname(conanfile_path)\n    export_recipe(conanfile, origin_folder, exports_folder, output)\n    export_source(conanfile, origin_folder, exports_source_folder, output)\n    shutil.copy2(conanfile_path, os.path.join(exports_folder, CONANFILE))\n\n    scm_data, captured_revision = _capture_export_scm_data(conanfile,\n                                                           os.path.dirname(conanfile_path),\n                                                           exports_folder,\n                                                           output, client_cache, conan_ref)\n\n    digest = FileTreeManifest.create(exports_folder, exports_source_folder)\n\n    if previous_digest and previous_digest == digest:\n        output.info(\"The stored package has not changed\")\n        modified_recipe = False\n        digest = previous_digest  # Use the old one, keep old timestamp\n    else:\n        output.success('A new %s version was exported' % CONANFILE)\n        output.info('Folder: %s' % exports_folder)\n        modified_recipe = True\n\n    digest.save(exports_folder)\n\n    revision = scm_data.revision if scm_data and captured_revision else digest.summary_hash\n    with client_cache.update_metadata(conan_ref) as metadata:\n        # Note that there is no time set, the time will come from the remote\n        metadata.recipe.revision = revision\n\n    # FIXME: Conan 2.0 Clear the registry entry if the recipe has changed\n    source = client_cache.source(conan_ref, conanfile.short_paths)\n    remove = False\n    if is_dirty(source):\n        output.info(\"Source folder is corrupted, forcing removal\")\n        remove = True\n    elif modified_recipe and not keep_source and os.path.exists(source):\n        output.info(\"Package recipe modified in export, forcing source folder removal\")\n        output.info(\"Use the --keep-source, -k option to skip it\")\n        remove = True\n    if remove:\n        output.info(\"Removing 'source' folder, this can take a while for big packages\")\n        try:\n            # remove only the internal\n            rmdir(source)\n        except BaseException as e:\n            output.error(\"Unable to delete source folder. \"\n                         \"Will be marked as corrupted for deletion\")\n            output.warn(str(e))\n            set_dirty(source)\n\n\ndef _init_export_folder(destination_folder, destination_src_folder):\n    previous_digest = None\n    try:\n        if os.path.exists(destination_folder):\n            if os.path.exists(os.path.join(destination_folder, CONAN_MANIFEST)):\n                previous_digest = FileTreeManifest.load(destination_folder)\n            # Maybe here we want to invalidate cache\n            rmdir(destination_folder)\n        os.makedirs(destination_folder)\n    except Exception as e:\n        raise ConanException(\"Unable to create folder %s\\n%s\" % (destination_folder, str(e)))\n    try:\n        if os.path.exists(destination_src_folder):\n            rmdir(destination_src_folder)\n        os.makedirs(destination_src_folder)\n    except Exception as e:\n        raise ConanException(\"Unable to create folder %s\\n%s\" % (destination_src_folder, str(e)))\n    return previous_digest\n\n\ndef _classify_patterns(patterns):\n    patterns = patterns or []\n    included, excluded = [], []\n    for p in patterns:\n        if p.startswith(\"!\"):\n            excluded.append(p[1:])\n        else:\n            included.append(p)\n    return included, excluded\n\n\ndef export_source(conanfile, origin_folder, destination_source_folder, output):\n    if isinstance(conanfile.exports_sources, str):\n        conanfile.exports_sources = (conanfile.exports_sources, )\n\n    included_sources, excluded_sources = _classify_patterns(conanfile.exports_sources)\n    copier = FileCopier(origin_folder, destination_source_folder)\n    for pattern in included_sources:\n        copier(pattern, links=True, excludes=excluded_sources)\n    package_output = ScopedOutput(\"%s exports_sources\" % output.scope, output)\n    copier.report(package_output)\n\n\ndef export_recipe(conanfile, origin_folder, destination_folder, output):\n    if isinstance(conanfile.exports, str):\n        conanfile.exports = (conanfile.exports, )\n\n    included_exports, excluded_exports = _classify_patterns(conanfile.exports)\n\n    try:\n        os.unlink(os.path.join(origin_folder, CONANFILE + 'c'))\n    except OSError:\n        pass\n\n    copier = FileCopier(origin_folder, destination_folder)\n    for pattern in included_exports:\n        copier(pattern, links=True, excludes=excluded_exports)\n    package_output = ScopedOutput(\"%s exports\" % output.scope, output)\n    copier.report(package_output)\n", "path": "conans/client/cmd/export.py"}]}