Skip to content

Commit c83761c

Browse files
committed
Add a new-implementation install script, refactor Provider to be a class rather than function.
1 parent da7d8c2 commit c83761c

File tree

4 files changed

+145
-29
lines changed

4 files changed

+145
-29
lines changed

scripts/create_files

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ majority of the installation process occurs as the unprivileged user.
3030

3131
import os, pickle, sys
3232

33-
if len(sys.argv) != 1:
33+
if len(sys.argv) != 2:
3434
print "This script is not intended to be called manually."
3535
exit(1)
3636

scripts/install

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python
2+
# Software License Agreement (BSD)
3+
#
4+
# @author Mike Purvis <[email protected]>
5+
# @copyright (c) 2015, Clearpath Robotics, Inc., All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that
8+
# the following conditions are met:
9+
# * Redistributions of source code must retain the above copyright notice, this list of conditions and the
10+
# following disclaimer.
11+
# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
12+
# following disclaimer in the documentation and/or other materials provided with the distribution.
13+
# * Neither the name of Clearpath Robotics nor the names of its contributors may be used to endorse or
14+
# promote products derived from this software without specific prior written permission.
15+
#
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
17+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18+
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
19+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20+
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23+
# POSSIBILITY OF SUCH DAMAGE.
24+
25+
"""
26+
The is the main CLI interface to the robot_upstart package.
27+
"""
28+
29+
import argparse
30+
import os
31+
32+
import robot_upstart
33+
from catkin.find_in_workspaces import find_in_workspaces
34+
35+
36+
def get_argument_parser():
37+
p = argparse.ArgumentParser(description=
38+
"""Use this tool to quickly and easily create system startup jobs
39+
which run one or more ROS launch files as a daemonized background
40+
process on your computer. More advanced users will prefer to access
41+
the Python API from their own setup scripts, but this exists as a
42+
simple helper, an example, and a compatibility shim for previous
43+
versions of robot_upstart which were bash-based.""")
44+
45+
p.add_argument("pkgpath", type=str, nargs=1, metavar=("pkg/path",),
46+
help="Package and path to install job launch files from.")
47+
p.add_argument("--job", type=str,
48+
help="Specify job name. If unspecified, will be constructed from package name.")
49+
p.add_argument("--interface", type=str, metavar="ethN",
50+
help="Specify network interface name to associate job with.")
51+
p.add_argument("--user", type=str, metavar="NAME",
52+
help="Specify user to launch job as.")
53+
p.add_argument("--setup", type=str, metavar="path/to/setup.bash",
54+
help="Specify workspace setup file for the job launch context.")
55+
p.add_argument("--rosdistro", type=str, metavar="DISTRO",
56+
help="Specify ROS distro this is for.")
57+
p.add_argument("--master", type=str, metavar="http://MASTER:11311",
58+
help="Specify an alternative ROS_MASTER_URI for the job launch context.")
59+
p.add_argument("--logdir", type=str, metavar="path/to/logs",
60+
help="Specify an a value for ROS_LOG_DIR in the job launch context.")
61+
p.add_argument("--augment",
62+
help="Bypass creating the job, and only copy user files. Assumes the job was previously created.")
63+
return p
64+
65+
66+
def main():
67+
args = get_argument_parser().parse_args()
68+
69+
pkg, pkgpath = args.pkgpath[0].split('/', 1)
70+
job_name = args.job or pkg.split('_', 1)[0]
71+
72+
# Any unspecified arguments are on the args object as None. These are filled
73+
# in by the Job constructor when passed as Nones.
74+
j = robot_upstart.Job(name=job_name, interface=args.interface, user=args.user,
75+
workspace_setup=args.setup, rosdistro=args.rosdistro,
76+
master_uri=args.master, log_path=args.logdir)
77+
78+
found_path = find_in_workspaces(project=pkg, path=pkgpath, first_match_only=True)
79+
if not found_path:
80+
print "Unable to located path %s in package %s. Installation aborted." % (pkgpath, pkg)
81+
82+
if os.path.isfile(found_path[0]):
83+
# Single file, install just that.
84+
j.add(package=pkg, filename=pkgpath)
85+
else:
86+
# Directory found, install everything within.
87+
j.add(package=pkg, glob=os.path.join(pkgpath, "*"))
88+
89+
if args.augment:
90+
j.generate_system_files = False
91+
92+
j.install()
93+
94+
return 0;
95+
96+
97+
if __name__ == "__main__":
98+
exit(main())

src/robot_upstart/job.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class Job(object):
4040
""" Represents a ROS configuration to launch on machine startup. """
4141

