We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Docker image for 2.4.0 contains packages from 2.3.1
The Docker image `buildbot/buildbot-master:v2.4.0` contains Python packages for buildbot `2.3.1`:

```sh
buildbot-console-view==2.3.1
buildbot-grid-view==2.3.1
buildbot-waterfall-view==2.3.1
buildbot-worker==2.3.1
buildbot-www==2.3.1
```

The `buildbot` package itself has version `2019.8.18`, which is not available from PyPI.

Even if `2019.8.18` turns out to be identical to `2.4.0`, this is somewhat problematic: Installing packages into the image with a dependency on buildbot will always uninstall the buildbot version that comes with the official image.

It would be nice if the Dockerfile could install buildbot from PyPI to ensure deterministic builds for those who need to derive their own image.

<details>
  <summary>Output of pip freeze</summary>

```
$ docker run --rm buildbot/buildbot-master:v2.4.0 pip freeze
asn1crypto==0.24.0
attrs==19.1.0
autobahn==19.8.1
Automat==0.7.0
buildbot==2019.8.18
buildbot-console-view==2.3.1
buildbot-grid-view==2.3.1
buildbot-waterfall-view==2.3.1
buildbot-worker==2.3.1
buildbot-www==2.3.1
certifi==2019.6.16
cffi==1.12.3
chardet==3.0.4
constantly==15.1.0
cryptography==2.7
decorator==4.4.0
future==0.17.1
hyperlink==19.0.0
idna==2.8
incremental==17.5.0
Jinja2==2.10.1
MarkupSafe==1.1.1
pbr==5.4.2
psycopg2==2.8.3
pyasn1==0.4.6
pyasn1-modules==0.2.6
pycparser==2.19
PyHamcrest==1.9.0
PyJWT==1.7.1
pyOpenSSL==19.0.0
python-dateutil==2.8.0
PyYAML==5.1.2
requests==2.22.0
service-identity==18.1.0
six==1.12.0
SQLAlchemy==1.3.7
sqlalchemy-migrate==0.12.0
sqlparse==0.3.0
Tempita==0.5.2
Twisted==19.7.0
txaio==18.8.1
txrequests==0.9.6
urllib3==1.25.3
zope.interface==4.6.0
```
</details>

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pkg/buildbot_pkg.py`
Content:
```
1 # This file is part of Buildbot.  Buildbot is free software: you can
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 # Method to add build step taken from here
17 # https://seasonofcode.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy.html
18 import datetime
19 import os
20 import re
21 import subprocess
22 import sys
23 from pkg_resources import parse_version
24 from subprocess import PIPE
25 from subprocess import STDOUT
26 from subprocess import Popen
27 
28 import setuptools.command.build_py
29 import setuptools.command.egg_info
30 from setuptools import setup
31 
32 import distutils.cmd  # isort:skip
33 
34 old_listdir = os.listdir
35 
36 
37 def listdir(path):
38     # patch listdir to avoid looking into node_modules
39     l = old_listdir(path)
40     if "node_modules" in l:
41         l.remove("node_modules")
42     return l
43 os.listdir = listdir
44 
45 
46 def check_output(cmd):
47     """Version of check_output which does not throw error"""
48     popen = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
49     out = popen.communicate()[0].strip()
50     if not isinstance(out, str):
51         out = out.decode(sys.stdout.encoding)
52     return out
53 
54 
55 def gitDescribeToPep440(version):
56     # git describe produce version in the form: v0.9.8-20-gf0f45ca
57     # where 20 is the number of commit since last release, and gf0f45ca is the short commit id preceded by 'g'
58     # we parse this a transform into a pep440 release version 0.9.9.dev20 (increment last digit and add dev before 20)
59 
60     VERSION_MATCH = re.compile(r'(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.post(?P<post>\d+))?(-(?P<dev>\d+))?(-g(?P<commit>.+))?')
61     v = VERSION_MATCH.search(version)
62     if v:
63         major = int(v.group('major'))
64         minor = int(v.group('minor'))
65         patch = int(v.group('patch'))
66         if v.group('dev'):
67             patch += 1
68             dev = int(v.group('dev'))
69             return "{}.{}.{}-dev{}".format(major, minor, patch, dev)
70         if v.group('post'):
71             return "{}.{}.{}.post{}".format(major, minor, patch, v.group('post'))
72         return "{}.{}.{}".format(major, minor, patch)
73 
74     return v
75 
76 
77 def mTimeVersion(init_file):
78     cwd = os.path.dirname(os.path.abspath(init_file))
79     m = 0
80     for root, dirs, files in os.walk(cwd):
81         for f in files:
82             m = max(os.path.getmtime(os.path.join(root, f)), m)
83     d = datetime.datetime.utcfromtimestamp(m)
84     return d.strftime("%Y.%m.%d")
85 
86 
87 def getVersionFromArchiveId(git_archive_id='$Format:%ct %d$'):
88     """ Extract the tag if a source is from git archive.
89 
90         When source is exported via `git archive`, the git_archive_id init value is modified
91         and placeholders are expanded to the "archived" revision:
92 
93             %ct: committer date, UNIX timestamp
94             %d: ref names, like the --decorate option of git-log
95 
96         See man gitattributes(5) and git-log(1) (PRETTY FORMATS) for more details.
97     """
98     # mangle the magic string to make sure it is not replaced by git archive
99     if not git_archive_id.startswith('$For''mat:'):
100         # source was modified by git archive, try to parse the version from
101         # the value of git_archive_id
102 
103         match = re.search(r'tag:\s*v([^,)]+)', git_archive_id)
104         if match:
105             # archived revision is tagged, use the tag
106             return gitDescribeToPep440(match.group(1))
107 
108         # archived revision is not tagged, use the commit date
109         tstamp = git_archive_id.strip().split()[0]
110         d = datetime.datetime.utcfromtimestamp(int(tstamp))
111         return d.strftime('%Y.%m.%d')
112     return None
113 
114 
115 def getVersion(init_file):
116     """
117     Return BUILDBOT_VERSION environment variable, content of VERSION file, git
118     tag or 'latest'
119     """
120 
121     try:
122         return os.environ['BUILDBOT_VERSION']
123     except KeyError:
124         pass
125 
126     try:
127         cwd = os.path.dirname(os.path.abspath(init_file))
128         fn = os.path.join(cwd, 'VERSION')
129         with open(fn) as f:
130             return f.read().strip()
131     except IOError:
132         pass
133 
134     version = getVersionFromArchiveId()
135     if version is not None:
136         return version
137 
138     try:
139         p = Popen(['git', 'describe', '--tags', '--always'], stdout=PIPE, stderr=STDOUT, cwd=cwd)
140         out = p.communicate()[0]
141 
142         if (not p.returncode) and out:
143             v = gitDescribeToPep440(str(out))
144             if v:
145                 return v
146     except OSError:
147         pass
148 
149     try:
150         # if we really can't find the version, we use the date of modification of the most recent file
151         # docker hub builds cannot use git describe
152         return mTimeVersion(init_file)
153     except Exception:
154         # bummer. lets report something
155         return "latest"
156 
157 
158 # JS build strategy:
159 #
160 # Obviously, building javascript with setuptools is not really something supported initially
161 #
162 # The goal of this hack are:
163 # - override the distutils command to insert our js build
164 # - has very small setup.py
165 #
166 # from buildbot_pkg import setup_www
167 #
168 # setup_www(
169 #   ...
170 #    packages=["buildbot_myplugin"]
171 # )
172 #
173 # We need to override the first command done, so that source tree is populated very soon,
174 # as well as version is found from git tree or "VERSION" file
175 #
176 # This supports following setup.py commands:
177 #
178 # - develop, via egg_info
179 # - install, via egg_info
180 # - sdist, via egg_info
181 # - bdist_wheel, via build
182 # This is why we override both egg_info and build, and the first run build
183 # the js.
184 
185 class BuildJsCommand(distutils.cmd.Command):
186     """A custom command to run JS build."""
187 
188     description = 'run JS build'
189     already_run = False
190 
191     def initialize_options(self):
192         """Set default values for options."""
193 
194     def finalize_options(self):
195         """Post-process options."""
196 
197     def run(self):
198         """Run command."""
199         if self.already_run:
200             return
201         package = self.distribution.packages[0]
202         if os.path.exists("gulpfile.js") or os.path.exists("webpack.config.js"):
203             yarn_version = check_output("yarn --version")
204             assert yarn_version != "", "need nodejs and yarn installed in current PATH"
205             yarn_bin = check_output("yarn bin").strip()
206 
207             commands = []
208 
209             commands.append(['yarn', 'install', '--pure-lockfile'])
210 
211             if os.path.exists("gulpfile.js"):
212                 commands.append([os.path.join(yarn_bin, "gulp"), 'prod', '--notests'])
213             elif os.path.exists("webpack.config.js"):
214                 commands.append(['yarn', 'run', 'build'])
215 
216             shell = bool(os.name == 'nt')
217 
218             for command in commands:
219                 self.announce('Running command: {}'.format(str(" ".join(command))),
220                               level=distutils.log.INFO)
221                 subprocess.check_call(command, shell=shell)
222 
223         self.copy_tree(os.path.join(package, 'static'), os.path.join(
224             "build", "lib", package, "static"))
225 
226         with open(os.path.join("build", "lib", package, "VERSION"), "w") as f:
227             f.write(self.distribution.metadata.version)
228 
229         with open(os.path.join(package, "VERSION"), "w") as f:
230             f.write(self.distribution.metadata.version)
231 
232         self.already_run = True
233 
234 
235 class BuildPyCommand(setuptools.command.build_py.build_py):
236     """Custom build command."""
237 
238     def run(self):
239         self.run_command('build_js')
240         super().run()
241 
242 
243 class EggInfoCommand(setuptools.command.egg_info.egg_info):
244     """Custom egginfo command."""
245 
246     def run(self):
247         self.run_command('build_js')
248         super().run()
249 
250 
251 def setup_www_plugin(**kw):
252     package = kw['packages'][0]
253     if 'version' not in kw:
254         kw['version'] = getVersion(os.path.join(package, "__init__.py"))
255 
256     setup(cmdclass=dict(
257         egg_info=EggInfoCommand,
258         build_py=BuildPyCommand,
259         build_js=BuildJsCommand),
260         **kw)
261
```
--- 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/pkg/buildbot_pkg.py b/pkg/buildbot_pkg.py
--- a/pkg/buildbot_pkg.py
+++ b/pkg/buildbot_pkg.py
@@ -199,19 +199,22 @@
         if self.already_run:
             return
         package = self.distribution.packages[0]
-        if os.path.exists("gulpfile.js") or os.path.exists("webpack.config.js"):
-            yarn_version = check_output("yarn --version")
-            assert yarn_version != "", "need nodejs and yarn installed in current PATH"
-            yarn_bin = check_output("yarn bin").strip()
+        if os.path.exists("webpack.config.js"):
 
-            commands = []
+            yarn_program = None
+            for program in ["yarnpkg", "yarn"]:
+                yarn_version = check_output([program, "--version"])
+                if yarn_version != "":
+                    yarn_program = program
 
-            commands.append(['yarn', 'install', '--pure-lockfile'])
+            assert yarn_version is not None, "need nodejs and yarn installed in current PATH"
 
-            if os.path.exists("gulpfile.js"):
-                commands.append([os.path.join(yarn_bin, "gulp"), 'prod', '--notests'])
-            elif os.path.exists("webpack.config.js"):
-                commands.append(['yarn', 'run', 'build'])
+            yarn_bin = check_output([yarn_program, "bin"]).strip()
+
+            commands = [
+                [yarn_program, 'install', '--pure-lockfile'],
+                [yarn_program, 'run', 'build'],
+            ]
 
             shell = bool(os.name == 'nt')

{"golden_diff": "diff --git a/pkg/buildbot_pkg.py b/pkg/buildbot_pkg.py\n--- a/pkg/buildbot_pkg.py\n+++ b/pkg/buildbot_pkg.py\n@@ -199,19 +199,22 @@\n         if self.already_run:\n             return\n         package = self.distribution.packages[0]\n-        if os.path.exists(\"gulpfile.js\") or os.path.exists(\"webpack.config.js\"):\n-            yarn_version = check_output(\"yarn --version\")\n-            assert yarn_version != \"\", \"need nodejs and yarn installed in current PATH\"\n-            yarn_bin = check_output(\"yarn bin\").strip()\n+        if os.path.exists(\"webpack.config.js\"):\n \n-            commands = []\n+            yarn_program = None\n+            for program in [\"yarnpkg\", \"yarn\"]:\n+                yarn_version = check_output([program, \"--version\"])\n+                if yarn_version != \"\":\n+                    yarn_program = program\n \n-            commands.append(['yarn', 'install', '--pure-lockfile'])\n+            assert yarn_version is not None, \"need nodejs and yarn installed in current PATH\"\n \n-            if os.path.exists(\"gulpfile.js\"):\n-                commands.append([os.path.join(yarn_bin, \"gulp\"), 'prod', '--notests'])\n-            elif os.path.exists(\"webpack.config.js\"):\n-                commands.append(['yarn', 'run', 'build'])\n+            yarn_bin = check_output([yarn_program, \"bin\"]).strip()\n+\n+            commands = [\n+                [yarn_program, 'install', '--pure-lockfile'],\n+                [yarn_program, 'run', 'build'],\n+            ]\n \n             shell = bool(os.name == 'nt')\n", "issue": "Docker image for 2.4.0 contains packages from 2.3.1\nThe Docker image `buildbot/buildbot-master:v2.4.0` contains Python packages for buildbot `2.3.1`:\r\n\r\n```sh\r\nbuildbot-console-view==2.3.1\r\nbuildbot-grid-view==2.3.1\r\nbuildbot-waterfall-view==2.3.1\r\nbuildbot-worker==2.3.1\r\nbuildbot-www==2.3.1\r\n```\r\n\r\nThe `buildbot` package itself has version `2019.8.18`, which is not available from PyPI.\r\n\r\nEven if `2019.8.18` turns out to be identical to `2.4.0`, this is somewhat problematic: Installing packages into the image with a dependency on buildbot will always uninstall the buildbot version that comes with the official image.\r\n\r\nIt would be nice if the Dockerfile could install buildbot from PyPI to ensure deterministic builds for those who need to derive their own image.\r\n\r\n<details>\r\n  <summary>Output of pip freeze</summary>\r\n\r\n```\r\n$ docker run --rm buildbot/buildbot-master:v2.4.0 pip freeze\r\nasn1crypto==0.24.0\r\nattrs==19.1.0\r\nautobahn==19.8.1\r\nAutomat==0.7.0\r\nbuildbot==2019.8.18\r\nbuildbot-console-view==2.3.1\r\nbuildbot-grid-view==2.3.1\r\nbuildbot-waterfall-view==2.3.1\r\nbuildbot-worker==2.3.1\r\nbuildbot-www==2.3.1\r\ncertifi==2019.6.16\r\ncffi==1.12.3\r\nchardet==3.0.4\r\nconstantly==15.1.0\r\ncryptography==2.7\r\ndecorator==4.4.0\r\nfuture==0.17.1\r\nhyperlink==19.0.0\r\nidna==2.8\r\nincremental==17.5.0\r\nJinja2==2.10.1\r\nMarkupSafe==1.1.1\r\npbr==5.4.2\r\npsycopg2==2.8.3\r\npyasn1==0.4.6\r\npyasn1-modules==0.2.6\r\npycparser==2.19\r\nPyHamcrest==1.9.0\r\nPyJWT==1.7.1\r\npyOpenSSL==19.0.0\r\npython-dateutil==2.8.0\r\nPyYAML==5.1.2\r\nrequests==2.22.0\r\nservice-identity==18.1.0\r\nsix==1.12.0\r\nSQLAlchemy==1.3.7\r\nsqlalchemy-migrate==0.12.0\r\nsqlparse==0.3.0\r\nTempita==0.5.2\r\nTwisted==19.7.0\r\ntxaio==18.8.1\r\ntxrequests==0.9.6\r\nurllib3==1.25.3\r\nzope.interface==4.6.0\r\n```\r\n</details>\n", "before_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n# Method to add build step taken from here\n# https://seasonofcode.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy.html\nimport datetime\nimport os\nimport re\nimport subprocess\nimport sys\nfrom pkg_resources import parse_version\nfrom subprocess import PIPE\nfrom subprocess import STDOUT\nfrom subprocess import Popen\n\nimport setuptools.command.build_py\nimport setuptools.command.egg_info\nfrom setuptools import setup\n\nimport distutils.cmd  # isort:skip\n\nold_listdir = os.listdir\n\n\ndef listdir(path):\n    # patch listdir to avoid looking into node_modules\n    l = old_listdir(path)\n    if \"node_modules\" in l:\n        l.remove(\"node_modules\")\n    return l\nos.listdir = listdir\n\n\ndef check_output(cmd):\n    \"\"\"Version of check_output which does not throw error\"\"\"\n    popen = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)\n    out = popen.communicate()[0].strip()\n    if not isinstance(out, str):\n        out = out.decode(sys.stdout.encoding)\n    return out\n\n\ndef gitDescribeToPep440(version):\n    # git describe produce version in the form: v0.9.8-20-gf0f45ca\n    # where 20 is the number of commit since last release, and gf0f45ca is the short commit id preceded by 'g'\n    # we parse this a transform into a pep440 release version 0.9.9.dev20 (increment last digit and add dev before 20)\n\n    VERSION_MATCH = re.compile(r'(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.post(?P<post>\\d+))?(-(?P<dev>\\d+))?(-g(?P<commit>.+))?')\n    v = VERSION_MATCH.search(version)\n    if v:\n        major = int(v.group('major'))\n        minor = int(v.group('minor'))\n        patch = int(v.group('patch'))\n        if v.group('dev'):\n            patch += 1\n            dev = int(v.group('dev'))\n            return \"{}.{}.{}-dev{}\".format(major, minor, patch, dev)\n        if v.group('post'):\n            return \"{}.{}.{}.post{}\".format(major, minor, patch, v.group('post'))\n        return \"{}.{}.{}\".format(major, minor, patch)\n\n    return v\n\n\ndef mTimeVersion(init_file):\n    cwd = os.path.dirname(os.path.abspath(init_file))\n    m = 0\n    for root, dirs, files in os.walk(cwd):\n        for f in files:\n            m = max(os.path.getmtime(os.path.join(root, f)), m)\n    d = datetime.datetime.utcfromtimestamp(m)\n    return d.strftime(\"%Y.%m.%d\")\n\n\ndef getVersionFromArchiveId(git_archive_id='$Format:%ct %d$'):\n    \"\"\" Extract the tag if a source is from git archive.\n\n        When source is exported via `git archive`, the git_archive_id init value is modified\n        and placeholders are expanded to the \"archived\" revision:\n\n            %ct: committer date, UNIX timestamp\n            %d: ref names, like the --decorate option of git-log\n\n        See man gitattributes(5) and git-log(1) (PRETTY FORMATS) for more details.\n    \"\"\"\n    # mangle the magic string to make sure it is not replaced by git archive\n    if not git_archive_id.startswith('$For''mat:'):\n        # source was modified by git archive, try to parse the version from\n        # the value of git_archive_id\n\n        match = re.search(r'tag:\\s*v([^,)]+)', git_archive_id)\n        if match:\n            # archived revision is tagged, use the tag\n            return gitDescribeToPep440(match.group(1))\n\n        # archived revision is not tagged, use the commit date\n        tstamp = git_archive_id.strip().split()[0]\n        d = datetime.datetime.utcfromtimestamp(int(tstamp))\n        return d.strftime('%Y.%m.%d')\n    return None\n\n\ndef getVersion(init_file):\n    \"\"\"\n    Return BUILDBOT_VERSION environment variable, content of VERSION file, git\n    tag or 'latest'\n    \"\"\"\n\n    try:\n        return os.environ['BUILDBOT_VERSION']\n    except KeyError:\n        pass\n\n    try:\n        cwd = os.path.dirname(os.path.abspath(init_file))\n        fn = os.path.join(cwd, 'VERSION')\n        with open(fn) as f:\n            return f.read().strip()\n    except IOError:\n        pass\n\n    version = getVersionFromArchiveId()\n    if version is not None:\n        return version\n\n    try:\n        p = Popen(['git', 'describe', '--tags', '--always'], stdout=PIPE, stderr=STDOUT, cwd=cwd)\n        out = p.communicate()[0]\n\n        if (not p.returncode) and out:\n            v = gitDescribeToPep440(str(out))\n            if v:\n                return v\n    except OSError:\n        pass\n\n    try:\n        # if we really can't find the version, we use the date of modification of the most recent file\n        # docker hub builds cannot use git describe\n        return mTimeVersion(init_file)\n    except Exception:\n        # bummer. lets report something\n        return \"latest\"\n\n\n# JS build strategy:\n#\n# Obviously, building javascript with setuptools is not really something supported initially\n#\n# The goal of this hack are:\n# - override the distutils command to insert our js build\n# - has very small setup.py\n#\n# from buildbot_pkg import setup_www\n#\n# setup_www(\n#   ...\n#    packages=[\"buildbot_myplugin\"]\n# )\n#\n# We need to override the first command done, so that source tree is populated very soon,\n# as well as version is found from git tree or \"VERSION\" file\n#\n# This supports following setup.py commands:\n#\n# - develop, via egg_info\n# - install, via egg_info\n# - sdist, via egg_info\n# - bdist_wheel, via build\n# This is why we override both egg_info and build, and the first run build\n# the js.\n\nclass BuildJsCommand(distutils.cmd.Command):\n    \"\"\"A custom command to run JS build.\"\"\"\n\n    description = 'run JS build'\n    already_run = False\n\n    def initialize_options(self):\n        \"\"\"Set default values for options.\"\"\"\n\n    def finalize_options(self):\n        \"\"\"Post-process options.\"\"\"\n\n    def run(self):\n        \"\"\"Run command.\"\"\"\n        if self.already_run:\n            return\n        package = self.distribution.packages[0]\n        if os.path.exists(\"gulpfile.js\") or os.path.exists(\"webpack.config.js\"):\n            yarn_version = check_output(\"yarn --version\")\n            assert yarn_version != \"\", \"need nodejs and yarn installed in current PATH\"\n            yarn_bin = check_output(\"yarn bin\").strip()\n\n            commands = []\n\n            commands.append(['yarn', 'install', '--pure-lockfile'])\n\n            if os.path.exists(\"gulpfile.js\"):\n                commands.append([os.path.join(yarn_bin, \"gulp\"), 'prod', '--notests'])\n            elif os.path.exists(\"webpack.config.js\"):\n                commands.append(['yarn', 'run', 'build'])\n\n            shell = bool(os.name == 'nt')\n\n            for command in commands:\n                self.announce('Running command: {}'.format(str(\" \".join(command))),\n                              level=distutils.log.INFO)\n                subprocess.check_call(command, shell=shell)\n\n        self.copy_tree(os.path.join(package, 'static'), os.path.join(\n            \"build\", \"lib\", package, \"static\"))\n\n        with open(os.path.join(\"build\", \"lib\", package, \"VERSION\"), \"w\") as f:\n            f.write(self.distribution.metadata.version)\n\n        with open(os.path.join(package, \"VERSION\"), \"w\") as f:\n            f.write(self.distribution.metadata.version)\n\n        self.already_run = True\n\n\nclass BuildPyCommand(setuptools.command.build_py.build_py):\n    \"\"\"Custom build command.\"\"\"\n\n    def run(self):\n        self.run_command('build_js')\n        super().run()\n\n\nclass EggInfoCommand(setuptools.command.egg_info.egg_info):\n    \"\"\"Custom egginfo command.\"\"\"\n\n    def run(self):\n        self.run_command('build_js')\n        super().run()\n\n\ndef setup_www_plugin(**kw):\n    package = kw['packages'][0]\n    if 'version' not in kw:\n        kw['version'] = getVersion(os.path.join(package, \"__init__.py\"))\n\n    setup(cmdclass=dict(\n        egg_info=EggInfoCommand,\n        build_py=BuildPyCommand,\n        build_js=BuildJsCommand),\n        **kw)\n", "path": "pkg/buildbot_pkg.py"}], "after_files": [{"content": "# This file is part of Buildbot.  Buildbot is free software: you can\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\n# Method to add build step taken from here\n# https://seasonofcode.com/posts/how-to-add-custom-build-steps-and-commands-to-setuppy.html\nimport datetime\nimport os\nimport re\nimport subprocess\nimport sys\nfrom pkg_resources import parse_version\nfrom subprocess import PIPE\nfrom subprocess import STDOUT\nfrom subprocess import Popen\n\nimport setuptools.command.build_py\nimport setuptools.command.egg_info\nfrom setuptools import setup\n\nimport distutils.cmd  # isort:skip\n\nold_listdir = os.listdir\n\n\ndef listdir(path):\n    # patch listdir to avoid looking into node_modules\n    l = old_listdir(path)\n    if \"node_modules\" in l:\n        l.remove(\"node_modules\")\n    return l\nos.listdir = listdir\n\n\ndef check_output(cmd):\n    \"\"\"Version of check_output which does not throw error\"\"\"\n    popen = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)\n    out = popen.communicate()[0].strip()\n    if not isinstance(out, str):\n        out = out.decode(sys.stdout.encoding)\n    return out\n\n\ndef gitDescribeToPep440(version):\n    # git describe produce version in the form: v0.9.8-20-gf0f45ca\n    # where 20 is the number of commit since last release, and gf0f45ca is the short commit id preceded by 'g'\n    # we parse this a transform into a pep440 release version 0.9.9.dev20 (increment last digit and add dev before 20)\n\n    VERSION_MATCH = re.compile(r'(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.post(?P<post>\\d+))?(-(?P<dev>\\d+))?(-g(?P<commit>.+))?')\n    v = VERSION_MATCH.search(version)\n    if v:\n        major = int(v.group('major'))\n        minor = int(v.group('minor'))\n        patch = int(v.group('patch'))\n        if v.group('dev'):\n            patch += 1\n            dev = int(v.group('dev'))\n            return \"{}.{}.{}-dev{}\".format(major, minor, patch, dev)\n        if v.group('post'):\n            return \"{}.{}.{}.post{}\".format(major, minor, patch, v.group('post'))\n        return \"{}.{}.{}\".format(major, minor, patch)\n\n    return v\n\n\ndef mTimeVersion(init_file):\n    cwd = os.path.dirname(os.path.abspath(init_file))\n    m = 0\n    for root, dirs, files in os.walk(cwd):\n        for f in files:\n            m = max(os.path.getmtime(os.path.join(root, f)), m)\n    d = datetime.datetime.utcfromtimestamp(m)\n    return d.strftime(\"%Y.%m.%d\")\n\n\ndef getVersionFromArchiveId(git_archive_id='$Format:%ct %d$'):\n    \"\"\" Extract the tag if a source is from git archive.\n\n        When source is exported via `git archive`, the git_archive_id init value is modified\n        and placeholders are expanded to the \"archived\" revision:\n\n            %ct: committer date, UNIX timestamp\n            %d: ref names, like the --decorate option of git-log\n\n        See man gitattributes(5) and git-log(1) (PRETTY FORMATS) for more details.\n    \"\"\"\n    # mangle the magic string to make sure it is not replaced by git archive\n    if not git_archive_id.startswith('$For''mat:'):\n        # source was modified by git archive, try to parse the version from\n        # the value of git_archive_id\n\n        match = re.search(r'tag:\\s*v([^,)]+)', git_archive_id)\n        if match:\n            # archived revision is tagged, use the tag\n            return gitDescribeToPep440(match.group(1))\n\n        # archived revision is not tagged, use the commit date\n        tstamp = git_archive_id.strip().split()[0]\n        d = datetime.datetime.utcfromtimestamp(int(tstamp))\n        return d.strftime('%Y.%m.%d')\n    return None\n\n\ndef getVersion(init_file):\n    \"\"\"\n    Return BUILDBOT_VERSION environment variable, content of VERSION file, git\n    tag or 'latest'\n    \"\"\"\n\n    try:\n        return os.environ['BUILDBOT_VERSION']\n    except KeyError:\n        pass\n\n    try:\n        cwd = os.path.dirname(os.path.abspath(init_file))\n        fn = os.path.join(cwd, 'VERSION')\n        with open(fn) as f:\n            return f.read().strip()\n    except IOError:\n        pass\n\n    version = getVersionFromArchiveId()\n    if version is not None:\n        return version\n\n    try:\n        p = Popen(['git', 'describe', '--tags', '--always'], stdout=PIPE, stderr=STDOUT, cwd=cwd)\n        out = p.communicate()[0]\n\n        if (not p.returncode) and out:\n            v = gitDescribeToPep440(str(out))\n            if v:\n                return v\n    except OSError:\n        pass\n\n    try:\n        # if we really can't find the version, we use the date of modification of the most recent file\n        # docker hub builds cannot use git describe\n        return mTimeVersion(init_file)\n    except Exception:\n        # bummer. lets report something\n        return \"latest\"\n\n\n# JS build strategy:\n#\n# Obviously, building javascript with setuptools is not really something supported initially\n#\n# The goal of this hack are:\n# - override the distutils command to insert our js build\n# - has very small setup.py\n#\n# from buildbot_pkg import setup_www\n#\n# setup_www(\n#   ...\n#    packages=[\"buildbot_myplugin\"]\n# )\n#\n# We need to override the first command done, so that source tree is populated very soon,\n# as well as version is found from git tree or \"VERSION\" file\n#\n# This supports following setup.py commands:\n#\n# - develop, via egg_info\n# - install, via egg_info\n# - sdist, via egg_info\n# - bdist_wheel, via build\n# This is why we override both egg_info and build, and the first run build\n# the js.\n\nclass BuildJsCommand(distutils.cmd.Command):\n    \"\"\"A custom command to run JS build.\"\"\"\n\n    description = 'run JS build'\n    already_run = False\n\n    def initialize_options(self):\n        \"\"\"Set default values for options.\"\"\"\n\n    def finalize_options(self):\n        \"\"\"Post-process options.\"\"\"\n\n    def run(self):\n        \"\"\"Run command.\"\"\"\n        if self.already_run:\n            return\n        package = self.distribution.packages[0]\n        if os.path.exists(\"webpack.config.js\"):\n\n            yarn_program = None\n            for program in [\"yarnpkg\", \"yarn\"]:\n                yarn_version = check_output([program, \"--version\"])\n                if yarn_version != \"\":\n                    yarn_program = program\n\n            assert yarn_version is not None, \"need nodejs and yarn installed in current PATH\"\n\n            yarn_bin = check_output([yarn_program, \"bin\"]).strip()\n\n            commands = [\n                [yarn_program, 'install', '--pure-lockfile'],\n                [yarn_program, 'run', 'build'],\n            ]\n\n            shell = bool(os.name == 'nt')\n\n            for command in commands:\n                self.announce('Running command: {}'.format(str(\" \".join(command))),\n                              level=distutils.log.INFO)\n                subprocess.check_call(command, shell=shell)\n\n        self.copy_tree(os.path.join(package, 'static'), os.path.join(\n            \"build\", \"lib\", package, \"static\"))\n\n        with open(os.path.join(\"build\", \"lib\", package, \"VERSION\"), \"w\") as f:\n            f.write(self.distribution.metadata.version)\n\n        with open(os.path.join(package, \"VERSION\"), \"w\") as f:\n            f.write(self.distribution.metadata.version)\n\n        self.already_run = True\n\n\nclass BuildPyCommand(setuptools.command.build_py.build_py):\n    \"\"\"Custom build command.\"\"\"\n\n    def run(self):\n        self.run_command('build_js')\n        super().run()\n\n\nclass EggInfoCommand(setuptools.command.egg_info.egg_info):\n    \"\"\"Custom egginfo command.\"\"\"\n\n    def run(self):\n        self.run_command('build_js')\n        super().run()\n\n\ndef setup_www_plugin(**kw):\n    package = kw['packages'][0]\n    if 'version' not in kw:\n        kw['version'] = getVersion(os.path.join(package, \"__init__.py\"))\n\n    setup(cmdclass=dict(\n        egg_info=EggInfoCommand,\n        build_py=BuildPyCommand,\n        build_js=BuildJsCommand),\n        **kw)\n", "path": "pkg/buildbot_pkg.py"}]}