Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command line creation of OSX app bundles #1

Open
nanotech opened this issue Feb 9, 2017 · 9 comments
Open

Command line creation of OSX app bundles #1

nanotech opened this issue Feb 9, 2017 · 9 comments

Comments

@nanotech
Copy link
Owner

nanotech commented Feb 9, 2017

Modify or create a method so that a programmer can compile to a Mac app bundle from the command line simply by modifying their .cabal, Setup.hs, and stack.yaml files on a mac with a fresh install of OSX Sierra and XCode 8.2. It cannot rely on deprecated frameworks like Carbon. The project must be able to integrate common libraries such as SDL2 and OpenCL, add resources to the package and the resulting package must launch from the finder and from the command line open command. It must integrate into a stack based as well as cabal based workflow. (link)

cabal-macosx does almost all of this already, so that's what I've used in this sample project. It works with both cabal and stack. The missing pieces were:

  • Cabal custom-setup support in stack. This actually was implemented just recently, but is not in a released version yet. The workaround meanwhile is to add the setup dependencies to the main library's dependency list and add explicit-setup-deps to the stack.yaml.

  • Rez is no longer needed to build modern Mac apps and can just be totally removed from the build process.

  • In LSOpenURLsWithRole() failed with error -10810 for the file..., -10810 is listed as an unknown internal error, but in my experience it has meant that the program has crashed before it has registered with launch services. This can be because of the difference in environment between running from the command line and from launch services (open and Finder). Running the program from the command line inherits the current working directory and environment variables, while running from open or Finder starts with a standard environment and a different current working directory unrelated to your shell's. File assets accessed by relative path, or expecting your shell's PATH when running subprocesses are typical culprits. Standard output and error are written to the Console app when run from launch services, which may help debug this.

    As I don't have your program, I can't determine if this is the exact cause, but whether this sample project runs will be a good indicator.

Continuing with the requirements:

  • Aside from documentation and tests, the only other mention or use of Carbon in cabal-macosx is in the LSRequiresCarbon key in the default plist, however the documentation for it says

    Note: The use of the word Carbon in the names LSRequiresCarbon and LSPrefersCarbon is misleading, since these keys actually signify that the application requires or prefers to run natively in MacOSX, irrespective of whether it is specifically Carbon-based; in particular, LSRequiresCarbon can apply equally well to Cocoa or Carbon applications.

    So whether it's there or not should not matter at all on any recent OS version.

  • Integrating common libraries like SDL2 and OpenCL is mostly a matter of chasing their shared library dependencies and including them in the bundle, which cabal-macosx does.

  • I haven't added resources aside from an icon to this sample project, but it is supported by cabal-macosx.

@biglambda
Copy link

I think your theory about the different environment is most likely correct. I load some openCL kernel code from a file for example. How can I see the normal error output of my code when I run it with open? How can I detect that I'm running from inside an app bundle and find the proper resources from within haskell?

@biglambda
Copy link

I cloned your repository and I get the following output from stack build

`hs-sdl-mac-0.1.0.0: configure
[1 of 1] Compiling Main ( /Users/biglambda/Software/hs-sdl-mac/Setup.hs, /Users/biglambda/Software/hs-sdl-mac/.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/setup/Main.o )
Linking /Users/biglambda/Software/hs-sdl-mac/.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/setup/setup ...
Configuring hs-sdl-mac-0.1.0.0...
hs-sdl-mac-0.1.0.0: build
Preprocessing executable 'hs-sdl-mac' for hs-sdl-mac-0.1.0.0...
[1 of 1] Compiling Main ( src/Main.hs, .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac/hs-sdl-mac-tmp/Main.o )
Linking .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac/hs-sdl-mac ...
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: can't open input file: .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib for writing (Permission denied)
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: can't lseek to offset: 0 in file: .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib for writing (Bad file descriptor)
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: can't write new headers in file: .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib (Bad file descriptor)
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: can't close written on input file: .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib (Bad file descriptor)
Creating application bundle directory .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app
setup: Pattern match failure in do expression at
Copying executable hs-sdl-mac into place from .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac/hs-sdl-mac to .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/MacOS/hs-sdl-mac
Distribution/MacOSX/Dependencies.hs:219:6-16
Copying icon.icns to app's icon
Building dependency graph
Copying /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib to .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib
Copying /usr/lib/libiconv.2.dylib to .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/lib/libiconv.2.dylib
Updating .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/MacOS/hs-sdl-mac's dependency on /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib to @executable_path/../Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib
Updating .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/MacOS/hs-sdl-mac's dependency on /usr/lib/libiconv.2.dylib to @executable_path/../Frameworks/usr/lib/libiconv.2.dylib
Updating .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib's dependency on /usr/lib/libiconv.2.dylib to @executable_path/../Frameworks/usr/lib/libiconv.2.dylib
Completed 29 action(s).

-- While building package hs-sdl-mac-0.1.0.0 using:
/Users/biglambda/Software/hs-sdl-mac/.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/setup/setup --builddir=.stack-work/dist/x86_64-osx/Cabal-1.24.0.0 build exe:hs-sdl-mac --ghc-options " -ddump-hi -ddump-to-file"
Process exited with code: ExitFailure 1`

