We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
cwl-runner --pack is not idempotent
---

## Expected Behavior
Running cwl-runner --pack on an already packed (normalized) cwl workflow should create the same file again.

## Actual Behavior
Id fields do get an additional path "main":
```text
cwl-runner --pack ./v1.0/null-expression1-tool.cwl ./v1.0/null-expression1-job.json > test.txt
cwl-runner --pack test.txt > test2.txt
diff test*
--- test.txt
+++ test2.txt
@@ -9,13 +9,13 @@
         {
             "type": "Any", 
             "default": "the-default", 
-            "id": "#main/i1"
+            "id": "#main/main/i1"
         }
     ], 
     "outputs": [
         {
             "type": "int", 
-            "id": "#main/output"
+            "id": "#main/main/output"
         }
     ], 
     "expression": "$({'output': (inputs.i1 == 'the-default' ? 1 : 2)})", 
```

## Workflow Code
```
See compliance tests
v1.0/null-expression1-tool.cwl

```

## Full Traceback
```pytb
NA
```

## Your Environment
* cwltool version: 1.0.20171017195544
Check using ``cwltool --version``


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `cwltool/pack.py`
Content:
```
1 from __future__ import absolute_import
2 import copy
3 from typing import Any, Callable, Dict, List, Set, Text, Union, cast
4 
5 from schema_salad.ref_resolver import Loader
6 from six.moves import urllib
7 
8 from .process import shortname, uniquename
9 import six
10 
11 
12 def flatten_deps(d, files):  # type: (Any, Set[Text]) -> None
13     if isinstance(d, list):
14         for s in d:
15             flatten_deps(s, files)
16     elif isinstance(d, dict):
17         if d["class"] == "File":
18             files.add(d["location"])
19         if "secondaryFiles" in d:
20             flatten_deps(d["secondaryFiles"], files)
21         if "listing" in d:
22             flatten_deps(d["listing"], files)
23 
24 
25 def find_run(d, loadref, runs):  # type: (Any, Callable[[Text, Text], Union[Dict, List, Text]], Set[Text]) -> None
26     if isinstance(d, list):
27         for s in d:
28             find_run(s, loadref, runs)
29     elif isinstance(d, dict):
30         if "run" in d and isinstance(d["run"], six.string_types):
31             if d["run"] not in runs:
32                 runs.add(d["run"])
33                 find_run(loadref(None, d["run"]), loadref, runs)
34         for s in d.values():
35             find_run(s, loadref, runs)
36 
37 
38 def find_ids(d, ids):  # type: (Any, Set[Text]) -> None
39     if isinstance(d, list):
40         for s in d:
41             find_ids(s, ids)
42     elif isinstance(d, dict):
43         for i in ("id", "name"):
44             if i in d and isinstance(d[i], six.string_types):
45                 ids.add(d[i])
46         for s in d.values():
47             find_ids(s, ids)
48 
49 
50 def replace_refs(d, rewrite, stem, newstem):
51     # type: (Any, Dict[Text, Text], Text, Text) -> None
52     if isinstance(d, list):
53         for s, v in enumerate(d):
54             if isinstance(v, six.string_types):
55                 if v in rewrite:
56                     d[s] = rewrite[v]
57                 elif v.startswith(stem):
58                     d[s] = newstem + v[len(stem):]
59             else:
60                 replace_refs(v, rewrite, stem, newstem)
61     elif isinstance(d, dict):
62         for s, v in d.items():
63             if isinstance(v, six.string_types):
64                 if v in rewrite:
65                     d[s] = rewrite[v]
66                 elif v.startswith(stem):
67                     d[s] = newstem + v[len(stem):]
68             replace_refs(v, rewrite, stem, newstem)
69 
70 def import_embed(d, seen):
71     # type: (Any, Set[Text]) -> None
72     if isinstance(d, list):
73         for v in d:
74             import_embed(v, seen)
75     elif isinstance(d, dict):
76         for n in ("id", "name"):
77             if n in d:
78                 if d[n] in seen:
79                     this = d[n]
80                     d.clear()
81                     d["$import"] = this
82                 else:
83                     this = d[n]
84                     seen.add(this)
85                     break
86 
87         for v in d.values():
88             import_embed(v, seen)
89 
90 
91 def pack(document_loader, processobj, uri, metadata):
92     # type: (Loader, Union[Dict[Text, Any], List[Dict[Text, Any]]], Text, Dict[Text, Text]) -> Dict[Text, Any]
93     def loadref(b, u):
94         # type: (Text, Text) -> Union[Dict, List, Text]
95         return document_loader.resolve_ref(u, base_url=b)[0]
96 
97     runs = {uri}
98     find_run(processobj, loadref, runs)
99 
100     ids = set()  # type: Set[Text]
101     for f in runs:
102         find_ids(document_loader.resolve_ref(f)[0], ids)
103 
104     names = set()  # type: Set[Text]
105     rewrite = {}  # type: Dict[Text, Text]
106 
107     mainpath, _ = urllib.parse.urldefrag(uri)
108 
109     def rewrite_id(r, mainuri):
110         # type: (Text, Text) -> None
111         if r == mainuri:
112             rewrite[r] = "#main"
113         elif r.startswith(mainuri) and r[len(mainuri)] in ("#", "/"):
114             pass
115         else:
116             path, frag = urllib.parse.urldefrag(r)
117             if path == mainpath:
118                 rewrite[r] = "#" + uniquename(frag, names)
119             else:
120                 if path not in rewrite:
121                     rewrite[path] = "#" + uniquename(shortname(path), names)
122 
123     sortedids = sorted(ids)
124 
125     for r in sortedids:
126         if r in document_loader.idx:
127             rewrite_id(r, uri)
128 
129     packed = {"$graph": [], "cwlVersion": metadata["cwlVersion"]
130               }  # type: Dict[Text, Any]
131 
132     schemas = set()  # type: Set[Text]
133     for r in sorted(runs):
134         dcr, metadata = document_loader.resolve_ref(r)
135         if not isinstance(dcr, dict):
136             continue
137         for doc in (dcr, metadata):
138             if "$schemas" in doc:
139                 for s in doc["$schemas"]:
140                     schemas.add(s)
141         if dcr.get("class") not in ("Workflow", "CommandLineTool", "ExpressionTool"):
142             continue
143         dc = cast(Dict[Text, Any], copy.deepcopy(dcr))
144         v = rewrite[r]
145         dc["id"] = v
146         for n in ("name", "cwlVersion", "$namespaces", "$schemas"):
147             if n in dc:
148                 del dc[n]
149         packed["$graph"].append(dc)
150 
151     if schemas:
152         packed["$schemas"] = list(schemas)
153 
154     for r in rewrite:
155         v = rewrite[r]
156         replace_refs(packed, rewrite, r + "/" if "#" in r else r + "#", v + "/")
157 
158     import_embed(packed, set())
159 
160     if len(packed["$graph"]) == 1:
161         # duplicate 'cwlVersion' inside $graph when there is a single item
162         # because we're printing contents inside '$graph' rather than whole dict
163         packed["$graph"][0]["cwlVersion"] = packed["cwlVersion"]
164 
165     return packed
166
```
--- 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/cwltool/pack.py b/cwltool/pack.py
--- a/cwltool/pack.py
+++ b/cwltool/pack.py
@@ -1,9 +1,11 @@
 from __future__ import absolute_import
 import copy
