aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/packages/fedora/BUILD30
-rw-r--r--scripts/packages/fedora/bazel.spec6
-rwxr-xr-xscripts/packages/fedora/build_rpm.sh56
-rw-r--r--tools/build_defs/pkg/BUILD18
-rw-r--r--tools/build_defs/pkg/README.md70
-rw-r--r--tools/build_defs/pkg/make_rpm.py228
-rw-r--r--tools/build_defs/pkg/make_rpm_test.py105
-rw-r--r--tools/build_defs/pkg/rpm.bzl109
8 files changed, 545 insertions, 77 deletions
diff --git a/scripts/packages/fedora/BUILD b/scripts/packages/fedora/BUILD
index 36d8690ebf..2ee91d01ab 100644
--- a/scripts/packages/fedora/BUILD
+++ b/scripts/packages/fedora/BUILD
@@ -5,26 +5,18 @@ filegroup(
srcs = glob(["**"]),
)
-genrule(
- name = "bazel-fedora",
- srcs = [
- "bazel.spec",
+load("//tools/build_defs/pkg:rpm.bzl", "pkg_rpm")
+
+pkg_rpm(
+ name = "bazel",
+ architecture = "x86_64",
+ changelog = "//:changelog-file",
+ data = [
+ "//scripts:bash_completion",
"//scripts/packages:bazel",
+ "//scripts/packages:bazel.bazelrc",
"//scripts/packages:bazel-real",
- "//scripts/packages:debian/bazel.bazelrc",
- "//scripts:bash_completion",
- ],
- outs = [
- "bazel-fedora.rpm",
],
- cmd = """
- $(location :build_rpm.sh) \
- $(location :bazel.spec) \
- $(location bazel-fedora.rpm) \
- $(location //scripts/packages:bazel) \
- $(location //scripts/packages:bazel-real) \
- $(location //scripts/packages:debian/bazel.bazelrc) \
- $(location //scripts:bash_completion)
- """,
- tools = ["build_rpm.sh"],
+ spec_file = "bazel.spec",
+ version_file = "//scripts/packages:version.txt",
)
diff --git a/scripts/packages/fedora/bazel.spec b/scripts/packages/fedora/bazel.spec
index 0d69e61e5d..14cabfc5c1 100644
--- a/scripts/packages/fedora/bazel.spec
+++ b/scripts/packages/fedora/bazel.spec
@@ -30,7 +30,7 @@ install -m 755 bazel %{buildroot}%{_bindir}/bazel
install -m 755 bazel-real %{buildroot}%{_bindir}/bazel-real
mkdir -p %{buildroot}%{_sysconfdir}/
install -m 644 bazel.bazelrc %{buildroot}%{_sysconfdir}/bazel.bazelrc
-mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d
+mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d/
install -m 644 bazel-complete.bash %{buildroot}%{_sysconfdir}/bash_completion.d/bazel
%files
@@ -40,4 +40,6 @@ install -m 644 bazel-complete.bash %{buildroot}%{_sysconfdir}/bash_completion.d/
%{_sysconfdir}/bash_completion.d/bazel
%changelog
-# TODO: Include changelog.
+* Fri Jan 27 2017 jcater@google.com - devel-1
+- Initial package version released.
+
diff --git a/scripts/packages/fedora/build_rpm.sh b/scripts/packages/fedora/build_rpm.sh
deleted file mode 100755
index 525c858b13..0000000000
--- a/scripts/packages/fedora/build_rpm.sh
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/bin/sh
-
-# Copyright 2016 The Bazel Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-set -eu
-
-# Build a RPM archive from the Bazel sources.
-
-# Usage: build_rpm.sh spec_file dest_file [other files]
-
-spec_file=$1
-shift
-dest_file=$1
-shift
-
-echo "Building ${dest_file} from ${spec_file}."
-WORK_DIR="${PWD}/bazel-fedora"
-
-# Copy needed sources.
-rm -rf ${WORK_DIR}
-mkdir -p ${WORK_DIR}/SOURCES
-mkdir -p ${WORK_DIR}/BUILD
-cp $spec_file ${WORK_DIR}
-for i in "$@"; do
- cp $i $WORK_DIR/BUILD
-done
-
-# Build the RPM.
-rpmbuild \
- --define "_topdir ${WORK_DIR}" \
- --define "_tmppath /tmp" \
- -bb ${spec_file} > rpmbuild.log 2>&1
-if [ $? -ne 0 ]; then
- err=$?
- echo "Error in rpmbuild:"
- cat rpmbuild.log
- exit $err
-fi
-out_file=$(grep '^Wrote:' rpmbuild.log | cut -d ' ' -f 2)
-
-# Copy output back to the destination.
-cp $out_file $dest_file
-echo "Created $dest_file"
-
diff --git a/tools/build_defs/pkg/BUILD b/tools/build_defs/pkg/BUILD
index 1bc4786034..266a2050ba 100644
--- a/tools/build_defs/pkg/BUILD
+++ b/tools/build_defs/pkg/BUILD
@@ -66,6 +66,24 @@ py_binary(
],
)
+# Used by pkg_rpm in rpm.bzl.
+py_binary(
+ name = "make_rpm",
+ srcs = ["make_rpm.py"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "@bazel_tools//third_party/py/gflags",
+ ],
+)
+
+py_test(
+ name = "make_rpm_test",
+ srcs = ["make_rpm_test.py"],
+ deps = [
+ ":make_rpm",
+ ],
+)
+
# tests
load("//tools/build_defs/pkg:pkg.bzl", "pkg_deb", "pkg_tar")
diff --git a/tools/build_defs/pkg/README.md b/tools/build_defs/pkg/README.md
index 8fda66636f..4369777cd7 100644
--- a/tools/build_defs/pkg/README.md
+++ b/tools/build_defs/pkg/README.md
@@ -5,6 +5,7 @@
<ul>
<li><a href="#pkg_tar">pkg_tar</a></li>
<li><a href="#pkg_deb">pkg_deb</a></li>
+ <li><a href="#pkg_rpm">pkg_rpm</a></li>
</ul>
</div>
@@ -386,3 +387,72 @@ for more details on this.
</tbody>
</tbody>
</table>
+
+<a name="pkg_rpm"></a>
+### pkg_rpm
+
+```python
+pkg_rpm(name, spec_file, architecture, version, version_file, changelog, data)
+```
+
+Create an RPM package. See <a
+href="http://rpm.org/documentation.html">http://rpm.org/documentation.html</a>
+for more details on this.
+
+<table class="table table-condensed table-bordered table-params">
+ <colgroup>
+ <col class="col-param" />
+ <col class="param-description" />
+ </colgroup>
+ <thead>
+ <tr>
+ <th colspan="2">Attributes</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>name</code></td>
+ <td>
+ <code>Name, required</code>
+ <p>A unique name for this rule. Used to name the output package.</p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>spec_file</code></td>
+ <td>
+ <code>File, required</code>
+ <p>The RPM specification file used to generate the package.</p>
+ <p>
+ See <a href="http://rpm.org/max-rpm-snapshot/s1-rpm-build-creating-spec-file.html">http://rpm.org/max-rpm-snapshot/s1-rpm-build-creating-spec-file.html</a>.
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>architecture</code></td>
+ <td>
+ <code>String, default to 'all'</code>
+ <p>The architecture that this package target.</p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>version</code>, <code>version_file</code></td>
+ <td>
+ <code>String or File, required</code>
+ <p>
+ The package version provided either inline (with <code>version</code>)
+ or from a file (with <code>version_file</code>).
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td><code>data</code></td>
+ <td>
+ <code>Files, required</code>
+ <p>
+ Files to include in the generated package.
+ </p>
+ </td>
+ </tr>
+ </tbody>
+ </tbody>
+</table>
diff --git a/tools/build_defs/pkg/make_rpm.py b/tools/build_defs/pkg/make_rpm.py
new file mode 100644
index 0000000000..9f0f40a23b
--- /dev/null
+++ b/tools/build_defs/pkg/make_rpm.py
@@ -0,0 +1,228 @@
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""A simple cross-platform helper to create an RPM package."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import contextlib
+import fileinput
+import os
+import re
+import shutil
+import subprocess
+import sys
+from tempfile import mkdtemp
+
+from third_party.py import gflags
+
+gflags.DEFINE_string('name', '', 'The name of the software being packaged.')
+gflags.DEFINE_string('version', '',
+ 'The version of the software being packaged.')
+gflags.DEFINE_string('arch', '',
+ 'The CPU architecture of the software being packaged.')
+
+gflags.DEFINE_string('spec_file', '',
+ 'The file containing the RPM specification.')
+gflags.DEFINE_string('out_file', '',
+ 'The destination to save the resulting RPM file to.')
+
+
+# Setup to safely create a temporary directory and clean it up when done.
+@contextlib.contextmanager
+def Cd(newdir, cleanup=lambda: True):
+ """Change the current working directory.
+
+ This will run the provided cleanup function when the context exits and the
+ previous working directory is restored.
+
+ Args:
+ newdir: The directory to change to. This must already exist.
+ cleanup: An optional cleanup function to be executed when the context exits.
+
+ Yields:
+ Nothing.
+ """
+
+ prevdir = os.getcwd()
+ os.chdir(os.path.expanduser(newdir))
+ try:
+ yield
+ finally:
+ os.chdir(prevdir)
+ cleanup()
+
+
+@contextlib.contextmanager
+def Tempdir():
+ """Create a new temporary directory and change to it.
+
+ The temporary directory will be removed when the context exits.
+
+ Yields:
+ The full path of the temporary directory.
+ """
+
+ dirpath = mkdtemp()
+
+ def Cleanup():
+ shutil.rmtree(dirpath)
+
+ with Cd(dirpath, Cleanup):
+ yield dirpath
+
+
+def GetFlagValue(flagvalue, strip=True):
+ if flagvalue:
+ if flagvalue[0] == '@':
+ with open(flagvalue[1:], 'r') as f:
+ flagvalue = f.read()
+ if strip:
+ return flagvalue.strip()
+ return flagvalue
+
+
+WROTE_FILE_RE = re.compile(r'Wrote: (?P<rpm_path>.+)', re.MULTILINE)
+
+
+def FindOutputFile(log):
+ """Find the written file from the log information."""
+
+ m = WROTE_FILE_RE.search(log)
+ if m:
+ return m.group('rpm_path')
+ return None
+
+
+def CopyAndRewrite(input_file, output_file, replacements=None):
+ """Copies the given file and optionally rewrites with replacements.
+
+ Args:
+ input_file: The file to copy.
+ output_file: The file to write to.
+ replacements: A dictionary of replacements.
+ Keys are prefixes scan for, values are the replacements to write after
+ the prefix.
+ """
+ with open(output_file, 'w') as output:
+ for line in fileinput.input(input_file):
+ if replacements:
+ for prefix, text in replacements.iteritems():
+ if line.startswith(prefix):
+ line = prefix + ' ' + text + '\n'
+ break
+ output.write(line)
+
+
+class RpmBuilder(object):
+ """A helper class to manage building the RPM file."""
+
+ SOURCE_DIR = 'SOURCES'
+ BUILD_DIR = 'BUILD'
+ TEMP_DIR = 'TMP'
+ DIRS = [SOURCE_DIR, BUILD_DIR, TEMP_DIR]
+
+ def __init__(self, name, version, arch):
+ self.name = name
+ self.version = GetFlagValue(version)
+ self.arch = arch
+ self.files = []
+ self.rpm_path = None
+
+ def AddFiles(self, files):
+ """Add a set of files to the current RPM."""
+ self.files += files
+
+ def SetupWorkdir(self, spec_file, original_dir):
+ """Create the needed structure in the workdir."""
+
+ # Create directory structure.
+ for name in RpmBuilder.DIRS:
+ if not os.path.exists(name):
+ os.makedirs(name, 0777)
+
+ shutil.copy(os.path.join(original_dir, spec_file), os.getcwd())
+
+ # Copy the files.
+ for f in self.files:
+ shutil.copy(os.path.join(original_dir, f), RpmBuilder.BUILD_DIR)
+
+ # Copy the spec file, updating with the correct version.
+ spec_origin = os.path.join(original_dir, spec_file)
+ self.spec_file = os.path.basename(spec_file)
+ replacements = {}
+ if self.version:
+ replacements['Version:'] = self.version
+ CopyAndRewrite(spec_origin, self.spec_file, replacements)
+
+ def CallRpmBuild(self, dirname):
+ """Call rpmbuild with the correct arguments."""
+
+ args = [
+ 'rpmbuild',
+ '--define',
+ '_topdir %s' % dirname,
+ '--define',
+ '_tmppath %s/TMP' % dirname,
+ '--bb',
+ self.spec_file,
+ ]
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ output = p.communicate()[0]
+
+ if p.returncode == 0:
+ # Find the created file.
+ self.rpm_path = FindOutputFile(output)
+
+ if p.returncode != 0 or not self.rpm_path:
+ print('Error calling rpmbuild:')
+ print(output)
+
+ # Return the status.
+ return p.returncode
+
+ def SaveResult(self, out_file):
+ """Save the result RPM out of the temporary working directory."""
+
+ if self.rpm_path:
+ shutil.copy(self.rpm_path, out_file)
+ print('Saved RPM file to %s' % out_file)
+ else:
+ print('No RPM file created.')
+
+ def Build(self, spec_file, out_file):
+ """Build the RPM described by the spec_file."""
+ print('Building RPM for %s at %s' % (self.name, out_file))
+
+ original_dir = os.getcwd()
+ spec_file = os.path.join(original_dir, spec_file)
+ out_file = os.path.join(original_dir, out_file)
+ with Tempdir() as dirname:
+ self.SetupWorkdir(spec_file, original_dir)
+ status = self.CallRpmBuild(dirname)
+ self.SaveResult(out_file)
+
+ return status
+
+
+def main(argv=()):
+ builder = RpmBuilder(FLAGS.name, FLAGS.version, FLAGS.arch)
+ builder.AddFiles(argv[1:])
+ return builder.Build(FLAGS.spec_file, FLAGS.out_file)
+
+
+if __name__ == '__main__':
+ FLAGS = gflags.FLAGS
+ main(FLAGS(sys.argv))
diff --git a/tools/build_defs/pkg/make_rpm_test.py b/tools/build_defs/pkg/make_rpm_test.py
new file mode 100644
index 0000000000..f8f771038a
--- /dev/null
+++ b/tools/build_defs/pkg/make_rpm_test.py
@@ -0,0 +1,105 @@
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for make_rpm."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import unittest
+
+from tools.build_defs.pkg import make_rpm
+
+
+def WriteFile(filename, *contents):
+ with open(filename, 'w') as text_file:
+ text_file.write('\n'.join(contents))
+
+
+def DirExists(dirname):
+ return os.path.exists(dirname) and os.path.isdir(dirname)
+
+
+def FileExists(filename):
+ return os.path.exists(filename) and not os.path.isdir(filename)
+
+
+def FileContents(filename):
+ with open(filename, 'r') as text_file:
+ return [s.strip() for s in text_file.readlines()]
+
+
+class MakeRpmTest(unittest.TestCase):
+
+ def testFindOutputFile(self):
+ log = """
+ Lots of data.
+ Wrote: /path/to/file/here.rpm
+ More data present.
+ """
+
+ result = make_rpm.FindOutputFile(log)
+ self.assertEquals('/path/to/file/here.rpm', result)
+
+ def testFindOutputFile_missing(self):
+ log = """
+ Lots of data.
+ More data present.
+ """
+
+ result = make_rpm.FindOutputFile(log)
+ self.assertEquals(None, result)
+
+ def testCopyAndRewrite(self):
+ with make_rpm.Tempdir():
+ WriteFile('test.txt', 'Some: data1', 'Other: data2', 'More: data3')
+ make_rpm.CopyAndRewrite('test.txt', 'out.txt', {
+ 'Some:': 'data1a',
+ 'More:': 'data3a',
+ })
+
+ self.assertTrue(FileExists('out.txt'))
+ self.assertItemsEqual(['Some: data1a', 'Other: data2', 'More: data3a'],
+ FileContents('out.txt'))
+
+ def testSetupWorkdir(self):
+ builder = make_rpm.RpmBuilder('test', '1.0', 'x86')
+ with make_rpm.Tempdir() as outer:
+ # Create spec_file, test files.
+ WriteFile('test.spec', 'Name: test', 'Version: 0.1', 'Summary: test data')
+ WriteFile('file1.txt', 'Hello')
+ WriteFile('file2.txt', 'Goodbye')
+ builder.AddFiles(['file1.txt', 'file2.txt'])
+
+ with make_rpm.Tempdir():
+ # Call RpmBuilder.
+ builder.SetupWorkdir('test.spec', outer)
+
+ # Make sure files exist.
+ self.assertTrue(DirExists('SOURCES'))
+ self.assertTrue(DirExists('BUILD'))
+ self.assertTrue(DirExists('TMP'))
+ self.assertTrue(FileExists('test.spec'))
+ self.assertItemsEqual(
+ ['Name: test', 'Version: 1.0', 'Summary: test data'],
+ FileContents('test.spec'))
+ self.assertTrue(FileExists('BUILD/file1.txt'))
+ self.assertItemsEqual(['Hello'], FileContents('BUILD/file1.txt'))
+ self.assertTrue(FileExists('BUILD/file2.txt'))
+ self.assertItemsEqual(['Goodbye'], FileContents('BUILD/file2.txt'))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/build_defs/pkg/rpm.bzl b/tools/build_defs/pkg/rpm.bzl
new file mode 100644
index 0000000000..2322dfae77
--- /dev/null
+++ b/tools/build_defs/pkg/rpm.bzl
@@ -0,0 +1,109 @@
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Rules to create RPM archives."""
+
+rpm_filetype = [".rpm"]
+spec_filetype = [".spec"]
+
+def _pkg_rpm_impl(ctx):
+ """Implements to pkg_rpm rule."""
+
+ files = []
+ args = ["--name=" + ctx.label.name]
+
+ # Version can be specified by a file or inlined
+ if ctx.attr.version_file:
+ if ctx.attr.version:
+ fail("Both version and version_file attributes were specified")
+ args += ["--version=@" + ctx.file.version_file.path]
+ files += [ctx.file.version_file]
+ elif ctx.attr.version:
+ args += ["--version=" + ctx.attr.version]
+ else:
+ fail("Neither version_file nor version attribute was specified")
+
+ if ctx.attr.architecture:
+ args += ["--arch=" + ctx.attr.architecture]
+
+ if ctx.attr.spec_file:
+ args += ["--spec_file=" + ctx.file.spec_file.path]
+ files += [ctx.file.spec_file]
+ else:
+ fail("spec_file was not specified")
+
+ args += ["--out_file=" + ctx.outputs.rpm.path]
+
+ # Add data files.
+ files += [ctx.file.changelog] + ctx.files.data
+ args += [ctx.file.changelog.path]
+ for f in ctx.files.data:
+ args += [f.path]
+
+ # Call the generator script.
+ # TODO(katre): Generate a source RPM.
+ ctx.action(
+ executable = ctx.executable._make_rpm,
+ arguments = args,
+ inputs = files,
+ outputs = [ctx.outputs.rpm],
+ mnemonic = "MakeRpm")
+
+ # Link the RPM to the expected output name.
+ ctx.action(
+ command = "ln -s %s %s" % (ctx.outputs.rpm.basename, ctx.outputs.out.path),
+ inputs = [ctx.outputs.rpm],
+ outputs = [ctx.outputs.out])
+
+# Define the rule.
+pkg_rpm = rule(
+ implementation = _pkg_rpm_impl,
+ attrs = {
+ "spec_file" : attr.label(mandatory=True, allow_files=spec_filetype, single_file=True),
+ "architecture": attr.string(default="all"),
+ "version_file": attr.label(allow_files=True, single_file=True),
+ "version": attr.string(),
+ "changelog" : attr.label(mandatory=True, allow_files=True, single_file=True),
+ "data": attr.label_list(mandatory=True, allow_files=True),
+
+ # Implicit dependencies.
+ "_make_rpm": attr.label(
+ default=Label("//tools/build_defs/pkg:make_rpm"),
+ cfg="host",
+ executable=True,
+ allow_files=True),
+ },
+ outputs = {
+ "out": "%{name}.rpm",
+ "rpm": "%{name}-%{architecture}.rpm",
+ },
+ executable = False)
+"""Creates an RPM format package from the data files.
+
+This runs rpmbuild (and requires it to be installed beforehand) to generate
+an RPM package based on the spec_file and data attributes.
+
+Args:
+ spec_file: The RPM spec file to use. If the version or version_file
+ attributes are provided, the Version in the spec will be overwritten.
+ Any Sources listed in the spec file must be provided as data dependencies.
+ version: The version of the package to generate. This will overwrite any
+ Version provided in the spec file. Only specify one of version and
+ version_file.
+ version_file: A file containing the version of the package to generate. This
+ will overwrite any Version provided in the spec file. Only specify one of
+ version and version_file.
+ changelog: A changelog file to include. This will not be written to the spec
+ file, which should only list changes to the packaging, not the software itself.
+ data: List all files to be included in the package here.
+"""