Skip to content

Commit 84972bc

Browse files
committed
Builder: Detect modified files before build and clean
1 parent a1c6e79 commit 84972bc

3 files changed

Lines changed: 86 additions & 39 deletions

File tree

lbuild/api.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from lbuild.config import ConfigNode
2222
from lbuild.utils import listify, listrify
2323
from lbuild.logger import CallCounter
24+
from lbuild.exception import LbuildApiBuildlogNotFoundException, LbuildApiModifiedFilesException
25+
from pathlib import Path
2426

2527

2628
class Builder:
@@ -121,7 +123,7 @@ def validate(self, modules=None, complete=True):
121123
self.parser.validate_modules(build_modules, complete)
122124
return (build_modules, CallCounter.levels)
123125

124-
def build(self, outpath, modules=None, simulate=False, use_symlinks=False):
126+
def build(self, outpath, modules=None, simulate=False, use_symlinks=False, write_buildlog=True):
125127
"""
126128
Build the given set of modules.
127129
@@ -133,8 +135,45 @@ def build(self, outpath, modules=None, simulate=False, use_symlinks=False):
133135
simulate -- If set to True simulate the build process. In
134136
that case no output will be generated.
135137
"""
138+
buildlogname = self.config.filename + ".log"
139+
try:
140+
self.clean(buildlogname)
141+
except LbuildApiBuildlogNotFoundException:
142+
pass
143+
136144
build_modules = self._filter_modules(modules)
137145
buildlog = BuildLog(outpath)
138146
lbuild.environment.SYMLINK_ON_COPY = use_symlinks
139147
self.parser.build_modules(build_modules, buildlog, simulate=simulate)
148+
if write_buildlog and not simulate:
149+
Path(buildlogname).write_bytes(buildlog.to_xml(to_string=True, path=self.cwd))
140150
return buildlog
151+
152+
def clean(self, buildlog, force=False):
153+
buildlogfile = Path(buildlog)
154+
if not buildlogfile.exists():
155+
raise LbuildApiBuildlogNotFoundException(buildlogfile)
156+
buildlog = BuildLog.from_xml(buildlogfile.read_bytes(), path=self.cwd)
157+
158+
unmodified, modified, missing = buildlog.compare_outpath()
159+
if not force and len(modified):
160+
raise LbuildApiModifiedFilesException(buildlogfile, modified)
161+
162+
removed = []
163+
dirs = set()
164+
for filename in sorted(unmodified + modified + [str(buildlogfile)]):
165+
dirs.add(os.path.dirname(filename))
166+
try:
167+
os.remove(filename)
168+
removed.append(filename)
169+
except OSError:
170+
pass
171+
172+
dirs = sorted(list(dirs), key=lambda d: d.count("/"), reverse=True)
173+
for directory in dirs:
174+
try:
175+
os.removedirs(directory)
176+
except OSError:
177+
pass
178+
179+
return removed

lbuild/exception.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,21 @@ def __init__(self, module, file, conflict): # RepositoryInit
353353
super().__init__(msg)
354354

355355

356+
# =============================== API EXCEPTIONS ==============================
357+
class LbuildApiBuildlogNotFoundException(LbuildException):
358+
def __init__(self, buildlog):
359+
msg = "Buildlog '{}' not found!".format(_hl(_rel(buildlog)))
360+
super().__init__(msg)
361+
self.buildlog = buildlog
362+
363+
class LbuildApiModifiedFilesException(LbuildException):
364+
def __init__(self, buildlog, modified):
365+
msg = ("Buildlog '{}' shows these generated files were modified:\n\n{}"
366+
.format(_hl(_rel(buildlog)), _bp(_rel(m) for m in modified)))
367+
super().__init__(msg)
368+
self.buildlog = buildlog
369+
self.modified = modified
370+
356371
# =========================== REPOSITORY EXCEPTIONS ===========================
357372
class LbuildRepositoryNoNameException(LbuildDumpConfigException):
358373
def __init__(self, parser, repo): # RepositoryInit

lbuild/main.py

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
import traceback
1717
import textwrap
1818

19+
from pathlib import Path
20+
1921
import lbuild.logger
2022
import lbuild.vcs.common
2123
from lbuild.format import format_option_short_description
22-
2324
from lbuild.api import Builder
2425

25-
__version__ = '1.13.0'
26+
__version__ = '1.14.0'
2627

2728

2829
class InitAction:
@@ -315,22 +316,20 @@ def register(self, argument_parser):
315316

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

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

327-
if args.buildlog:
328-
configfilename = args.config
329-
logfilename = configfilename + ".log"
330-
buildlog.log_unsafe("lbuild", "buildlog.xml.in", logfilename)
331-
with open(logfilename, "wb") as logfile:
332-
logfile.write(buildlog.to_xml(to_string=True, path=os.getcwd()))
333-
334333
return ""
335334

336335

@@ -343,38 +342,32 @@ def register(self, argument_parser):
343342
parser.add_argument(
344343
"--buildlog",
345344
dest="buildlog",
346-
default="project.xml.log",
345+
default=next(Path(os.getcwd()).glob("*.xml.log"), "project.xml.log"),
347346
help="Use the given buildlog to identify the files to remove.")
347+
parser.add_argument(
348+
"--force",
349+
dest="force_clean",
350+
action="store_true",
351+
default=False,
352+
help="Remove modified files without error.")
348353
parser.set_defaults(execute_action=self.perform)
349354

350355
@staticmethod
351356
def perform(args, builder):
352-
ostream = []
353-
if os.path.exists(args.buildlog):
354-
with open(args.buildlog, "rb") as logfile:
355-
buildlog = lbuild.buildlog.BuildLog.from_xml(logfile.read(), path=os.getcwd())
356-
else:
357-
builder.load(args.repositories)
358-
buildlog = builder.build(args.path, simulate=True)
359-
360-
dirs = set()
361-
filenames = [op.local_filename_out() for op in buildlog.operations]
362-
for filename in sorted(filenames):
363-
ostream.append("Removing " + filename)
364-
dirs.add(os.path.dirname(filename))
365-
try:
366-
os.remove(filename)
367-
except OSError:
368-
pass
369-
370-
dirs = sorted(list(dirs), key=lambda d: d.count("/"), reverse=True)
371-
for directory in dirs:
372-
try:
373-
os.removedirs(directory)
374-
except OSError:
375-
pass
376-
377-
return "\n".join(ostream)
357+
# builder.load(args.repositories)
358+
# buildlog = builder.build(args.path, simulate=True)
359+
try:
360+
removed = builder.clean(args.buildlog, args.force_clean)
361+
except lbuild.exception.LbuildApiModifiedFilesException as error:
362+
raise lbuild.exception.LbuildException(str(error) +
363+
"\nRun '{}' to remove these files anyways.".format(
364+
lbuild.exception._hl("lbuild clean --force")))
365+
except lbuild.exception.LbuildApiBuildlogNotFoundException as error:
366+
raise lbuild.exception.LbuildException(str(error) +
367+
"\nRun with '{}' or manually delete the generated files.".format(
368+
lbuild.exception._hl("lbuild clean --buildlog path/to/buildlog.xml.log")))
369+
370+
return "\n".join("Removing '{}'".format(os.path.relpath(f)) for f in removed)
378371

379372

380373
class DependenciesAction(ManipulationActionBase):

0 commit comments

Comments
 (0)