Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: amaranth-lang/amaranth
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 07a3685da8e7
Choose a base ref
...
head repository: amaranth-lang/amaranth
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: ef7a3bcfb1aa
Choose a head ref
  • 1 commit
  • 2 files changed
  • 1 contributor

Commits on Aug 26, 2020

  1. Copy the full SHA
    ef7a3bc View commit details
Showing with 109 additions and 4 deletions.
  1. +108 −4 nmigen/build/run.py
  2. +1 −0 setup.py
112 changes: 108 additions & 4 deletions nmigen/build/run.py
Original file line number Diff line number Diff line change
@@ -7,9 +7,11 @@
import tempfile
import zipfile
import hashlib
import pathlib


__all__ = ["BuildPlan", "BuildProducts", "LocalBuildProducts"]
__all__ = ["BuildPlan", "BuildProducts", "LocalBuildProducts", "RemoteSSHBuildProducts"]



class BuildPlan:
@@ -74,9 +76,10 @@ def execute_local(self, root="build", *, run_script=True):
os.chdir(root)

for filename, content in self.files.items():
filename = os.path.normpath(filename)
# Just to make sure we don't accidentally overwrite anything outside of build root.
assert not filename.startswith("..")
filename = pathlib.Path(filename)
# Forbid parent directory components completely to avoid the possibility
# of writing outside the build root.
assert ".." not in filename.parts
dirname = os.path.dirname(filename)
if dirname:
os.makedirs(dirname, exist_ok=True)
@@ -99,6 +102,82 @@ def execute_local(self, root="build", *, run_script=True):
finally:
os.chdir(cwd)

def execute_remote_ssh(self, *, connect_to = {}, root, run_script=True):
"""
Execute build plan using the remote SSH strategy. Files from the build
plan are transferred via SFTP to the directory ``root`` on a remote
server. If ``run_script`` is ``True``, the ``paramiko`` SSH client will
then run ``{script}.sh``. ``root`` can either be an absolute or
relative (to the login directory) path.
``connect_to`` is a dictionary that holds all input arguments to
``paramiko``'s ``SSHClient.connect``
(`documentation <http://docs.paramiko.org/en/stable/api/client.html#paramiko.client.SSHClient.connect>`_).
At a minimum, the ``hostname`` input argument must be supplied in this
dictionary as the remote server.
Returns :class:`RemoteSSHBuildProducts`.
"""
from paramiko import SSHClient

with SSHClient() as client:
client.load_system_host_keys()
client.connect(**connect_to)

with client.open_sftp() as sftp:
def mkdir_exist_ok(path):
try:
sftp.mkdir(str(path))
except IOError as e:
# mkdir fails if directory exists. This is fine in nmigen.build.
# Reraise errors containing e.errno info.
if e.errno:
raise e

def mkdirs(path):
# Iteratively create parent directories of a file by iterating over all
# parents except for the root ("."). Slicing the parents results in
# TypeError, so skip over the root ("."); this also handles files
# already in the root directory.
for parent in reversed(path.parents):
if parent == pathlib.PurePosixPath("."):
continue
else:
mkdir_exist_ok(parent)

mkdir_exist_ok(root)

sftp.chdir(root)
for filename, content in self.files.items():
filename = pathlib.PurePosixPath(filename)
assert ".." not in filename.parts

mkdirs(filename)

mode = "wt" if isinstance(content, str) else "wb"
with sftp.file(str(filename), mode) as f:
# "b/t" modifier ignored in SFTP.
if mode == "wt":
f.write(content.encode("utf-8"))
else:
f.write(content)

if run_script:
transport = client.get_transport()
channel = transport.open_session()
channel.set_combine_stderr(True)

cmd = "if [ -f ~/.profile ]; then . ~/.profile; fi && cd {} && sh {}.sh".format(root, self.script)
channel.exec_command(cmd)

# Show the output from the server while products are built.
buf = channel.recv(1024)
while buf:
print(buf.decode("utf-8"), end="")
buf = channel.recv(1024)

return RemoteSSHBuildProducts(connect_to, root)

def execute(self):
"""
Execute build plan using the default strategy. Use one of the ``execute_*`` methods
@@ -162,3 +241,28 @@ def get(self, filename, mode="b"):
super().get(filename, mode)
with open(os.path.join(self.__root, filename), "r" + mode) as f:
return f.read()


class RemoteSSHBuildProducts(BuildProducts):
def __init__(self, connect_to, root):
self.__connect_to = connect_to
self.__root = root

def get(self, filename, mode="b"):
super().get(filename, mode)

from paramiko import SSHClient

with SSHClient() as client:
client.load_system_host_keys()
client.connect(**self.__connect_to)

with client.open_sftp() as sftp:
sftp.chdir(self.__root)

with sftp.file(filename, "r" + mode) as f:
# "b/t" modifier ignored in SFTP.
if mode == "t":
return f.read().decode("utf-8")
else:
return f.read()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@ def doc_version():
extras_require={
# this version requirement needs to be synchronized with the one in nmigen.back.verilog!
"builtin-yosys": ["nmigen-yosys>=0.9.*"],
"remote-build": ["paramiko~=2.7"],
},
packages=find_packages(exclude=["*.test*"]),
entry_points={