diff --git a/lbuild/buildlog.py b/lbuild/buildlog.py index 2b305d7..a5f89d8 100644 --- a/lbuild/buildlog.py +++ b/lbuild/buildlog.py @@ -19,6 +19,7 @@ import lbuild.utils from .exception import LbuildBuildlogOverwritingFileException +from .utils import posixpath LOGGER = logging.getLogger('lbuild.buildlog') @@ -62,7 +63,7 @@ def local_filename_out(self, relative_to=None): @staticmethod def _local_filename(filename, path): localfile = join(relpath(dirname(filename), path), basename(filename)) - return normpath(localfile) + return posixpath(normpath(localfile)) def __repr__(self): return "<{}: {} -> {}>".format(self.module_name, self.filename_in, self.filename_out) @@ -242,16 +243,16 @@ def to_xml(self, path, to_string=True): with self.__lock: outpathnode = lxml.etree.SubElement(rootnode, "outpath") - outpathnode.text = relpath(self.outpath, path) + outpathnode.text = posixpath(relpath(self.outpath, path)) for operation in self.operations: operationnode = lxml.etree.SubElement(rootnode, "operation") modulenode = lxml.etree.SubElement(operationnode, "module") modulenode.text = operation.module_name srcnode = lxml.etree.SubElement(operationnode, "source") - srcnode.text = relpath(operation.filename_in, path) + srcnode.text = posixpath(relpath(operation.filename_in, path)) destnode = lxml.etree.SubElement(operationnode, "destination") - destnode.text = relpath(operation.filename_out, path) + destnode.text = posixpath(relpath(operation.filename_out, path)) if operation.time is not None: timenode = lxml.etree.SubElement(operationnode, "time") diff --git a/lbuild/environment.py b/lbuild/environment.py index 84850ff..58daaf9 100644 --- a/lbuild/environment.py +++ b/lbuild/environment.py @@ -24,6 +24,7 @@ import lbuild.facade as lf import lbuild.exception as le from .parser import Parser +from .utils import posixpath SIMULATE = False SYMLINK_ON_COPY = False @@ -359,7 +360,7 @@ def relcwdoutpath(self, *path): """Relocate given path to the outpath in respect to current working directory""" relative = self.cwdpath("") path = os.path.join(self.cwdoutpath(""), *path) - return os.path.relpath(os.path.abspath(path), os.path.abspath(relative)) + return posixpath(os.path.relpath(os.path.abspath(path), os.path.abspath(relative))) def reloutpath(self, path, relative=None): if relative is None: @@ -367,7 +368,7 @@ def reloutpath(self, path, relative=None): else: relative = os.path.join(self.__outpath, relative) path = os.path.join(self.__outpath, path) - return os.path.relpath(os.path.abspath(path), os.path.abspath(relative)) + return posixpath(os.path.relpath(os.path.abspath(path), os.path.abspath(relative))) def generated_local_files(self, filterfunc=None): """ @@ -382,10 +383,10 @@ def generated_local_files(self, filterfunc=None): filenames = [] operations = self.buildlog.operations_per_module(self.__module.fullname) for operation in operations: - filename = os.path.normpath( + filename = posixpath(os.path.normpath( os.path.join(os.path.relpath(os.path.dirname(operation.filename_out), self.outpath('.')), - os.path.basename(operation.filename_out))) + os.path.basename(operation.filename_out)))) if filterfunc is None or filterfunc(filename): filenames.append(filename) return filenames diff --git a/lbuild/main.py b/lbuild/main.py index cf25e00..c819707 100644 --- a/lbuild/main.py +++ b/lbuild/main.py @@ -22,7 +22,7 @@ from lbuild.api import Builder -__version__ = '1.22.0' +__version__ = '1.23.0' class InitAction: diff --git a/lbuild/option.py b/lbuild/option.py index fa8f64d..73c3b70 100644 --- a/lbuild/option.py +++ b/lbuild/option.py @@ -18,6 +18,7 @@ from .node import BaseNode from .format import ColorWrapper as _cw +from .utils import posixpath import lbuild.exception as le LOGGER = logging.getLogger('lbuild.option') @@ -132,6 +133,9 @@ def _relocate_path(self, path): # relocate path relative to the option filepath to absolute if self._relocate(path): path = os.path.abspath(os.path.join(self._filepath, path)) + elif not os.path.isabs(path): + # relative paths are written into generated files + path = posixpath(path) return path def _validate_path(self, path): diff --git a/lbuild/utils.py b/lbuild/utils.py index c7fa0ef..ab46b7e 100644 --- a/lbuild/utils.py +++ b/lbuild/utils.py @@ -29,6 +29,16 @@ ] +def posixpath(path): + """ + Convert a relative path to forward slashes on every host. + + Paths that are written into generated files must not contain the + backslashes of a Windows path, since they are escape characters there. + """ + return path.replace(os.sep, "/") + + def ignore_files(*files): """ Ignore file and folder names without checking the full path. diff --git a/test/buildlog_test.py b/test/buildlog_test.py index a8ba73d..bf4a0c3 100644 --- a/test/buildlog_test.py +++ b/test/buildlog_test.py @@ -11,7 +11,9 @@ import os import sys +import ntpath import unittest +from unittest import mock # Hack to support the usage of `coverage` sys.path.append(os.path.abspath(".")) @@ -100,6 +102,45 @@ def test_should_create_local_path(self): self.assertEqual("in1", o1.local_filename_in()) self.assertEqual("out1", o1.local_filename_out("test")) + def test_should_create_local_path_with_forward_slashes(self): + # Use the Windows path functions on every host + names = ["join", "relpath", "dirname", "normpath", "basename", "isabs", "abspath"] + with mock.patch.multiple("lbuild.buildlog", **{n: getattr(ntpath, n) for n in names}), \ + mock.patch("os.path", ntpath), mock.patch("os.sep", "\\"): + log = lbuild.buildlog.BuildLog("C:\\project\\out", cwd="C:\\project") + self.module1._filename = "C:\\repo\\m1\\module.lb" + o1 = log.log(self.module1, "sub\\in1", "generated\\nanopb\\out1") + + self.assertEqual("C:\\project\\out\\generated\\nanopb\\out1", o1.filename_out) + self.assertEqual("sub/in1", o1.local_filename_in()) + self.assertEqual("generated/nanopb/out1", o1.local_filename_out()) + self.assertEqual("nanopb/out1", o1.local_filename_out("generated")) + self.assertEqual("out/generated/nanopb/out1", o1.local_filename_out("..")) + self.assertEqual("generated/nanopb/out1", + lbuild.facade.BuildLogOperationFacade(o1).filename) + + def test_should_read_xml_with_native_and_forward_slashes(self): + # Use the Windows path functions on every host + names = ["join", "relpath", "dirname", "normpath", "basename", "isabs", "abspath"] + with mock.patch.multiple("lbuild.buildlog", **{n: getattr(ntpath, n) for n in names}), \ + mock.patch("os.path", ntpath), mock.patch("os.sep", "\\"): + log = lbuild.buildlog.BuildLog("C:\\project", cwd="C:\\project") + self.module1._filename = "C:\\repo\\m1\\module.lb" + log.log(self.module1, "sub\\in1", "generated\\out1") + + xml = log.to_xml(path="C:\\project") + self.assertIn(b".", xml) + self.assertIn(b"../repo/m1/sub/in1", xml) + self.assertIn(b"generated/out1", xml) + + # Build logs written by previous versions contain native paths + for content in [xml, xml.replace(b"/", b"\\").replace(b"<\\", b"