+import re
 from typing import Any, Callable, Dict, List, Set, Text, Union, cast
 
 from schema_salad.ref_resolver import Loader
 from six.moves import urllib
+from ruamel.yaml.comments import CommentedSeq, CommentedMap
 
 from .process import shortname, uniquename
 import six
@@ -64,7 +66,12 @@
                 if v in rewrite:
                     d[s] = rewrite[v]
                 elif v.startswith(stem):
-                    d[s] = newstem + v[len(stem):]
+                    id_ = v[len(stem):]
+                    # prevent appending newstems if tool is already packed
+                    if id_.startswith(newstem.strip("#")):
+                        d[s] = "#" + id_
+                    else:
+                        d[s] = newstem + id_
             replace_refs(v, rewrite, stem, newstem)
 
 def import_embed(d, seen):
@@ -106,12 +113,16 @@
 
     mainpath, _ = urllib.parse.urldefrag(uri)
 
-    def rewrite_id(r, mainuri):
-        # type: (Text, Text) -> None
+    def rewrite_id(r, mainuri, document_packed=False):
+        # type: (Text, Text, bool) -> None
         if r == mainuri:
             rewrite[r] = "#main"
         elif r.startswith(mainuri) and r[len(mainuri)] in ("#", "/"):
-            pass
+            if document_packed:
+                # rewrite tool and mainuri ids in a packed document
+                tool_id = re.search("#[^/]*$", r)
+                if tool_id:
+                    rewrite[r] = tool_id.group()
         else:
             path, frag = urllib.parse.urldefrag(r)
             if path == mainpath:
@@ -122,9 +133,10 @@
 
     sortedids = sorted(ids)
 
+    is_document_packed = all(id.startswith(uri) for id in sortedids)
     for r in sortedids:
         if r in document_loader.idx:
-            rewrite_id(r, uri)
+            rewrite_id(r, uri, is_document_packed)
 
     packed = {"$graph": [], "cwlVersion": metadata["cwlVersion"]
               }  # type: Dict[Text, Any]
@@ -132,6 +144,9 @@
     schemas = set()  # type: Set[Text]
     for r in sorted(runs):
         dcr, metadata = document_loader.resolve_ref(r)
+        if isinstance(dcr, CommentedSeq):
+            dcr = dcr[0]
+            dcr = cast(CommentedMap, dcr)
         if not isinstance(dcr, dict):
             continue
         for doc in (dcr, metadata):
@@ -141,7 +156,7 @@
         if dcr.get("class") not in ("Workflow", "CommandLineTool", "ExpressionTool"):
             continue
         dc = cast(Dict[Text, Any], copy.deepcopy(dcr))
-        v = rewrite[r]
+        v = rewrite.get(r, r + "#main")
         dc["id"] = v
         for n in ("name", "cwlVersion", "$namespaces", "$schemas"):
             if n in dc:

{"golden_diff": "diff --git a/cwltool/pack.py b/cwltool/pack.py\n--- a/cwltool/pack.py\n+++ b/cwltool/pack.py\n@@ -1,9 +1,11 @@\n from __future__ import absolute_import\n import copy\n+import re\n from typing import Any, Callable, Dict, List, Set, Text, Union, cast\n \n from schema_salad.ref_resolver import Loader\n from six.moves import urllib\n+from ruamel.yaml.comments import CommentedSeq, CommentedMap\n \n from .process import shortname, uniquename\n import six\n@@ -64,7 +66,12 @@\n                 if v in rewrite:\n                     d[s] = rewrite[v]\n                 elif v.startswith(stem):\n-                    d[s] = newstem + v[len(stem):]\n+                    id_ = v[len(stem):]\n+                    # prevent appending newstems if tool is already packed\n+                    if id_.startswith(newstem.strip(\"#\")):\n+                        d[s] = \"#\" + id_\n+                    else:\n+                        d[s] = newstem + id_\n             replace_refs(v, rewrite, stem, newstem)\n \n def import_embed(d, seen):\n@@ -106,12 +113,16 @@\n \n     mainpath, _ = urllib.parse.urldefrag(uri)\n \n-    def rewrite_id(r, mainuri):\n-        # type: (Text, Text) -> None\n+    def rewrite_id(r, mainuri, document_packed=False):\n+        # type: (Text, Text, bool) -> None\n         if r == mainuri:\n             rewrite[r] = \"#main\"\n         elif r.startswith(mainuri) and r[len(mainuri)] in (\"#\", \"/\"):\n-            pass\n+            if document_packed:\n+                # rewrite tool and mainuri ids in a packed document\n+                tool_id = re.search(\"#[^/]*$\", r)\n+                if tool_id:\n+                    rewrite[r] = tool_id.group()\n         else:\n             path, frag = urllib.parse.urldefrag(r)\n             if path == mainpath:\n@@ -122,9 +133,10 @@\n \n     sortedids = sorted(ids)\n \n+    is_document_packed = all(id.startswith(uri) for id in sortedids)\n     for r in sortedids:\n         if r in document_loader.idx:\n-            rewrite_id(r, uri)\n+            rewrite_id(r, uri, is_document_packed)\n \n     packed = {\"$graph\": [], \"cwlVersion\": metadata[\"cwlVersion\"]\n               }  # type: Dict[Text, Any]\n@@ -132,6 +144,9 @@\n     schemas = set()  # type: Set[Text]\n     for r in sorted(runs):\n         dcr, metadata = document_loader.resolve_ref(r)\n+        if isinstance(dcr, CommentedSeq):\n+            dcr = dcr[0]\n+            dcr = cast(CommentedMap, dcr)\n         if not isinstance(dcr, dict):\n             continue\n         for doc in (dcr, metadata):\n@@ -141,7 +156,7 @@\n         if dcr.get(\"class\") not in (\"Workflow\", \"CommandLineTool\", \"ExpressionTool\"):\n             continue\n         dc = cast(Dict[Text, Any], copy.deepcopy(dcr))\n-        v = rewrite[r]\n+        v = rewrite.get(r, r + \"#main\")\n         dc[\"id\"] = v\n         for n in (\"name\", \"cwlVersion\", \"$namespaces\", \"$schemas\"):\n             if n in dc:\n", "issue": "cwl-runner --pack is not idempotent\n---\r\n\r\n## Expected Behavior\r\nRunning cwl-runner --pack on an already packed (normalized) cwl workflow should create the same file again.\r\n\r\n## Actual Behavior\r\nId fields do get an additional path \"main\":\r\n```text\r\ncwl-runner --pack ./v1.0/null-expression1-tool.cwl ./v1.0/null-expression1-job.json > test.txt\r\ncwl-runner --pack test.txt > test2.txt\r\ndiff test*\r\n--- test.txt\r\n+++ test2.txt\r\n@@ -9,13 +9,13 @@\r\n         {\r\n             \"type\": \"Any\", \r\n             \"default\": \"the-default\", \r\n-            \"id\": \"#main/i1\"\r\n+            \"id\": \"#main/main/i1\"\r\n         }\r\n     ], \r\n     \"outputs\": [\r\n         {\r\n             \"type\": \"int\", \r\n-            \"id\": \"#main/output\"\r\n+            \"id\": \"#main/main/output\"\r\n         }\r\n     ], \r\n     \"expression\": \"$({'output': (inputs.i1 == 'the-default' ? 1 : 2)})\", \r\n```\r\n\r\n## Workflow Code\r\n```\r\nSee compliance tests\r\nv1.0/null-expression1-tool.cwl\r\n\r\n```\r\n\r\n## Full Traceback\r\n```pytb\r\nNA\r\n```\r\n\r\n## Your Environment\r\n* cwltool version: 1.0.20171017195544\r\nCheck using ``cwltool --version``\r\n\n", "before_files": [{"content": "from __future__ import absolute_import\nimport copy\nfrom typing import Any, Callable, Dict, List, Set, Text, Union, cast\n\nfrom schema_salad.ref_resolver import Loader\nfrom six.moves import urllib\n\nfrom .process import shortname, uniquename\nimport six\n\n\ndef flatten_deps(d, files):  # type: (Any, Set[Text]) -> None\n    if isinstance(d, list):\n        for s in d:\n            flatten_deps(s, files)\n    elif isinstance(d, dict):\n        if d[\"class\"] == \"File\":\n            files.add(d[\"location\"])\n        if \"secondaryFiles\" in d:\n            flatten_deps(d[\"secondaryFiles\"], files)\n        if \"listing\" in d:\n            flatten_deps(d[\"listing\"], files)\n\n\ndef find_run(d, loadref, runs):  # type: (Any, Callable[[Text, Text], Union[Dict, List, Text]], Set[Text]) -> None\n    if isinstance(d, list):\n        for s in d:\n            find_run(s, loadref, runs)\n    elif isinstance(d, dict):\n        if \"run\" in d and isinstance(d[\"run\"], six.string_types):\n            if d[\"run\"] not in runs:\n                runs.add(d[\"run\"])\n                find_run(loadref(None, d[\"run\"]), loadref, runs)\n        for s in d.values():\n            find_run(s, loadref, runs)\n\n\ndef find_ids(d, ids):  # type: (Any, Set[Text]) -> None\n    if isinstance(d, list):\n        for s in d:\n            find_ids(s, ids)\n    elif isinstance(d, dict):\n        for i in (\"id\", \"name\"):\n            if i in d and isinstance(d[i], six.string_types):\n                ids.add(d[i])\n        for s in d.values():\n            find_ids(s, ids)\n\n\ndef replace_refs(d, rewrite, stem, newstem):\n    # type: (Any, Dict[Text, Text], Text, Text) -> None\n    if isinstance(d, list):\n        for s, v in enumerate(d):\n            if isinstance(v, six.string_types):\n                if v in rewrite:\n                    d[s] = rewrite[v]\n                elif v.startswith(stem):\n                    d[s] = newstem + v[len(stem):]\n            else:\n                replace_refs(v, rewrite, stem, newstem)\n    elif isinstance(d, dict):\n        for s, v in d.items():\n            if isinstance(v, six.string_types):\n                if v in rewrite:\n                    d[s] = rewrite[v]\n                elif v.startswith(stem):\n                    d[s] = newstem + v[len(stem):]\n            replace_refs(v, rewrite, stem, newstem)\n\ndef import_embed(d, seen):\n    # type: (Any, Set[Text]) -> None\n    if isinstance(d, list):\n        for v in d:\n            import_embed(v, seen)\n    elif isinstance(d, dict):\n        for n in (\"id\", \"name\"):\n            if n in d:\n                if d[n] in seen:\n                    this = d[n]\n                    d.clear()\n                    d[\"$import\"] = this\n                else:\n                    this = d[n]\n                    seen.add(this)\n                    break\n\n        for v in d.values():\n            import_embed(v, seen)\n\n\ndef pack(document_loader, processobj, uri, metadata):\n    # type: (Loader, Union[Dict[Text, Any], List[Dict[Text, Any]]], Text, Dict[Text, Text]) -> Dict[Text, Any]\n    def loadref(b, u):\n        # type: (Text, Text) -> Union[Dict, List, Text]\n        return document_loader.resolve_ref(u, base_url=b)[0]\n\n    runs = {uri}\n    find_run(processobj, loadref, runs)\n\n    ids = set()  # type: Set[Text]\n    for f in runs:\n        find_ids(document_loader.resolve_ref(f)[0], ids)\n\n    names = set()  # type: Set[Text]\n    rewrite = {}  # type: Dict[Text, Text]\n\n    mainpath, _ = urllib.parse.urldefrag(uri)\n\n    def rewrite_id(r, mainuri):\n        # type: (Text, Text) -> None\n        if r == mainuri:\n            rewrite[r] = \"#main\"\n        elif r.startswith(mainuri) and r[len(mainuri)] in (\"#\", \"/\"):\n            pass\n        else:\n            path, frag = urllib.parse.urldefrag(r)\n            if path == mainpath:\n                rewrite[r] = \"#\" + uniquename(frag, names)\n            else:\n                if path not in rewrite:\n                    rewrite[path] = \"#\" + uniquename(shortname(path), names)\n\n    sortedids = sorted(ids)\n\n    for r in sortedids:\n        if r in document_loader.idx:\n            rewrite_id(r, uri)\n\n    packed = {\"$graph\": [], \"cwlVersion\": metadata[\"cwlVersion\"]\n              }  # type: Dict[Text, Any]\n\n    schemas = set()  # type: Set[Text]\n    for r in sorted(runs):\n        dcr, metadata = document_loader.resolve_ref(r)\n        if not isinstance(dcr, dict):\n            continue\n        for doc in (dcr, metadata):\n            if \"$schemas\" in doc:\n                for s in doc[\"$schemas\"]:\n                    schemas.add(s)\n        if dcr.get(\"class\") not in (\"Workflow\", \"CommandLineTool\", \"ExpressionTool\"):\n            continue\n        dc = cast(Dict[Text, Any], copy.deepcopy(dcr))\n        v = rewrite[r]\n        dc[\"id\"] = v\n        for n in (\"name\", \"cwlVersion\", \"$namespaces\", \"$schemas\"):\n            if n in dc:\n                del dc[n]\n        packed[\"$graph\"].append(dc)\n\n    if schemas:\n        packed[\"$schemas\"] = list(schemas)\n\n    for r in rewrite:\n        v = rewrite[r]\n        replace_refs(packed, rewrite, r + \"/\" if \"#\" in r else r + \"#\", v + \"/\")\n\n    import_embed(packed, set())\n\n    if len(packed[\"$graph\"]) == 1:\n        # duplicate 'cwlVersion' inside $graph when there is a single item\n        # because we're printing contents inside '$graph' rather than whole dict\n        packed[\"$graph\"][0][\"cwlVersion\"] = packed[\"cwlVersion\"]\n\n    return packed\n", "path": "cwltool/pack.py"}], "after_files": [{"content": "from __future__ import absolute_import\nimport copy\nimport re\nfrom typing import Any, Callable, Dict, List, Set, Text, Union, cast\n\nfrom schema_salad.ref_resolver import Loader\nfrom six.moves import urllib\nfrom ruamel.yaml.comments import CommentedSeq, CommentedMap\n\nfrom .process import shortname, uniquename\nimport six\n\n\ndef flatten_deps(d, files):  # type: (Any, Set[Text]) -> None\n    if isinstance(d, list):\n        for s in d:\n            flatten_deps(s, files)\n    elif isinstance(d, dict):\n        if d[\"class\"] == \"File\":\n            files.add(d[\"location\"])\n        if \"secondaryFiles\" in d:\n            flatten_deps(d[\"secondaryFiles\"], files)\n        if \"listing\" in d:\n            flatten_deps(d[\"listing\"], files)\n\n\ndef find_run(d, loadref, runs):  # type: (Any, Callable[[Text, Text], Union[Dict, List, Text]], Set[Text]) -> None\n    if isinstance(d, list):\n        for s in d:\n            find_run(s, loadref, runs)\n    elif isinstance(d, dict):\n        if \"run\" in d and isinstance(d[\"run\"], six.string_types):\n            if d[\"run\"] not in runs:\n                runs.add(d[\"run\"])\n                find_run(loadref(None, d[\"run\"]), loadref, runs)\n        for s in d.values():\n            find_run(s, loadref, runs)\n\n\ndef find_ids(d, ids):  # type: (Any, Set[Text]) -> None\n    if isinstance(d, list):\n        for s in d:\n            find_ids(s, ids)\n    elif isinstance(d, dict):\n        for i in (\"id\", \"name\"):\n            if i in d and isinstance(d[i], six.string_types):\n                ids.add(d[i])\n        for s in d.values():\n            find_ids(s, ids)\n\n\ndef replace_refs(d, rewrite, stem, newstem):\n    # type: (Any, Dict[Text, Text], Text, Text) -> None\n    if isinstance(d, list):\n        for s, v in enumerate(d):\n            if isinstance(v, six.string_types):\n                if v in rewrite:\n                    d[s] = rewrite[v]\n                elif v.startswith(stem):\n                    d[s] = newstem + v[len(stem):]\n            else:\n                replace_refs(v, rewrite, stem, newstem)\n    elif isinstance(d, dict):\n        for s, v in d.items():\n            if isinstance(v, six.string_types):\n                if v in rewrite:\n                    d[s] = rewrite[v]\n                elif v.startswith(stem):\n                    id_ = v[len(stem):]\n                    # prevent appending newstems if tool is already packed\n                    if id_.startswith(newstem.strip(\"#\")):\n                        d[s] = \"#\" + id_\n                    else:\n                        d[s] = newstem + id_\n            replace_refs(v, rewrite, stem, newstem)\n\ndef import_embed(d, seen):\n    # type: (Any, Set[Text]) -> None\n    if isinstance(d, list):\n        for v in d:\n            import_embed(v, seen)\n    elif isinstance(d, dict):\n        for n in (\"id\", \"name\"):\n            if n in d:\n                if d[n] in seen:\n                    this = d[n]\n                    d.clear()\n                    d[\"$import\"] = this\n                else:\n                    this = d[n]\n                    seen.add(this)\n                    break\n\n        for v in d.values():\n            import_embed(v, seen)\n\n\ndef pack(document_loader, processobj, uri, metadata):\n    # type: (Loader, Union[Dict[Text, Any], List[Dict[Text, Any]]], Text, Dict[Text, Text]) -> Dict[Text, Any]\n    def loadref(b, u):\n        # type: (Text, Text) -> Union[Dict, List, Text]\n        return document_loader.resolve_ref(u, base_url=b)[0]\n\n    runs = {uri}\n    find_run(processobj, loadref, runs)\n\n    ids = set()  # type: Set[Text]\n    for f in runs:\n        find_ids(document_loader.resolve_ref(f)[0], ids)\n\n    names = set()  # type: Set[Text]\n    rewrite = {}  # type: Dict[Text, Text]\n\n    mainpath, _ = urllib.parse.urldefrag(uri)\n\n    def rewrite_id(r, mainuri, document_packed=False):\n        # type: (Text, Text, bool) -> None\n        if r == mainuri:\n            rewrite[r] = \"#main\"\n        elif r.startswith(mainuri) and r[len(mainuri)] in (\"#\", \"/\"):\n            if document_packed:\n                # rewrite tool and mainuri ids in a packed document\n                tool_id = re.search(\"#[^/]*$\", r)\n                if tool_id:\n                    rewrite[r] = tool_id.group()\n        else:\n            path, frag = urllib.parse.urldefrag(r)\n            if path == mainpath:\n                rewrite[r] = \"#\" + uniquename(frag, names)\n            else:\n                if path not in rewrite:\n                    rewrite[path] = \"#\" + uniquename(shortname(path), names)\n\n    sortedids = sorted(ids)\n\n    is_document_packed = all(id.startswith(uri) for id in sortedids)\n    for r in sortedids:\n        if r in document_loader.idx:\n            rewrite_id(r, uri, is_document_packed)\n\n    packed = {\"$graph\": [], \"cwlVersion\": metadata[\"cwlVersion\"]\n              }  # type: Dict[Text, Any]\n\n    schemas = set()  # type: Set[Text]\n    for r in sorted(runs):\n        dcr, metadata = document_loader.resolve_ref(r)\n        if isinstance(dcr, CommentedSeq):\n            dcr = dcr[0]\n            dcr = cast(CommentedMap, dcr)\n        if not isinstance(dcr, dict):\n            continue\n        for doc in (dcr, metadata):\n            if \"$schemas\" in doc:\n                for s in doc[\"$schemas\"]:\n                    schemas.add(s)\n        if dcr.get(\"class\") not in (\"Workflow\", \"CommandLineTool\", \"ExpressionTool\"):\n            continue\n        dc = cast(Dict[Text, Any], copy.deepcopy(dcr))\n        v = rewrite.get(r, r + \"#main\")\n        dc[\"id\"] = v\n        for n in (\"name\", \"cwlVersion\", \"$namespaces\", \"$schemas\"):\n            if n in dc:\n                del dc[n]\n        packed[\"$graph\"].append(dc)\n\n    if schemas:\n        packed[\"$schemas\"] = list(schemas)\n\n    for r in rewrite:\n        v = rewrite[r]\n        replace_refs(packed, rewrite, r + \"/\" if \"#\" in r else r + \"#\", v + \"/\")\n\n    import_embed(packed, set())\n\n    if len(packed[\"$graph\"]) == 1:\n        # duplicate 'cwlVersion' inside $graph when there is a single item\n        # because we're printing contents inside '$graph' rather than whole dict\n        packed[\"$graph\"][0][\"cwlVersion\"] = packed[\"cwlVersion\"]\n\n    return packed\n", "path": "cwltool/pack.py"}]}