Requiring a module using its path from the project's root? - racket

I'm making a web application. Consider such structure
ProjectName/racket/Servlet.rkt
ProjectName/racket/chart/barchart/BarChart.rkt
ProjectName/template/barchart.svg
How can I inside the BarChart.rkt module require the Barchart.svg template, without using any ..? Ideally, if the application is launched from ProjectName (i.e. cd ProjectName; racket racket/Servlet.rkt), the require part would resemble (require "template/barchart.svg"), but use ProjectName as a root instead of the relative ProjectName/racket/barchart.

1) Naive method
You can use (define-runtime-path):
ProjectName/chart/barchart/BarChart.rkt:
#lang racket/base
(provide barchart-template)
(require
racket/runtime-path
racket/file)
(define-runtime-path barchart.svg "../../template/barchart.svg")
(define (barchart-template)
(file->string barchart.svg))
ProjectName/Servlet.rkt:
#lang racket/base
(require ProjectName/chart/barchart/Barchart)
(displayln (barchart-template)) ;; prints the content of the SVG file, wherever you are
2) Better method
Registering your package
From my experience, the best way to handle paths when developing a Racket application is making a package, then use the classic (require my-package/my-module) syntax instead of using relative paths.
For example, if you have a project like this:
ProjectName/Servlet.rkt
ProjectName/chart/barchart/BarChart.rkt
ProjectName/template/barchart.svg
By adding an info.rkt file at the root of your project, you transform it into a package.
echo "#lang info" > ProjectName/info.rkt
Then call cd ProjectName; raco pkg install.
Then you can require BarChart.rkt in any file with (require ProjectName/chart/BarChart).
Why am I telling you all this? Because now, you can start your application from any folder:
racket -l ProjectName/Servlet
Which will allow you to test easily if your paths are handled whatever the directory you are running the program in.
Getting rid of ../..
Now that your package is registered in your local database, you can easily find it's root directory using (pkg-directory) from pkg/lib:
ProjectName/chart/barchart/BarChart.rkt:
#lang racket/base
(provide barchart-template)
(require
racket/file
pkg/lib)
(define (barchart-template)
(define template.svg (build-path (pkg-directory "ProjectName")
"template/barchart.svg"))
(file->string template.svg))

Related

ASDF not finding package in custom directory

