Use LLDB to debug a Swift macro?

I'm trying to get a macro working, but for some reason a variable isn't getting an answer from FileManager.default.contentsOfDirectory.

Is there anyway to step through the expansion with lldb?

Macro runs in sandbox, so it cannot access files on your host.

I find it’s convenient to debug macro code by showing variable value in compile error (or warning).

This is dismaying. I'd love to be able to allow access to my source files.

I find it’s convenient to debug macro code by showing variable value in compile error (or warning).
[/quote]

Yeah, I was doing things like this, which led to this question.

Thanks.

It can access code by importing module, but not through file system. For example, if you have some config data, you can put them in a module so that macro can access them.

If the macro is a standalone executable you should be able to attach to it by asking lldb to wait for the process:

lldb -n my_macro_binary -w

if it runs as a compiler plugin dylib you'll need to attach LLDB to the Swift compiler instead and set a breakpoint inside the macro.

1 Like

Macros aren't supposed to have access to the outside world and must be sandboxed and run in a self-contained environment for predictable behavior. Access to the file system makes their execution non-deterministic and breaks build caching.

1 Like

Wouldn't using a tool like gyb also break build caching?

If GYB reads an unrelated file in the process of its template expansion, then the answer is "yes". I don't know of such cases though, unless you'd like to point some of those out?

I just used this to build my project, worked fine.

I did have to turn Sandboxing off, would that work for macros?

import Foundation
import SwiftUI


%{
    import os
    import re
    files = os.listdir("Sandbox")
    players = []
    for play in files:
            nameMatch = re.match("(.+Play).swift", play)
            if nameMatch != None:
                players.append(nameMatch.group(1))
            final = ('[\n' + ',\n'.join([f'\tAnyView({x}())' for x in players]) + '\n]')
}%

var screens: [AnyView] = ${final}

You sure can create a GYB file or a macro that violate build system's expectations, but it doesn't mean you should. In fact, you should avoid that as it would break caching and incremental builds. The build system expects that same sources with the same toolchain and the same SDK will produce the same output. Compile-time side effects violate these assumptions.

1 Like

I guess there's way to use this to shoot yourself in the foot, but I'm literally just using gyb to do the edits I would otherwise be doing by hand. If every single file were a gyb, well I guess you could have a lot of cache misses if file times are used to invalidate cache lines. But no surprise there.

Cache misses are not the issue per se, but the fact that such GYB files or macros don't explicitly declare dependencies on files that they actually depend on. So you'll modify a file being read in template expansion, but the build system will ignore that modification as it's not tracking that file, thus not rebuilding relevant code. This makes incremental builds basically invalid, you'll have to clean your build directory every time to get valid output.

2 Likes

I'm not sure I am following you. The gyb run happens, unconditionally, before compilation. The file it produces is in the project directory when compilation starts and is a standard .swift file. The worst case for my naive code is that that file gets rebuilt every time, which is a tiny cost in my small project. I don't see why it'd be ignored.

WRT to a macro, the way I was designing it was to fit in with the strictures for macros, i.e. don't modify extant code, just insert. All I'm trying to do is generate an array of wrapped calls to code in my project directory so I don't have to type its name repeatedly.