| # 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.items(): |
| 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, 0o777) |
| |
| 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)) |