prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  ERROR: Cannot locate specified Dockerfile
  I'm not sure if this is a Docker Compose bug or docker-py bug, but this used to work:
  
  `docker-compose.yml`:
  ```yaml
  version: '3.5'
  
  services:
    php:
      build:
        context: .
        dockerfile: ./docker/php.Dockerfile
  ```
  
  but now the `./` prefix is causing:
  ```
  ERROR: Cannot locate specified Dockerfile: ./docker/php.Dockerfile
  ```
  
  I have to change it to `dockerfile: docker/php.Dockerfile` to get it to work.
  
  --
  
  docker-py version: 3.1.1
  
  Python 3.6.4
  
  `docker version`:
  ```
  Client:
   Version:	18.02.0-ce
   API version:	1.36
   Go version:	go1.9.4
   Git commit:	fc4de447b5
   Built:	Tue Feb 13 15:28:01 2018
   OS/Arch:	linux/amd64
   Experimental:	false
   Orchestrator:	swarm
  
  Server:
   Engine:
    Version:	18.02.0-ce
    API version:	1.36 (minimum version 1.12)
    Go version:	go1.9.4
    Git commit:	fc4de447b5
    Built:	Tue Feb 13 15:28:34 2018
    OS/Arch:	linux/amd64
    Experimental:	false
  ```
  
  OS: Manjaro Linux 17.1.6
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `docker/utils/build.py`
  Content:
  ```
  1 import os
  2 import re
  3 
  4 from ..constants import IS_WINDOWS_PLATFORM
  5 from fnmatch import fnmatch
  6 from itertools import chain
  7 from .utils import create_archive
  8 
  9 
  10 def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
  11     root = os.path.abspath(path)
  12     exclude = exclude or []
  13     return create_archive(
  14         files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile)),
  15         root=root, fileobj=fileobj, gzip=gzip
  16     )
  17 
  18 
  19 _SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')
  20 
  21 
  22 def exclude_paths(root, patterns, dockerfile=None):
  23     """
  24     Given a root directory path and a list of .dockerignore patterns, return
  25     an iterator of all paths (both regular files and directories) in the root
  26     directory that do *not* match any of the patterns.
  27 
  28     All paths returned are relative to the root.
  29     """
  30 
  31     if dockerfile is None:
  32         dockerfile = 'Dockerfile'
  33 
  34     def normalize(p):
  35         # Leading and trailing slashes are not relevant. Yes,
  36         # "foo.py/" must exclude the "foo.py" regular file. "."
  37         # components are not relevant either, even if the whole
  38         # pattern is only ".", as the Docker reference states: "For
  39         # historical reasons, the pattern . is ignored."
  40         split = [pt for pt in re.split(_SEP, p) if pt and pt != '.']
  41         # ".." component must be cleared with the potential previous
  42         # component, regardless of whether it exists: "A preprocessing
  43         # step [...]  eliminates . and .. elements using Go's
  44         # filepath.".
  45         i = 0
  46         while i < len(split):
  47             if split[i] == '..':
  48                 del split[i]
  49                 if i > 0:
  50                     del split[i - 1]
  51                     i -= 1
  52             else:
  53                 i += 1
  54         return split
  55 
  56     patterns = (
  57         (True, normalize(p[1:]))
  58         if p.startswith('!') else
  59         (False, normalize(p))
  60         for p in patterns)
  61     patterns = list(reversed(list(chain(
  62         # Exclude empty patterns such as "." or the empty string.
  63         filter(lambda p: p[1], patterns),
  64         # Always include the Dockerfile and .dockerignore
  65         [(True, dockerfile.split('/')), (True, ['.dockerignore'])]))))
  66     return set(walk(root, patterns))
  67 
  68 
  69 def walk(root, patterns, default=True):
  70     """
  71     A collection of file lying below root that should be included according to
  72     patterns.
  73     """
  74 
  75     def match(p):
  76         if p[1][0] == '**':
  77             rec = (p[0], p[1][1:])
  78             return [p] + (match(rec) if rec[1] else [rec])
  79         elif fnmatch(f, p[1][0]):
  80             return [(p[0], p[1][1:])]
  81         else:
  82             return []
  83 
  84     for f in os.listdir(root):
  85         cur = os.path.join(root, f)
  86         # The patterns if recursing in that directory.
  87         sub = list(chain(*(match(p) for p in patterns)))
  88         # Whether this file is explicitely included / excluded.
  89         hit = next((p[0] for p in sub if not p[1]), None)
  90         # Whether this file is implicitely included / excluded.
  91         matched = default if hit is None else hit
  92         sub = list(filter(lambda p: p[1], sub))
  93         if os.path.isdir(cur):
  94             # Entirely skip directories if there are no chance any subfile will
  95             # be included.
  96             if all(not p[0] for p in sub) and not matched:
  97                 continue
  98             # I think this would greatly speed up dockerignore handling by not
  99             # recursing into directories we are sure would be entirely
  100             # included, and only yielding the directory itself, which will be
  101             # recursively archived anyway. However the current unit test expect
  102             # the full list of subfiles and I'm not 100% sure it would make no
  103             # difference yet.
  104             # if all(p[0] for p in sub) and matched:
  105             #     yield f
  106             #     continue
  107             children = False
  108             for r in (os.path.join(f, p) for p in walk(cur, sub, matched)):
  109                 yield r
  110                 children = True
  111             # The current unit tests expect directories only under those
  112             # conditions. It might be simplifiable though.
  113             if (not sub or not children) and hit or hit is None and default:
  114                 yield f
  115         elif matched:
  116             yield f
  117
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/docker/utils/build.py b/docker/utils/build.py
  --- a/docker/utils/build.py
  +++ b/docker/utils/build.py
  @@ -31,18 +31,21 @@
       if dockerfile is None:
           dockerfile = 'Dockerfile'
   
  +    def split_path(p):
  +        return [pt for pt in re.split(_SEP, p) if pt and pt != '.']
  +
       def normalize(p):
           # Leading and trailing slashes are not relevant. Yes,
           # "foo.py/" must exclude the "foo.py" regular file. "."
           # components are not relevant either, even if the whole
           # pattern is only ".", as the Docker reference states: "For
           # historical reasons, the pattern . is ignored."
  -        split = [pt for pt in re.split(_SEP, p) if pt and pt != '.']
           # ".." component must be cleared with the potential previous
           # component, regardless of whether it exists: "A preprocessing
           # step [...]  eliminates . and .. elements using Go's
           # filepath.".
           i = 0
  +        split = split_path(p)
           while i < len(split):
               if split[i] == '..':
                   del split[i]
  @@ -62,7 +65,7 @@
           # Exclude empty patterns such as "." or the empty string.
           filter(lambda p: p[1], patterns),
           # Always include the Dockerfile and .dockerignore
  -        [(True, dockerfile.split('/')), (True, ['.dockerignore'])]))))
  +        [(True, split_path(dockerfile)), (True, ['.dockerignore'])]))))
       return set(walk(root, patterns))
  
verification_info: {"golden_diff": "diff --git a/docker/utils/build.py b/docker/utils/build.py\n--- a/docker/utils/build.py\n+++ b/docker/utils/build.py\n@@ -31,18 +31,21 @@\n     if dockerfile is None:\n         dockerfile = 'Dockerfile'\n \n+    def split_path(p):\n+        return [pt for pt in re.split(_SEP, p) if pt and pt != '.']\n+\n     def normalize(p):\n         # Leading and trailing slashes are not relevant. Yes,\n         # \"foo.py/\" must exclude the \"foo.py\" regular file. \".\"\n         # components are not relevant either, even if the whole\n         # pattern is only \".\", as the Docker reference states: \"For\n         # historical reasons, the pattern . is ignored.\"\n-        split = [pt for pt in re.split(_SEP, p) if pt and pt != '.']\n         # \"..\" component must be cleared with the potential previous\n         # component, regardless of whether it exists: \"A preprocessing\n         # step [...]  eliminates . and .. elements using Go's\n         # filepath.\".\n         i = 0\n+        split = split_path(p)\n         while i < len(split):\n             if split[i] == '..':\n                 del split[i]\n@@ -62,7 +65,7 @@\n         # Exclude empty patterns such as \".\" or the empty string.\n         filter(lambda p: p[1], patterns),\n         # Always include the Dockerfile and .dockerignore\n-        [(True, dockerfile.split('/')), (True, ['.dockerignore'])]))))\n+        [(True, split_path(dockerfile)), (True, ['.dockerignore'])]))))\n     return set(walk(root, patterns))\n", "issue": "ERROR: Cannot locate specified Dockerfile\nI'm not sure if this is a Docker Compose bug or docker-py bug, but this used to work:\r\n\r\n`docker-compose.yml`:\r\n```yaml\r\nversion: '3.5'\r\n\r\nservices:\r\n  php:\r\n    build:\r\n      context: .\r\n      dockerfile: ./docker/php.Dockerfile\r\n```\r\n\r\nbut now the `./` prefix is causing:\r\n```\r\nERROR: Cannot locate specified Dockerfile: ./docker/php.Dockerfile\r\n```\r\n\r\nI have to change it to `dockerfile: docker/php.Dockerfile` to get it to work.\r\n\r\n--\r\n\r\ndocker-py version: 3.1.1\r\n\r\nPython 3.6.4\r\n\r\n`docker version`:\r\n```\r\nClient:\r\n Version:\t18.02.0-ce\r\n API version:\t1.36\r\n Go version:\tgo1.9.4\r\n Git commit:\tfc4de447b5\r\n Built:\tTue Feb 13 15:28:01 2018\r\n OS/Arch:\tlinux/amd64\r\n Experimental:\tfalse\r\n Orchestrator:\tswarm\r\n\r\nServer:\r\n Engine:\r\n  Version:\t18.02.0-ce\r\n  API version:\t1.36 (minimum version 1.12)\r\n  Go version:\tgo1.9.4\r\n  Git commit:\tfc4de447b5\r\n  Built:\tTue Feb 13 15:28:34 2018\r\n  OS/Arch:\tlinux/amd64\r\n  Experimental:\tfalse\r\n```\r\n\r\nOS: Manjaro Linux 17.1.6\n", "before_files": [{"content": "import os\nimport re\n\nfrom ..constants import IS_WINDOWS_PLATFORM\nfrom fnmatch import fnmatch\nfrom itertools import chain\nfrom .utils import create_archive\n\n\ndef tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):\n    root = os.path.abspath(path)\n    exclude = exclude or []\n    return create_archive(\n        files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile)),\n        root=root, fileobj=fileobj, gzip=gzip\n    )\n\n\n_SEP = re.compile('/|\\\\\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')\n\n\ndef exclude_paths(root, patterns, dockerfile=None):\n    \"\"\"\n    Given a root directory path and a list of .dockerignore patterns, return\n    an iterator of all paths (both regular files and directories) in the root\n    directory that do *not* match any of the patterns.\n\n    All paths returned are relative to the root.\n    \"\"\"\n\n    if dockerfile is None:\n        dockerfile = 'Dockerfile'\n\n    def normalize(p):\n        # Leading and trailing slashes are not relevant. Yes,\n        # \"foo.py/\" must exclude the \"foo.py\" regular file. \".\"\n        # components are not relevant either, even if the whole\n        # pattern is only \".\", as the Docker reference states: \"For\n        # historical reasons, the pattern . is ignored.\"\n        split = [pt for pt in re.split(_SEP, p) if pt and pt != '.']\n        # \"..\" component must be cleared with the potential previous\n        # component, regardless of whether it exists: \"A preprocessing\n        # step [...]  eliminates . and .. elements using Go's\n        # filepath.\".\n        i = 0\n        while i < len(split):\n            if split[i] == '..':\n                del split[i]\n                if i > 0:\n                    del split[i - 1]\n                    i -= 1\n            else:\n                i += 1\n        return split\n\n    patterns = (\n        (True, normalize(p[1:]))\n        if p.startswith('!') else\n        (False, normalize(p))\n        for p in patterns)\n    patterns = list(reversed(list(chain(\n        # Exclude empty patterns such as \".\" or the empty string.\n        filter(lambda p: p[1], patterns),\n        # Always include the Dockerfile and .dockerignore\n        [(True, dockerfile.split('/')), (True, ['.dockerignore'])]))))\n    return set(walk(root, patterns))\n\n\ndef walk(root, patterns, default=True):\n    \"\"\"\n    A collection of file lying below root that should be included according to\n    patterns.\n    \"\"\"\n\n    def match(p):\n        if p[1][0] == '**':\n            rec = (p[0], p[1][1:])\n            return [p] + (match(rec) if rec[1] else [rec])\n        elif fnmatch(f, p[1][0]):\n            return [(p[0], p[1][1:])]\n        else:\n            return []\n\n    for f in os.listdir(root):\n        cur = os.path.join(root, f)\n        # The patterns if recursing in that directory.\n        sub = list(chain(*(match(p) for p in patterns)))\n        # Whether this file is explicitely included / excluded.\n        hit = next((p[0] for p in sub if not p[1]), None)\n        # Whether this file is implicitely included / excluded.\n        matched = default if hit is None else hit\n        sub = list(filter(lambda p: p[1], sub))\n        if os.path.isdir(cur):\n            # Entirely skip directories if there are no chance any subfile will\n            # be included.\n            if all(not p[0] for p in sub) and not matched:\n                continue\n            # I think this would greatly speed up dockerignore handling by not\n            # recursing into directories we are sure would be entirely\n            # included, and only yielding the directory itself, which will be\n            # recursively archived anyway. However the current unit test expect\n            # the full list of subfiles and I'm not 100% sure it would make no\n            # difference yet.\n            # if all(p[0] for p in sub) and matched:\n            #     yield f\n            #     continue\n            children = False\n            for r in (os.path.join(f, p) for p in walk(cur, sub, matched)):\n                yield r\n                children = True\n            # The current unit tests expect directories only under those\n            # conditions. It might be simplifiable though.\n            if (not sub or not children) and hit or hit is None and default:\n                yield f\n        elif matched:\n            yield f\n", "path": "docker/utils/build.py"}], "after_files": [{"content": "import os\nimport re\n\nfrom ..constants import IS_WINDOWS_PLATFORM\nfrom fnmatch import fnmatch\nfrom itertools import chain\nfrom .utils import create_archive\n\n\ndef tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):\n    root = os.path.abspath(path)\n    exclude = exclude or []\n    return create_archive(\n        files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile)),\n        root=root, fileobj=fileobj, gzip=gzip\n    )\n\n\n_SEP = re.compile('/|\\\\\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')\n\n\ndef exclude_paths(root, patterns, dockerfile=None):\n    \"\"\"\n    Given a root directory path and a list of .dockerignore patterns, return\n    an iterator of all paths (both regular files and directories) in the root\n    directory that do *not* match any of the patterns.\n\n    All paths returned are relative to the root.\n    \"\"\"\n\n    if dockerfile is None:\n        dockerfile = 'Dockerfile'\n\n    def split_path(p):\n        return [pt for pt in re.split(_SEP, p) if pt and pt != '.']\n\n    def normalize(p):\n        # Leading and trailing slashes are not relevant. Yes,\n        # \"foo.py/\" must exclude the \"foo.py\" regular file. \".\"\n        # components are not relevant either, even if the whole\n        # pattern is only \".\", as the Docker reference states: \"For\n        # historical reasons, the pattern . is ignored.\"\n        # \"..\" component must be cleared with the potential previous\n        # component, regardless of whether it exists: \"A preprocessing\n        # step [...]  eliminates . and .. elements using Go's\n        # filepath.\".\n        i = 0\n        split = split_path(p)\n        while i < len(split):\n            if split[i] == '..':\n                del split[i]\n                if i > 0:\n                    del split[i - 1]\n                    i -= 1\n            else:\n                i += 1\n        return split\n\n    patterns = (\n        (True, normalize(p[1:]))\n        if p.startswith('!') else\n        (False, normalize(p))\n        for p in patterns)\n    patterns = list(reversed(list(chain(\n        # Exclude empty patterns such as \".\" or the empty string.\n        filter(lambda p: p[1], patterns),\n        # Always include the Dockerfile and .dockerignore\n        [(True, split_path(dockerfile)), (True, ['.dockerignore'])]))))\n    return set(walk(root, patterns))\n\n\ndef walk(root, patterns, default=True):\n    \"\"\"\n    A collection of file lying below root that should be included according to\n    patterns.\n    \"\"\"\n\n    def match(p):\n        if p[1][0] == '**':\n            rec = (p[0], p[1][1:])\n            return [p] + (match(rec) if rec[1] else [rec])\n        elif fnmatch(f, p[1][0]):\n            return [(p[0], p[1][1:])]\n        else:\n            return []\n\n    for f in os.listdir(root):\n        cur = os.path.join(root, f)\n        # The patterns if recursing in that directory.\n        sub = list(chain(*(match(p) for p in patterns)))\n        # Whether this file is explicitely included / excluded.\n        hit = next((p[0] for p in sub if not p[1]), None)\n        # Whether this file is implicitely included / excluded.\n        matched = default if hit is None else hit\n        sub = list(filter(lambda p: p[1], sub))\n        if os.path.isdir(cur):\n            # Entirely skip directories if there are no chance any subfile will\n            # be included.\n            if all(not p[0] for p in sub) and not matched:\n                continue\n            # I think this would greatly speed up dockerignore handling by not\n            # recursing into directories we are sure would be entirely\n            # included, and only yielding the directory itself, which will be\n            # recursively archived anyway. However the current unit test expect\n            # the full list of subfiles and I'm not 100% sure it would make no\n            # difference yet.\n            # if all(p[0] for p in sub) and matched:\n            #     yield f\n            #     continue\n            children = False\n            for r in (os.path.join(f, p) for p in walk(cur, sub, matched)):\n                yield r\n                children = True\n            # The current unit tests expect directories only under those\n            # conditions. It might be simplifiable though.\n            if (not sub or not children) and hit or hit is None and default:\n                yield f\n        elif matched:\n            yield f\n", "path": "docker/utils/build.py"}]}