@nanotech
Copy link
Owner Author

How can I see the normal error output of my code when I run it with open?

From my first post:

Standard output and error are written to the Console app when run from launch services, which may help debug this.

The console app is at /Applications/Utilities/Console.app. Select your Mac in the sidebar (or All Messages prior to 10.12), and filter by the name of your app.

How can I detect that I'm running from inside an app bundle and find the proper resources from within haskell?

Use getExecutablePath from executable-path to build a path to your resources. If you need to check if the program is running from inside a bundle, check if something like getExecutablePath >>= \exePath -> doesDirectoryExist (takeDirectory (takeDirectory exePath) </> "MacOS") is true.

error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/install_name_tool: can't open input file: .stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib for writing (Permission denied)

Homebrew removes write access to the files it installs, and cabal-macosx is copying permissions when it copies the files in (it probably shouldn't). As a workaround, run

chmod +w /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib

and re-run stack build. I'll have a look at patching cabal-macosx to not copy permissions.

@nanotech
Copy link
Owner Author

I've patched cabal-macosx to set the writable bit before running install_name_tool on libraries, and updated the dependency here.

@biglambda
Copy link

Ok, that's working. I'm working on the executable-path stuff, I'll let you know how that goes.

@biglambda
Copy link

I think I almost have this working, unfortunately the when I try to add a file to the resources list field of the MacApp structure it does not seem to be able to find that file regardless of trying multiple places including the root directory of my project. Any ideas on this?

@nanotech
Copy link
Owner Author

What's the error message? As a test, when I add README.md to the resources field for this project,

import Distribution.MacOSX
import Distribution.Simple

main :: IO ()
main = defaultMainWithHooks $ simpleUserHooks {
         postBuild = appBundleBuildHook guiApps -- no-op if not MacOS X
       }

guiApps :: [MacApp]
guiApps = [MacApp "hs-sdl-mac"
                  (Just "icon.icns")
                  Nothing -- Build a default Info.plist for the icon.
                  ["README.md"] -- Resources.
                  [] -- No other binaries.
                  ChaseWithDefaults
          ]

it builds as

Configuring hs-sdl-mac-0.1.0.0...
Building hs-sdl-mac-0.1.0.0...
Preprocessing executable 'hs-sdl-mac' for hs-sdl-mac-0.1.0.0...
Creating application bundle directory dist/build/hs-sdl-mac.app
Copying executable hs-sdl-mac into place from dist/build/hs-sdl-mac/hs-sdl-mac to dist/build/hs-sdl-mac.app/Contents/MacOS/hs-sdl-mac
Copying icon.icns to app's icon
Copying resource README.md to dist/build/hs-sdl-mac.app/Contents/Resources/README.md
Building dependency graph
Copying /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib to dist/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib
Copying /usr/lib/libiconv.2.dylib to dist/build/hs-sdl-mac.app/Contents/Frameworks/usr/lib/libiconv.2.dylib
Updating dist/build/hs-sdl-mac.app/Contents/MacOS/hs-sdl-mac's dependency on /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib to @executable_path/../Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib
Updating dist/build/hs-sdl-mac.app/Contents/MacOS/hs-sdl-mac's dependency on /usr/lib/libiconv.2.dylib to @executable_path/../Frameworks/usr/lib/libiconv.2.dylib
Updating dist/build/hs-sdl-mac.app/Contents/Frameworks/usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib's dependency on /usr/lib/libiconv.2.dylib to @executable_path/../Frameworks/usr/lib/libiconv.2.dylib

In particular,

Copying resource README.md to dist/build/hs-sdl-mac.app/Contents/Resources/README.md

And opening the Resources directory shows README.md being there.

@biglambda
Copy link

Got it, whitespace at the end of the string was tripping it up. Do you have a minute to get on Skype to talk about completing the bounty?

@nanotech
Copy link
Owner Author

Great! I don't use Skype, but you can email me at nanotech@nanotechcorp.net and we can talk there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants