Attempt at personal Swift Package Manager starter template with init script - any tips?

So I went to create a new Package today. started with the swift package init tool, started tweaking, remembered the ping on this thread on the current templates, thought even if they do fix it, what I want and what they build will probably never perfectly line up...

Made myself my own little starter repo...

GitHub - carlynorama/TemplatePackageToolLibrary

Hashed together a setup.swift script (shell executable) that I could use to name replace (and some other clean up). I can run the copy I have to clone the repo anew, or clone the repo first and run its copy.

It's a hack that I need to clean up, but

  • what do other folks do to jumpstart their projects?
  • any thoughts on how I could make the script be less totally Windows hostile?
  • I don't usually "script" in swift, any one pagers that people want to share for best practice ideas?

Since your template is for a command-line tool, recall that they require no package if they have no package dependencies:

swift script.swift

or an executable text file:

`#/usr/bin/env swift

print("Hello")

With package dependencies, consider clutch [1], which can manage a "nest" package of scripts with common dependencies.

The nest is a regular package (for build and git purposes), but the script files can be anywhere on your system. Script binaries are rebuilt if needed when run, and you can initialize new scripts with existing priors or templates. Clutch works with most any regular-looking package and manages multiple nests. You can have nests for builds, media tooling, code/analysis, document processing, etc.

[1] https://github.com/swift-nest/clutch

1 Like

Interesting!

I gave it a go, and had to correct some code to get the script to build and run:
lines 85 and 86:

print(URL(filePath: target))
newPrefix = URL(filePath: target).lastPathComponent

to

print(URL(fileURLWithPath: target))newPrefix = URL(fileURLWithPath: target).lastPathComponent

and line 346 tmp.append(component: newName) to tmp.appendingPathComponent(newName)

but are running into an error:

./TemplatePackageToolLibrary/setup.swift all
TemplatePackageToolLibrary/setup.swift:346:13: warning: result of call to 'appendingPathComponent' is unused
        tmp.appendingPathComponent(newName)
            ^                     ~~~~~~~~~
TemplatePackageToolLibrary/setup.swift:345:13: warning: variable 'tmp' was never mutated; consider changing to 'let' constant
        var tmp = srcURL.deletingLastPathComponent()
        ~~~ ^
        let
w00t! A New Project!
/usr/local/bin/git

Script: TemplatePackageToolLibrary/setup.swift
Please enter new package name:
FooBar
Create subfolder with this name? (alternative is to use this directory)
y
fetching repo...
Cloning into 'FooBar'...
remote: Enumerating objects: 51, done.        
remote: Counting objects: 100% (51/51), done.        
remote: Compressing objects: 100% (35/35), done.        
remote: Total 51 (delta 22), reused 40 (delta 13), pack-reused 0        
Receiving objects: 100% (51/51), 16.87 KiB | 2.41 MiB/s, done.
Resolving deltas: 100% (22/22), done.
FooBar in FooBar
setup/setup.swift:137: Fatal error: name not full replaced due to Error Domain=NSCocoaErrorDomain Code=516 "A file with the same name already exists."
Stack dump:
0.      Program arguments: /usr/libexec/swift/bin/swift-frontend -frontend -interpret TemplatePackageToolLibrary/setup.swift -disable-objc-interop -color-diagnostics -new-driver-path /usr/libexec/swift/bin/swift-driver -empty-abi-descriptor -resource-dir /usr/libexec/swift/lib/swift -module-name setup -plugin-path /usr/libexec/swift/lib/swift/host/plugins -plugin-path /usr/libexec/swift/local/lib/swift/host/plugins -- all
1.      Swift version 5.10 (swift-5.10-RELEASE)
2.      Compiling with the current language version
3.      While running user code "TemplatePackageToolLibrary/setup.swift"
 #0 0x000055d394ad7b03 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/usr/libexec/swift/bin/swift-frontend+0x616eb03)
 #1 0x000055d394ad5a8e llvm::sys::RunSignalHandlers() (/usr/libexec/swift/bin/swift-frontend+0x616ca8e)
 #2 0x000055d394ad7e7f SignalHandler(int) (/usr/libexec/swift/bin/swift-frontend+0x616ee7f)
 #3 0x00007f261abf5420 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x14420)
 #4 0x00007f261a6d255f $ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_SSAHSus6UInt32VtF (/usr/libexec/swift/lib/swift/linux/libswiftCore.so+0x16455f)
 #5 0x00007f2616f55eea 
 #6 0x000055d38f73e88f llvm::orc::runAsMain(int (*)(int, char**), llvm::ArrayRef<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, llvm::Optional<llvm::StringRef>) (/usr/libexec/swift/bin/swift-frontend+0xdd588f)
 #7 0x000055d38f622608 swift::SwiftJIT::runMain(llvm::ArrayRef<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >) (/usr/libexec/swift/bin/swift-frontend+0xcb9608)
 #8 0x000055d38f620413 swift::RunImmediately(swift::CompilerInstance&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, swift::IRGenOptions const&, swift::SILOptions const&, std::unique_ptr<swift::SILModule, std::default_delete<swift::SILModule> >&&) (/usr/libexec/swift/bin/swift-frontend+0xcb7413)
 #9 0x000055d38f5cc636 processCommandLineAndRunImmediately(swift::CompilerInstance&, std::unique_ptr<swift::SILModule, std::default_delete<swift::SILModule> >&&, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::FrontendObserver*, int&) (/usr/libexec/swift/bin/swift-frontend+0xc63636)
#10 0x000055d38f5c7886 performCompileStepsPostSILGen(swift::CompilerInstance&, std::unique_ptr<swift::SILModule, std::default_delete<swift::SILModule> >, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, int&, swift::FrontendObserver*) (/usr/libexec/swift/bin/swift-frontend+0xc5e886)
#11 0x000055d38f5c691b swift::performCompileStepsPostSema(swift::CompilerInstance&, int&, swift::FrontendObserver*) (/usr/libexec/swift/bin/swift-frontend+0xc5d91b)
#12 0x000055d38f5d9bda withSemanticAnalysis(swift::CompilerInstance&, swift::FrontendObserver*, llvm::function_ref<bool (swift::CompilerInstance&)>, bool) (/usr/libexec/swift/bin/swift-frontend+0xc70bda)
#13 0x000055d38f5cac88 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) (/usr/libexec/swift/bin/swift-frontend+0xc61c88)
#14 0x000055d38f5c86fd swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) (/usr/libexec/swift/bin/swift-frontend+0xc5f6fd)
#15 0x000055d38f4626cf swift::mainEntry(int, char const**) (/usr/libexec/swift/bin/swift-frontend+0xaf96cf)
#16 0x00007f2619112083 __libc_start_main /build/glibc-wuryBv/glibc-2.31/csu/../csu/libc-start.c:342:3
#17 0x000055d38f46121e _start (/usr/libexec/swift/bin/swift-frontend+0xaf821e)

💣 Program crashed: Illegal instruction at 0x00007f261a6d255f

Thread 0 "swift-frontend" crashed:

0 0x00007f261a6d255f _assertionFailure(_:_:file:line:flags:) + 351 in libswiftCore.so
1 0x00007f2616f55eea

Backtrace took 0.15s

Illegal instruction (core dumped)

Apparantly the script wants to rename 'FooBar' to 'FooBar'.

Am I missing some instruction?

That's exactly what the setup.swift uses! I'm really enjoying it.

I've been putting off using Swift this way for anything load bearing, so all tips welcome. I still resort to my good old .sh and .py friends as a first thought.

Nest looks really interesting for future projects. As a design constraint, I'd like this particular script to stay one page and have no package dependencies. It's really for putting a coat of paint on a package that's almost exactly what you want. If I go beyond that, I will have lost the plot. A real interactive Package Maker I'd be happy to help with, but that's a different infrastructure.

Thank you so much for braving the rough edges!

That print shouldn't have been in there at all anymore and I suspect the line 346 problem was maybe a symptom of something else? (also what swift branch/OS are you on? I should test some others)

What was your path? Were you running "all" on an already downloaded repo? I've added a check for that now. In theory you should have been running "post", but as that is the most common situation I've added some logical defaults for no arguments.

As of 5 minutes ago, in a freshly downloaded repo, running the bare command should have it use the folder name as the new desired name, keeping the files in the current directory.

I also notice that you are running the script not from the same folder as the script, which honestly hadn't occurred to me as a path someone would take. The init uses the scripts PWD from a ProcessInfo call as a reference point for some things. I wonder if that is part of the problem. I'll run some tests.

The UI will be undergoing some more refactoring. It is repeated code if-tree hell at the moment! ArgumentParser has truly spoiled me! (in a good way)!

ETA: Realized that fix made no sense without a check to see if they had changed the name from "TemplatePackageToolLibrary"... also forgot to load the relevant "post" commands.

1 Like

Awesome! I'm going to try again later today.

OK, so I pulled the latest version of your repo (main branch). While compiling I now get this error:

setup.swift:107:29: error: no exact matches in call to initializer 
                newPrefix = URL(filePath: target).lastPathComponent
setup.swift:140:25: error: no exact matches in call to initializer 
            newPrefix = URL(filePath: target).lastPathComponent
setup.swift:349:13: error: value of type 'URL' has no member 'append'
        tmp.append(component: newName)

This is the Swift version I'm running (in GitHub Codespace).

Swift version 5.10 (swift-5.10-RELEASE)
Target: x86_64-unknown-linux-gnu

Codespaces can be a bit strange, so I'll try on macOS tonight as well.

1 Like

Ah, that one is tweaking a memory of a linux vs mac thing, IIRC. I used a fix in a different project and forgot it was needed. I'll switch boxes, fix, and let you know!

I can confirm that on macOS it does indeed work as expected. :tada:

I assume I could also use change line 8 let source = "https://github.com/carlynorama/TemplatePackageToolLibrary.git" to point to another repo to choose a different template? I.e. a template without the CLI/argument parser stuff?

1 Like

Yes! that is the idea! I suspect I will have more than one template repo. I can also make the "MyTool" phrase a variable. It would make sense to add a prompt when if source.isEmpty like the other items, but maybe not the phrase to replace?

I don't have a feel yet for what values need "run time" flexibility and which ones don't.

And of course, best would be to allow the knowledgable user to pass them in directly without all the chit-chat.

Just starting on Linux check now.

I have now run it on 5.10-RELEASE. I'm not sure if I picked the most future-proof syntax to use, but it works.

1 Like