Skip to content

Commit

Permalink
Builder: Detect modified files before build and clean
Browse files Browse the repository at this point in the history
  • Loading branch information
salkinium committed Oct 24, 2019
1 parent a1c6e79 commit 84972bc
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 39 deletions.
41 changes: 40 additions & 1 deletion lbuild/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from lbuild.config import ConfigNode
from lbuild.utils import listify, listrify
from lbuild.logger import CallCounter
from lbuild.exception import LbuildApiBuildlogNotFoundException, LbuildApiModifiedFilesException
from pathlib import Path


class Builder:
Expand Down Expand Up @@ -121,7 +123,7 @@ def validate(self, modules=None, complete=True):
self.parser.validate_modules(build_modules, complete)
return (build_modules, CallCounter.levels)

def build(self, outpath, modules=None, simulate=False, use_symlinks=False):
def build(self, outpath, modules=None, simulate=False, use_symlinks=False, write_buildlog=True):
"""
Build the given set of modules.
Expand All @@ -133,8 +135,45 @@ def build(self, outpath, modules=None, simulate=False, use_symlinks=False):
simulate -- If set to True simulate the build process. In
that case no output will be generated.
"""
buildlogname = self.config.filename + ".log"
try:
self.clean(buildlogname)
except LbuildApiBuildlogNotFoundException:
pass

build_modules = self._filter_modules(modules)
buildlog = BuildLog(outpath)
lbuild.environment.SYMLINK_ON_COPY = use_symlinks
self.parser.build_modules(build_modules, buildlog, simulate=simulate)
if write_buildlog and not simulate:
Path(buildlogname).write_bytes(buildlog.to_xml(to_string=True, path=self.cwd))
return buildlog

def clean(self, buildlog, force=False):
buildlogfile = Path(buildlog)
if not buildlogfile.exists():
raise LbuildApiBuildlogNotFoundException(buildlogfile)
buildlog = BuildLog.from_xml(buildlogfile.read_bytes(), path=self.cwd)

unmodified, modified, missing = buildlog.compare_outpath()
if not force and len(modified):
raise LbuildApiModifiedFilesException(buildlogfile, modified)

removed = []
dirs = set()
for filename in sorted(unmodified + modified + [str(buildlogfile)]):
dirs.add(os.path.dirname(filename))
try:
os.remove(filename)
removed.append(filename)
except OSError:
pass

dirs = sorted(list(dirs), key=lambda d: d.count("/"), reverse=True)
for directory in dirs:
try:
os.removedirs(directory)
except OSError:
pass

return removed
15 changes: 15 additions & 0 deletions lbuild/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,21 @@ def __init__(self, module, file, conflict): # RepositoryInit
super().__init__(msg)


# =============================== API EXCEPTIONS ==============================
class LbuildApiBuildlogNotFoundException(LbuildException):
def __init__(self, buildlog):
msg = "Buildlog '{}' not found!".format(_hl(_rel(buildlog)))
super().__init__(msg)
self.buildlog = buildlog

class LbuildApiModifiedFilesException(LbuildException):
def __init__(self, buildlog, modified):
msg = ("Buildlog '{}' shows these generated files were modified:\n\n{}"
.format(_hl(_rel(buildlog)), _bp(_rel(m) for m in modified)))
super().__init__(msg)
self.buildlog = buildlog
self.modified = modified

# =========================== REPOSITORY EXCEPTIONS ===========================
class LbuildRepositoryNoNameException(LbuildDumpConfigException):
def __init__(self, parser, repo): # RepositoryInit
Expand Down
69 changes: 31 additions & 38 deletions lbuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
import traceback
import textwrap

from pathlib import Path

import lbuild.logger
import lbuild.vcs.common
from lbuild.format import format_option_short_description

from lbuild.api import Builder

__version__ = '1.13.0'
__version__ = '1.14.0'


class InitAction:
Expand Down Expand Up @@ -315,22 +316,20 @@ def register(self, argument_parser):

@staticmethod
def perform(args, builder):
buildlog = builder.build(args.path, args.modules, simulate=args.simulate,
use_symlinks=args.symlink)
try:
buildlog = builder.build(args.path, args.modules, simulate=args.simulate,
use_symlinks=args.symlink, write_buildlog=args.buildlog)
except lbuild.exception.LbuildApiModifiedFilesException as error:
raise lbuild.exception.LbuildException(str(error) +
"\nA build may overwrite these files, run '{}' to remove them anyways.".format(
lbuild.exception._hl("lbuild clean --force")))

if args.simulate:
ostream = []
for operation in buildlog.operations:
ostream.append(operation.local_filename_out())
return "\n".join(sorted(ostream))

if args.buildlog:
configfilename = args.config
logfilename = configfilename + ".log"
buildlog.log_unsafe("lbuild", "buildlog.xml.in", logfilename)
with open(logfilename, "wb") as logfile:
logfile.write(buildlog.to_xml(to_string=True, path=os.getcwd()))

return ""


Expand All @@ -343,38 +342,32 @@ def register(self, argument_parser):
parser.add_argument(
"--buildlog",
dest="buildlog",
default="project.xml.log",
default=next(Path(os.getcwd()).glob("*.xml.log"), "project.xml.log"),
help="Use the given buildlog to identify the files to remove.")
parser.add_argument(
"--force",
dest="force_clean",
action="store_true",
default=False,
help="Remove modified files without error.")
parser.set_defaults(execute_action=self.perform)

@staticmethod
def perform(args, builder):
ostream = []
if os.path.exists(args.buildlog):
with open(args.buildlog, "rb") as logfile:
buildlog = lbuild.buildlog.BuildLog.from_xml(logfile.read(), path=os.getcwd())
else:
builder.load(args.repositories)
buildlog = builder.build(args.path, simulate=True)

dirs = set()
filenames = [op.local_filename_out() for op in buildlog.operations]
for filename in sorted(filenames):
ostream.append("Removing " + filename)
dirs.add(os.path.dirname(filename))
try:
os.remove(filename)
except OSError:
pass

dirs = sorted(list(dirs), key=lambda d: d.count("/"), reverse=True)
for directory in dirs:
try:
os.removedirs(directory)
except OSError:
pass

return "\n".join(ostream)
# builder.load(args.repositories)
# buildlog = builder.build(args.path, simulate=True)
try:
removed = builder.clean(args.buildlog, args.force_clean)
except lbuild.exception.LbuildApiModifiedFilesException as error:
raise lbuild.exception.LbuildException(str(error) +
"\nRun '{}' to remove these files anyways.".format(
lbuild.exception._hl("lbuild clean --force")))
except lbuild.exception.LbuildApiBuildlogNotFoundException as error:
raise lbuild.exception.LbuildException(str(error) +
"\nRun with '{}' or manually delete the generated files.".format(
lbuild.exception._hl("lbuild clean --buildlog path/to/buildlog.xml.log")))

return "\n".join("Removing '{}'".format(os.path.relpath(f)) for f in removed)


class DependenciesAction(ManipulationActionBase):
Expand Down

0 comments on commit 84972bc

Please sign in to comment.