4242
def __init__(self, name="ros", interface=None, user=None, workspace_setup=None,
43-
rosdistro=None, master_uri="http://127.0.0.1:11311", log_path="/tmp"):
43+
rosdistro=None, master_uri=None, log_path=None):
4444
"""Construct a new Job definition.
4545
4646
:param name: Name of job to create. Defaults to "ros", but you might
@@ -83,9 +83,13 @@ def __init__(self, name="ros", interface=None, user=None, workspace_setup=None,
8383
# Fall back on current distro if not otherwise specified.
8484
self.rosdistro = rosdistro or os.environ['ROS_DISTRO']
8585

86-
self.master_uri = master_uri
86+
self.master_uri = master_uri or "http://127.0.0.1:11311"
8787

88-
self.log_path = log_path
88+
self.log_path = log_path or "/tmp"
89+
90+
# Override this to false if you want to bypass generating the
91+
# upstart conf file.
92+
self.generate_system_files = True
8993

9094
# Set of files to be installed for the job. This is only launchers
9195
# and other user-specified configs--- nothing related to the system
@@ -128,7 +132,7 @@ def add(self, package=None, filename=None, glob=None):
128132
for path in search_paths:
129133
self.files.extend(glob_files(os.path.join(path, glob)))
130134

131-
def install(self, root="/", sudo="/usr/bin/sudo", provider=providers.upstart):
135+
def install(self, root="/", sudo="/usr/bin/sudo", Provider=providers.Upstart):
132136
""" Install the job definition to the system.
133137
134138
:param root: Override the root to install to, useful for testing.
@@ -143,7 +147,8 @@ def install(self, root="/", sudo="/usr/bin/sudo", provider=providers.upstart):
143147
# This is a recipe of files and their contents which is pickled up and
144148
# passed to a sudo process so that it can create the actual files,
145149
# without needing a ROS workspace or any other environmental setup.
146-
installation_files = provider(root, self)
150+
p = Provider(root, self)
151+
installation_files = p.generate()
147152

148153
create_files_exec = find_in_workspaces(project="robot_upstart",
149154
path="scripts/create_files",

src/robot_upstart/providers.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
# POSSIBILITY OF SUCH DAMAGE.
2323

2424
"""
25-
These functions implement the translation from user-intended behaviours
25+
These classes implement the translation from user-intended behaviours
2626
as specified in the state of the Job class to the system-specific configuration
2727
files. At present, there is only an upstart configuration, but similar providers
2828
could be defined for systemd, supervisor, launchd, or other systems.
@@ -35,30 +35,43 @@
3535
from catkin.find_in_workspaces import find_in_workspaces
3636

3737

38-
def upstart(root, job):
39-
job.job_path = os.path.join(root, "etc/ros", job.rosdistro, job.name + ".d")
38+
class Generic(object):
39+
def __init__(self, root, job):
40+
self.root = root
41+
self.job = job
4042

41-
# Make up list of files to copy to system locations.
42-
installation_files = {}
43-
for filename in job.files:
44-
with open(filename) as f:
45-
dest_filename = os.path.join(job.job_path, os.path.basename(filename))
46-
installation_files[dest_filename] = {"content": f.read()}
4743

48-
# Share a single instance of the empy interpreter. Because it is outputting
49-
# to a StringIO, that object needs to be truncated between templates.
50-
output = StringIO.StringIO()
51-
interpreter = em.Interpreter(output=output, globals=job.__dict__.copy())
44+
class Upstart(Generic):
45+
def generate(self):
46+
self.job.job_path = os.path.join(self.root, "etc/ros",
47+
self.job.rosdistro, self.job.name + ".d")
5248

53-
def do_template(template, output_file, chmod=644):
54-
with open(find_in_workspaces(project="robot_upstart", path=template)[0]) as f:
55-
interpreter.file(f)
56-
installation_files[output_file] = {"content": output.getvalue(), "chmod": chmod}
57-
output.truncate(0)
49+
# Make up list of files to copy to system locations.
50+
installation_files = {}
51+
for filename in self.job.files:
52+
with open(filename) as f:
53+
dest_filename = os.path.join(self.job.job_path, os.path.basename(filename))
54+
installation_files[dest_filename] = {"content": f.read()}
55+
56+
# This is optional to support the old --augment flag where a "job" only adds
57+
# launch files to an existing configuration.
58+
if (self.job.generate_system_files):
59+
# Share a single instance of the empy interpreter.
60+
self.interpreter = em.Interpreter(globals=self.job.__dict__.copy())
5861

59-
do_template("templates/job.conf.em", os.path.join(root, "etc/init", job.name + ".conf"), 755)
60-
do_template("templates/job-start.em", os.path.join(root, "usr/sbin", job.name + "-start"), 755)
61-
do_template("templates/job-stop.em", os.path.join(root, "usr/sbin", job.name + "-stop"))
62-
interpreter.shutdown()
62+
installation_files[os.path.join(self.root, "etc/init", self.job.name + ".conf")] = {
63+
"content": self._fill_template("templates/job.conf.em"), "mode": 644}
64+
installation_files[os.path.join(self.root, "usr/sbin", self.job.name + "-start")] = {
65+
"content": self._fill_template("templates/job-start.em"), "mode": 755}
66+
installation_files[os.path.join(self.root, "usr/sbin", self.job.name + "-stop")] = {
67+
"content": self._fill_template("templates/job-stop.em"), "mode": 755}
68+
self.interpreter.shutdown()
6369

64-
return installation_files
70+
return installation_files
71+
72+
def _fill_template(self, template):
73+
self.interpreter.output = StringIO.StringIO()
74+
self.interpreter.reset()
75+
with open(find_in_workspaces(project="robot_upstart", path=template)[0]) as f:
76+
self.interpreter.file(f)
77+
return self.interpreter.output.getvalue()

0 commit comments

Comments
 (0)