I'm new to Common Lisp, I'm using Emacs/SLIME on Windows 10, and I'm trying to wrap my head around how CL and ASDF/packaging works.
I have a custom package 'my-pack' in a directory 'D:\Dropbox\my-packages'.
I have created a .conf file in:
%LOCALAPPDATA%\config\common-lisp\source-registry.conf.d\
And added this line:
(:tree "D:\\Dropbox\\my-packages\\")
I opened Emacs, started SLIME and made the project via the REPL:
(cl-project:make-project #p"D:/Dropbox/my-packages/my-pack)
I verified that the project is in the directory and then loaded the system with asdf (version 3.3.1):
(asdf:load-system :my-pack)
And it worked fine.
But when I quit and restart Emacs, I get the following error when trying to the load the system:
Component :MY-PACK not found
[Condition of type ASDF/FIND-COMPONENT:MISSING-COMPONENT]
As far as I can tell I've followed the steps in the manual. Any help appreciated.
cl-project's make-project ends with this line:
(push dir asdf:*central-registry*)
it adds your new project's directory to this list that tells ASDF where to look for projects. What is its value when you restart CL?
2.
\config\common-lisp\
Shouldn't it be .config?
However, I don't encourage to use this conf file with :tree. The doc says:
tell ASDF to recursively scan all the subdirectories
So, imagine that just once, you try yourself at web development and you install, for example, JavaScript dependencies with npm or equivalent, you'll end up with a gigantic node_modules/ and your Lisp will now take a few seconds to start up.
I suggest to put your projects under ~/common-lisp/ or ~/quicklisp/local-projects, or to create symlinks, or to add yourself your projects in asdf:*central-registry* from your Lisp startup file:
;; .sbclrc
(pushnew "/home/me/projects/ciel/" asdf:*central-registry* :test #'equal)

Providing struct constructor from library

I'm currently working on extending racklog, which is a library I installed by running raco pkg install in the repo directory.
I'm trying to provide a new function, namely a struct constructor. Currently, I'm defining the struct as follows in racklog.rkt. I then provide it from that file.
; racklog.rkt
(struct my-struct (value))
(provide my-struct)
The main file of the library just provides everything from this file:
; main.rkt
(require "racklog.rkt")
(provide (all-from-out "racklog.rkt"))
However, when I try to use the provided constructor (which should be named my-struct) in a file requiring this module, it says that the id isn't found. In particular, I'm trying:
; test.rkt
(require racklog)
my-struct
This also happens even with non-struct things such as defined variables, functions, etc. All the other provided forms seem to be working fine. What's the way I can fix this so I can use the provided constructor? Thanks!
This is an unfortunate stale compiled file issue. Try raco setup --pkgs racklog to compile racklog and run your program again. It should now work. Alternatively, you can manually delete the compiled directories.

On Using clojure.tools.namespace

In emacs cider repl, now I know how to use clojure.tools.namespace in a leiningen project. However, when I use it on a single clj file which doesn't belong to any project, it seems clojure.tools.namespace doesn't work on the file:
=> #<FileNotFoundException java.io.FileNotFoundException: Could not locate com/foo__init.class or com/foo.clj on classpath: >
I have declared clojure.tools.namespace in .lein/profile.clj and require it in the clj file. How should I make clojure.tools.namespace work on a single clj file?
My profile.clj
{:user
{:repl-options {:timeout 128000}
:plugins [;; REPL
[cider/cider-nrepl "0.9.0-SNAPSHOT"]
[refactor-nrepl "0.2.2"]
;; Application server
[lein-immutant "2.0.0-SNAPSHOT"]
;; Automated testing
[lein-cloverage "1.0.2"]
[lein-test-out "0.3.1"]
;; Package management
[lein-ancient "0.6.2"]
[lein-clojars "0.9.1"]
;; Documentation
[codox "0.6.8"]
[lein-clojuredocs "1.0.2"]
;; Static analysis
[lein-typed "0.3.4"]
;; [jonase/eastwood "0.1.2"]
[lein-bikeshed "0.1.6"]
[lein-kibit "0.0.8"]]
:jvm-opts ["-Dapple.awt.UIElement=true"]
:dependencies [[org.clojars.gjahad/debug-repl "0.3.3"]
[difform "1.1.2"]
[spyscope "0.1.4"]
[org.clojure/tools.trace "0.7.8"]
[org.clojure/tools.namespace "0.2.9"]
[im.chit/vinyasa "0.2.0"]
[slamhound "1.5.5"]
[criterium "0.4.3"]]
:injections [(require 'spyscope.core)
(require 'alex-and-georges.debug-repl)
(require 'com.georgejahad.difform)
(require '[vinyasa.inject :as inj])
(inj/inject 'clojure.core '>
'[[clojure.repl apropos dir doc find-doc pst source]
[clojure.tools.trace trace trace-forms trace-ns trace-vars
untrace-ns untrace-vars]
[clojure.test run-tests run-all-tests]
[clojure.pprint pprint pp]
[com.georgejahad.difform difform]
[alex-and-georges.debug-repl debug-repl]
[vinyasa.pull pull]])]}}
While this doesn't answer your specific question, it might provide some ideas on
alternative workflows which may help.
lein-exec This is a lein plugin that
lets you write clojure scripts or evaluate clojure expressions on the command line
in a similar way to what can be done with shells, python, ruby, perl etc. Of
course, startup time is a bit high, but this plugin will help you deal with
dependencies etc inside your single clj script file. (there are other possible
solutions to improve the startup speed). See this blog post for some examples
http://charsequence.blogspot.in/2012/04/scripting-clojure-with-leiningen-2.html
A scratch project. I have a fairly generic project called scratch. It just allows
me to create a new file within the src directory of the project which I can use for
experiments or demonstrations. For example, I have a file called stackoverflow.clj
within this scratch project which I use when working out the answer to a clojure
question on stack overflow. In fact, I have a lot of individual clj files in this
directory, each just representing a simple idea, test, experiment etc. It isn't
really a project i.e. I couldn't do a lein run at the root of the project and
expect anything meaningful. However, I can go to the source directory in emacs,
open a file and then run cider.

Can't use lisp packages defined in a system

I was trying to make executable file using lisp code. But I can't compile lisp file at all because there is no hellowolrd package before loading helloworld system
;; test.lisp
(asdf:load-system :helloworld)
(defun main()
(helloworld:start))
Of course, I made the helloworld system and put It in ~/quicklisp/local-projects/. helloworld system is successfully loaded without errors.
;; ~/quicklisp/local-projects/helloworld/helloworld.asd
(asdf:defsystem helloworld
:version "1.0"
:components ((:file "package")))
;; ~/quicklisp/local-projects/helloworld/package.lisp
(defpackage :helloworld
(:use :common-lisp :asdf)
(:export :start))
(in-package :helloworld)
(defun start()
(format t "Welcome, ASDF"))
I want to compile test.lisp without explicit loading. I also tried use-package and defpackage but failed.
;; test.lisp
(asdf:load-system :helloworld)
(use-package :helloworld)
(defun main()
(helloworld:start))
;; test.lisp
(asdf:load-system :helloworld)
(defpackage :test
(:use :cl :asdf)
(:export :main))
(in-package :test)
(defun main()
(helloworld:start))
How can I use helloworld package defined in helloworld system without loading it?
Should I have to make a new system using helloworld system?
In this code, there's something interesting going on:
;; test.lisp
(asdf:load-system :helloworld)
(defun main()
(helloworld:start))
You can't compile it as a whole because, as you've noted trying to read the symbol hellowworld:start is a problem, because there's no helloworld package yet. To read the symbol, you at least need to have the package defined. But then, why don't we get the same problem with (asdf:load-system :helloworld)? Simply, the ASDF package has already been defined (either the implementation includes it, or you loaded it already, or something else. One thing you could do, then, is to make sure at compilation time that you've already loaded your helloworld system:
;; test.lisp
(eval-when (:compile-toplevel)
(asdf:load-system :helloworld))
(defun main()
(helloworld:start))
That should let you compile the file; since you'll evaluate the loading form when compiling, and then the package will be defined by the time you define main.
Of course, now you'll have a compiled file, but what will happen if you load it into a fresh instance of Lisp where the helloworld system hasn't been loaded? You'll have a problem. So you really want to load that system when you load the file too, and probably if you're just executing forms from the file too (e.g., if you were reading forms one at a time and evaluating them). So you probably want to evaluate that load-system in two more contexts:
;; test.lisp
(eval-when (:compile-toplevel :load-toplevel :execute)
(asdf:load-system :helloworld))
(defun main()
(helloworld:start))
All that said, be sure to consider whether this is the most appropriate way to load your system in this case. It may make sense if, for some reason, you're trying to keep all the code in one file, or making a delivery script. If, on the other hand, you're making another ASDF loadable system, then you'd probably just include helloworld as a dependency and let ASDF handle loading the dependencies.
1- To have a file that can be both compiled and loaded, use eval-when. See my article http://fare.livejournal.com/146698.html
2- I recommend using cl-launch:
cl-launch -sp helloworld -r start -o helloworld -d !
3- If you don't want to waste 60MB in space, but instead can stand half a second to a few seconds of startup latency, use a #!/usr/bin/cl script.
If you want to compile a binary, you can use a simple Lisp script for that, e. g. (assuming SBCL)
;;;; build.lisp
(require "asdf")
(asdf:operate 'asdf:load-op "helloworld")
(sb-ext:save-lisp-and-die "helloworld"
:toplevel helloworld:start
:executable t)
Makefile:
all:
sbcl --load build.lisp
When loading a file, Lisp consequently executes its forms, so by the time it arrives at save-lisp-and-die it already knows about the helloworld package.
There are also dedicated tools for making executables, buildapp and cl-launch. The latter can produce executable scripts and work with saved cores.
UPD
Yes, the executable built in this way will contain the whole Lisp, so 1) it will be huge; 2) having two or more such executables means that you've got (probably superfluous) copies of the whole Lisp.
Another implementation can produce smaller binaries, e. g. CLISP; they say commercial ones are even better.
For Lisp-less target computers this is the only possible solution. On the contrary, if a target computer has a Lisp compiler, there are are two other options: scripts and saved cores.
You can make a shell script invoking sbcl --load <file>or use cl-launch to obtain a sophisticated shell wrapper, which can be as portable as you want it to. SBCL and CLISP support shebang, too.
However, if startup time matters, loading libraries will be a bottleneck, even though they are compiled only once. In this case you can work around by using a custom core, or image. A core can be thought of as a saved state of the Lisp world, and saving a core is the primary task of sb-ext:save-lisp-and-die and its counterparts. Loading a core is fast, and you immediately have all the libraries compiled in it. But then again the size becomes considerable, though not as huge as in the case of a standalone executable. Naturally, one core can be used for several applications. Cl-launch can work with cores as well; in particular, it allows to conveniently define the entry function.

Invoking makefile in the project root directory from subdirectory from Emacs

I have a makefile in the project root directory. If I am editing a file in a subdirectory, how do I invoke make from EMACS? M-x compile make will not work as it looks for the makefile in the current directory. But I have the makefile in the project root directory.
Any thoughts?
Edit
As suggested, make -f fullpath_to_makefile did the trick. But I have some includes in the makefile like include "tests/module.mk" which failed. It is looking for "tests" directory in the subdirectory. This can be solved by specifying fully qualified path in the makefile. But I don't think that is a good solution. Any better approaches?
The M-x compile function looks up the compile-command-variable which you can override on the promt -- so just replace it with something like
(cd ../.. && make && cd -)
and let that run.
I also often use a file header (in line 1) such as
// -*- compile-command: "g++ -o myprog myprog.ccc -lfoo -lbar && ./myprog"; -*-
which you can generalize at will with different options. After reloading the file, M-x compile will execute your custom compile command which I find quite useful.
(I use scons, but the principle is the same. Change SConstruct to Makefile and scons to make...)
I've customized by .emacs so that it always compiles the project containing the current buffer's file, however deeply nested; it searches upwards for the first SConstruct and uses that as it's project root directory.
Here's a couple of functions which search up the directory hierarchy looking for SConstruct.
;; inspired by jds-find-tags-file in http://www.emacswiki.org/emacs/EmacsTags
(defun find-sconstruct ()
"recursively searches upwards from buffer's current dir for file named SConstruct and returns that dir. Or nil if not found or if buffer is not visiting a file"
(labels
((find-sconstruct-r (path)
(let* ((parent (file-name-directory path))
(possible-file (concat parent "SConstruct")))
(cond
((file-exists-p possible-file)
(throw 'found-it possible-file))
((string= "/SConstruct" possible-file)
(error "No SConstruct found"))
(t (find-sconstruct-r (directory-file-name parent)))))))
(if (buffer-file-name)
(catch 'found-it
(find-sconstruct-r (buffer-file-name)))
(error "Buffer is not visiting a file"))))
(defun project-root ()
(file-name-directory (find-sconstruct)))
You can then change your compile-command to use project-root e.g.
(concat "cd " (project-root) " && scons")
I use EDE (from CEDET) to define projects, and store compilation commands in the project definition. Look to my config for examples: lines 105-133 -- examples of projects, lines 135-165 -- code, that defines compilation functions, and lines 168-189 -- functions for different kinds of projects -- standard (compile from root directory), and cmake (compilation in separate directory)
Another alternative is to set the variable compilation-process-setup-function which is documented as:
Function to call to customize the compilation process. This function
is called immediately before the compilation process is started. It
can be used to set any variables or functions that are used while
processing the output of the compilation process. The function is
called with variables compilation-buffer' andcompilation-window'
bound to the compilation buffer and window, respectively.
I use Maven alot and wrote this library to support your issue for a Maven context. In the following, change the value of the variable compile-search-file as appropriate:
;;; Support for Maven 2
(require 'compile)
(setq compile-search-file "pom.xml")
(defun find-search-file ()
;; Search for the pom file traversing up the directory tree.
(setq dir (expand-file-name default-directory))
(let ((parent (file-name-directory (directory-file-name dir))))
(while (and (not (file-readable-p (concat dir compile-search-file)))
(not (string= parent dir)))
(setq dir parent
parent (file-name-directory (directory-file-name dir))))
(if (string= dir parent)
(error "Search file %s is missing" compile-search-file)
(with-current-buffer compilation-last-buffer
(message "Test %s %s." compilation-buffer compilation-window)
(setq default-directory dir)))))
;; Add the following to support Emacs' compile mode:
(add-to-list
'compilation-error-regexp-alist-alist
'(mvn "^\\(.*\\):\\[\\([0-9]*\\),\\([0-9]*\\)\\]" 1 2 3))
(add-to-list 'compilation-error-regexp-alist 'mvn)
(setq compilation-process-setup-function 'find-search-file)
(provide 'maven-support)
When I use Emacs to compile the minibuffer looks something like this;
make -k
and I just add any text I like, such as -f ../../root/Makefile. This might work for you.
Mark
I'm not much of an emacs user, but could you pass make -C ../ or however many directories up you need to go?
After a while of different attempts to make EDE work the way I wanted, I went for .dir-locals.el:
((c++-mode . ((compile-command . "make -C ../build -j2 whatever"))))
I found it slightly better for me than having a // -*- -*- in a header of every file,
and a whole lot better than specifying in my init.el (or any other config) those ede-cpp-root-project with full paths to projects, which I either create too often or move all of a sudden :)
Nice addition to the scheme was cmake which makes compile errors to be 'properly jumpable' since it uses full paths in generated makefiles.
I've just started working on a more generic, extensible, and robust yet still reasonably quick-and-dirty solution I just created. It's somewhat based on the Maven example above but I prefer not mucking around with global variables so I use the let or let* special forms a lot more. And of course it uses make.
Right now it only supports Makefiles but you can add a clause to the (cond) special form in the `my-compilation-process-setup-function' function if you want to support one or more additional different build systems.
It even has doc strings!
You'll see it at my Github eventually.
This should do it:
make -f path_to_rootdir/Makefile -I path_to_rootdir/tests
The -f tells it what makefile to use, the -I tells it where to look for files to be included in the makefile (that aren't in the local directory).
Start with M-x compile RET. When it prompts for a command, just enter cd /path/to/root && make and hit return. This command works for all variants of make and handles the "included makefile" problem without any extra flags.
The next time you type M-x compile RET, this new string will be presented as the default, so you only have to hit return. On the off chance that you're actively compiling multiple projects within emacs, you can use M-p and M-n to move backwards and forwards through the history of compile comamnds.