-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathCreating-custom-project-file-for-F.html
492 lines (335 loc) · 24.9 KB
/
Creating-custom-project-file-for-F.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Creating custom project file for F#</title>
<meta name="description" content="Creating custom project file for F#">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/favicon/favicon.ico">
<link rel="apple-touch-icon-precomposed" href="/favicon/lattice-optic-illusion-152-204321.png" >
<meta name="msapplication-TileColor" content="#ffffff" />
<meta name="msapplication-TileImage" content="/favicon/lattice-optic-illusion-144-204321.png">
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Lato:400,400italic,700,700italic" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/styles/default.min.css">
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/font-awesome.min.css" />
<link rel="canonical" href="http://localhost:4000/Creating-custom-project-file-for-F">
<link rel="alternate" type="application/rss+xml" title="F# Reflections Feed" href="http://localhost:4000/feed.xml">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
</head>
<body>
<nav id="top" class="site-navigation">
<div class="inner">
<button id="menu-toggle" aria-expanded="false">Menu</button>
<div class="nav-menu">
<ul class="menu">
<li class="">
<a class="" href="/">Home</a>
</li>
<li class="">
<a class="" href="/about">About</a>
</li>
<li class="">
<a class="" href="/tags/">Archives</a>
</li>
<li class="">
<a class="" href="/2016">2016</a>
</li>
</ul>
</div><!-- .nav-menu -->
<div class="subscribe-button">
<a class="fa-feed square fill-horizontal" href="/feed.xml"><span class="screen-reader-text">RSS</span></a>
</div><!-- .social-links -->
</div><!-- .inner -->
</nav><!-- .site-navigation -->
<header class="site-header">
<div class="inner">
<h1 class="site-title"><a class="logo-text" href="/">F# Reflections</a></h1>
<p class="site-description">Thoughts, stories and ideas.</p>
</div><!-- .inner -->
</header>
<div class="site-content">
<div class="inner">
<main class="site-main">
<article class="post">
<header class="entry-header">
<div class="entry-header-wrap">
<div class="post-thumbnail">
<img src="/images/posts/landscape_4.jpg" alt="Creating custom project file for F#">
</div><!-- .post-thumbnail -->
<h1 class="entry-title">Creating custom project file for F#</h1>
</div><!-- .entry-header-wrap -->
<div class="entry-meta">
by <span class="post-author">Krzysztof Cieslak</span> on <time class="published" datetime="2016-06-23">June 23, 2016</time>
</div><!-- .entry-meta -->
</header><!-- .entry-header -->
<div class="entry-content">
<h1>Introduction</h1>
<p>Let’s imagine a world where F# is independent language, with cross-platform tooling making life of every F# developer easier. In this world, after we’ve fixed dependency management problem with Paket, we could go one step further… and fix project file format and building. We would use same principles as Paket - very simple, human readable, file format which can be edited without any other tooling, and command line tool responsible for building such project.</p>
<p>In such world I’ve decided to create new open-source project called <code>Chris</code></p>
<h1>File format</h1>
<p>For our hypothetical project file (called <code>project.chris</code>) I’ve decided to use <code>toml</code>. In this very simple file we would just specify few properties, files of our project, GAC references and external NuGet packages. It would look like as follows:</p>
<pre><code class="language-toml">[Info]
Name = "Fancy_Test_Project"
Author = "Lambda Factory"
Git = ""
[Stuff]
References = ["mscorlib", "System", "System.Core", "System.Numerics"]
Files = ["Message.fs", "Test.fs"]
Packages = []
</code></pre>
<!--more-->
<h1>Build</h1>
<p>Most important feature of every project system is ability to build project to executable version. As I’ve mentioned earlier one of the important points I’d like to address is ability to build project with command line with very simple commands. To build any F# project, in the end - no matter which project system we use, we have to execute F# Compiler with <a href="https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/compiler-options-%5bfsharp%5d">appropriate arguments</a> (defining references, different build options, and source files).</p>
<p>In this case I’ve created really simple console application which user runs in project directory and it builds project, and produce <code>.exe</code> file. To parse <code>toml</code> I’ve decided to use <a href="https://github.com/paiden/Nett">Nett library</a>.</p>
<p>First step is defining model representing our project, what is rather straightforward:</p>
<pre><code class="language-fsharp">type Info = {
Name : string
Author : string
Git : string
}
type Stuff = {
Files : string []
References : string []
Packages : string []
}
type Project = {
Info : Info
Stuff : Stuff
}
</code></pre>
<p>Next step is creating 2 helper functions - one loading <code>project.chris</code> and parsing it to our model, second one transforming this model to right F# Compiler arguments. Again, nothing complex.</p>
<pre><code class="language-fsharp">let load (path : string) =
let tmp = Toml.ReadFile path
let info = tmp.Get<TomlTable> "Info"
let stuff = tmp.Get<TomlTable> "Stuff"
{
Info = {
Name = info.Get<string>("Name")
Author = info.Get<string>("Author")
Git = info.Get<string>("Git") }
Stuff = {
Files = stuff.Get<string []>("Files")
References = stuff.Get<string []>("References")
Packages = stuff.Get<string []>("Packages") }
}, path
let toFSCParams (project, path) =
let folder = Path.GetDirectoryName path
let output = sprintf "%s.exe" project.Info.Name
[|
yield "-out:" + output
yield @"-r:C:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\4.3.1.0\FSharp.Core.dll"
for r in project.Stuff.References do
yield sprintf @"-r:C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\%s.dll" r
yield "--noframework"
for f in project.Stuff.Files do
yield Path.Combine (folder, f)
|]
</code></pre>
<p>Last step is creating function which runs F# Compiler. To do so we will use <a href="https://fsharp.github.io/FSharp.Compiler.Service/">FSharp.Compiler.Service</a> - .Net library that exposes functionality for implementing F# language bindings, additional tools based on the compiler or refactoring tools. What’s most important in this case it also let’s <a href="https://fsharp.github.io/FSharp.Compiler.Service/compiler.html">host F# compiler inside other application</a>.</p>
<pre><code class="language-fsharp">open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.SimpleSourceCodeServices
let build (project,path) =
let scs = SimpleSourceCodeServices()
let parm = (project, path) |> toFSCParams
parm |> scs.Compile
</code></pre>
<p>And at the end we pipe it together:</p>
<pre><code class="language-fsharp">[<EntryPoint>]
let main argv =
let errors, exit =
"project.chris"
|> Path.GetFullPath
|> load
|> build
exit // return an integer exit code
</code></pre>
<p>Now we can build our project!</p>
<p><img src="images/gifs/chris_build.gif" alt="Build with Chris" /></p>
<h1>Editor support</h1>
<p>Another important part of project system is providing information for F# Language Services which lets us to create rich editing environment across many different editors. Fortunatly FSharp.Compiler.Service is designed to be totally decoupled from project file format and requires its <a href="https://fsharp.github.io/FSharp.Compiler.Service/reference/microsoft-fsharp-compiler-sourcecodeservices-fsharpprojectoptions.html">custom type</a> to provide all nice <a href="https://fsharp.github.io/FSharp.Compiler.Service/editor.html">Language Services features</a>. Since we already have function loading <code>project.chris</code> to memory all we need to do is creating function which maps our type to type required by FCS.</p>
<pre><code class="language-fsharp">let toFSharpProjectOptions (project, path) =
let rsp = (project, path) |> toFSCParams
{
ProjectFileName = path
ProjectFileNames = [||]
OtherOptions = rsp
ReferencedProjects = [||]
IsIncompleteTypeCheckEnvironment = false
UseScriptResolutionRules = false
LoadTime = System.DateTime.Now
UnresolvedReferences = None;
}
</code></pre>
<p>Next step is actually adding support for <code>project.chris</code> to our editor of choice. It’s pretty easy for all editors and tools using FSharp.Compiler.Service in the background, but for sake of simplicity I’ve chosen <a href="https://code.visualstudio.com/">VS Code</a> with <a href="https://ionide.io">Ionide</a>. As you may know, VS Code is editor created using <a href="http://electron.atom.io/">Electron</a> which is framework for building cross-platform, desktop applications using HTML, JS, and CSS. Such choice has many advantages, but one bug important drawback from F# editor tooling point of view - we can’t directly use .Net libraries in VS Code. To communicate with .Net libraries (and especially with FSharp.Compiler.Service) we are using <a href="https://github.com/fsharp/FsAutoComplete">FsAutoComplete</a> - F# compiler service API exposed via a console application and HTTP server.</p>
<p>Adding <code>project.chris</code> support to FsAutoComplete is, again, fairly easy. First step is adding Chris project as reference to FsAutoComplete. Next we create helper function which will be executed if we try to load <code>project.chris</code> file - it’s basically copy-paste of <a href="https://github.com/fsharp/FsAutoComplete/blob/master/src/FsAutoComplete.Core/CompilerServiceInterface.fs#L189-L222">same functions</a> for <code>.fsproj</code> and <code>project.json</code></p>
<pre><code class="language-fsharp"> member x.TryGetChrisProjectOption (file : string) : Result<_> =
if not (File.Exists file) then
Failure (sprintf "File '%s' does not exist" file)
else
try
let po = file |> Chris.load |> Chris.toFSharpProjectOptions
let compileFiles = Seq.filter (fun (s:string) -> s.EndsWith(".fs")) po.OtherOptions
let outputFile = Seq.tryPick (chooseByPrefix "--out:") po.OtherOptions
let references = Seq.choose (chooseByPrefix "-r:") po.OtherOptions
Success (po, Seq.toList compileFiles, outputFile, Seq.toList references, Map<string,string>([||]))
with e ->
Failure e.Message
</code></pre>
<p>Last step is modifying <a href="https://github.com/fsharp/FsAutoComplete/blob/master/src/FsAutoComplete.Core/FsAutoComplete.Core.fs#L51-L55">handler for project request</a></p>
<pre><code class="language-fsharp">let options =
if file.EndsWith "fsproj" then
checker.TryGetProjectOptions(file, verbose)
elif file.EndsWith "chris" then
checker.TryGetChrisProjectOption file
else
checker.TryGetCoreProjectOptions file
</code></pre>
<p>Compile FSAC, copy modified version to Iondie plugin folder… and that’s all. Everything is working as always, just with <code>project.chris</code>.
<img src="images/gifs/chris_editor.gif" alt="Editor with Chris" /></p>
<h1>Frequently Asked Question</h1>
<p><em>Q: You haven’t mentioned VS at all, it surely means you hate it and ignore, right?</em></p>
<p>A: No, VS (with VFPT installed) is best F# IDE, but I don’t use it too much. Adding custom project file support to VS is definitely possible, it just requires knowledge I don’t have. Thus, I haven’t mentioned it in post.</p>
<p><em>Q: What about C# / F# interoperability, compatibility with existing F# projects?</em></p>
<p>A: In worst case scenario - we have compatibility on compiled code / package level ( I can imagine some workflow using Paket which would make it not too problematic). In “best” case - custom MsBuild task, but again, I don’t have enough knowledge to judge how possible it is.</p>
<p><em>Q: You’re just one of those elitist, arrogant guys thinking they are better because they use this new-overhyped thing - functional programming. You just hate MsBuild because it’s C#!</em></p>
<p>A: Yes, indeed. Move along. Nothing to see here.</p>
<p><em>Q: What about support for any other F# tooling such as FSharpLint?</em></p>
<p>A: Any tool using FSharp.Compiler.Service should be very easy to port, as presented above on FsAutoComplete example.</p>
<p><em>Q: Where is code, I want to use it now!</em></p>
<p>A: Code of this experiment is not published anywhere on purpose - I don’t want anyone to use it by any chance. It was just very simple proof of concept. Hopefully, one day F# Community will be ready for new project system.</p>
<p><em>Q: I <strong>really</strong> want to use something like that</em></p>
<p>A: Ask <a href="https://twitter.com/7sharp9_exhumed">Dave Thomas</a> about his Xebec project, and support <a href="https://twitter.com/7sharp9_exhumed/status/745995222178480128">fsprojexit</a></p>
<h1>Summary</h1>
<p>In this post I’ve presented possible ways of expanding F# development environment with new project system. I haven’t shown here any ready solutions for the problem but I hope that this post shows that having new, better project system is possible. We just need as Community to start reasonable discussion about such possibility.</p>
</div><!-- .entry-content -->
<footer class="entry-footer">
<div class="share-post">
<span>Share</span>
<a class="fa-twitter" target="_blank" href="https://twitter.com/intent/tweet?text=Creating%20custom%20project%20file%20for%20F#&url=http://localhost:4000/Creating-custom-project-file-for-F"><span class="screen-reader-text">Twitter</span></a>
<a class="fa-facebook" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=http://localhost:4000/Creating-custom-project-file-for-F&t=Creating%20custom%20project%20file%20for%20F#"><span class="screen-reader-text">Facebook</span></a>
<a class="fa-google-plus" target="_blank" href="http://plus.google.com/share?url=http://localhost:4000/Creating-custom-project-file-for-F"><span class="screen-reader-text">Google+</span></a>
</div><!-- .share-post -->
<div class="tag-links">
Tags: <a href="/tags/index.html#F%23" title="Pages tagged F#" rel="tag">F#</a><a href="/tags/index.html#VS+Code" title="Pages tagged VS Code" rel="tag">VS Code</a><a href="/tags/index.html#Tooling" title="Pages tagged Tooling" rel="tag">Tooling</a>
</div>
</footer><!-- .entry-footer -->
</article><!-- .post -->
<div class="author-box">
<div class="author-info">
<a href="http://kcieslak.io/about" target="_blank"> <div class="author-avatar" style="background-image: url(/images/authorimage.jpg)"><span class="screen-reader-text">Krzysztof Cieslak's Picture</span></div> </a>
<div class="author-details">
<h2 class="author-title">About Krzysztof Cieslak</h2>
<p class="author-bio">Krzysztof is a F# developer and consultant, open source contributor, conference speaker</p>
<span class="author-location"><i class="fa-map-marker" aria-hidden="true"></i> Lodz, Poland</span>
<span class="author-website"><i class="fa-chain" aria-hidden="true"></i> <a href="http://kcieslak.io" target="_blank"> http://kcieslak.io</a></span>
</div><!-- .author-details -->
</div><!-- .author-info -->
</div><!-- .author-box -->
<nav class="post-navigation">
<h2 class="screen-reader-text">Post navigation</h2>
<div class="nav-previous">
<div class="nav-inside">
<a href="/Getting-Started-with-Fable-and-Webpack" class="nav-thumb" style="background-image: url(/images/posts/landscape_3.jpg)"></a>
<div class="nav-before">Previous</div>
<div class="nav-title"><a href="/Getting-Started-with-Fable-and-Webpack">Getting started with Fable and Webpack</a></div>
<div class="nav-date">May 16, 2016</div>
</div><!-- .nav-inside -->
</div><!-- .nav-previous -->
<div class="nav-next">
<div class="nav-inside">
<a href="/Working-with-F-Projects-In-VSCode" class="nav-thumb" style="background-image: url(/images/posts/landscape_5.jpg)"></a>
<div class="nav-before">Next</div>
<div class="nav-title"><a href="/Working-with-F-Projects-In-VSCode">Working with F# projects in VSCode</a></div>
<div class="nav-date">July 27, 2016</div>
</div><!-- .nav-inside -->
</div><!-- .nav-next -->
</nav><!-- .post-navigation -->
</main><!-- .site-main -->
<aside class="sidebar">
<section class="widget widget-text">
<h2 class="widget-title">About Krzysztof Cieslak</h2>
<p>Krzysztof is a F# developer and consultant, open source contributor, conference speaker</p>
</section><!-- .widget-text -->
<section class="widget widget-text">
<p><a href="http://lambdafactory.io" target="_blank"><img src="/images/banner.png" alt="Optional Banner Ad" /></a></p>
</section><!-- .widget-text-->
<section class="widget widget-recent-posts">
<h2 class="widget-title">Latest Posts</h2>
<ul class="recent-posts">
<li class="recent-item"><a href="/F-Editor-Tooling-Part1">Behind the F# editor tootling. Part 1: introduction to compilers.</a> <span>August 27, 2019</span></li>
<li class="recent-item"><a href="/Ionide-Introducing-Info-Panel">Ionide — Introducing Info Panel</a> <span>May 6, 2019</span></li>
<li class="recent-item"><a href="/Ionide-New-Hope">Ionide — A New Hope</a> <span>March 24, 2019</span></li>
<li class="recent-item"><a href="/PureScript-on-Azure-Functions">PureScript on Azure Functions</a> <span>January 12, 2019</span></li>
<li class="recent-item"><a href="/Future-of-F-Cross-platform-editor-tooling">Future of F# cross-platform editor tooling</a> <span>December 31, 2018</span></li>
</ul><!-- .recent-posts -->
</section><!-- .widget-recent-posts -->
<!--Create a sorted array of tags-->
<section class="widget widget-tags">
<h2 class="widget-title">Tags</h2>
<div class="tagcloud">
<a href="/tags/#Azure">Azure</a>
<a href="/tags/#Azure+Functions">Azure Functions</a>
<a href="/tags/#Community">Community</a>
<a href="/tags/#Compiler">Compiler</a>
<a href="/tags/#F%23">F#</a>
<a href="/tags/#FAKE">FAKE</a>
<a href="/tags/#FSharp.Compiler.Services">FSharp.Compiler.Services</a>
<a href="/tags/#Fable">Fable</a>
<a href="/tags/#JS">JS</a>
<a href="/tags/#MVP">MVP</a>
<a href="/tags/#OAuth">OAuth</a>
<a href="/tags/#Open+Source">Open Source</a>
<a href="/tags/#Paket">Paket</a>
<a href="/tags/#PureScript">PureScript</a>
<a href="/tags/#Saturn">Saturn</a>
<a href="/tags/#Serverless">Serverless</a>
<a href="/tags/#Tooling">Tooling</a>
<a href="/tags/#VS+Code">VS Code</a>
<a href="/tags/#Web">Web</a>
<a href="/tags/#Webpack">Webpack</a>
</div><!-- .tagcloud -->
</section><!-- .widget -->
<section class="widget widget-text">
<h2 class="widget-title">Contact me</h2>
<a href="https://twitter.com/k_cieslak" class="fa-twitter square fill-horizontal"><span class="screen-reader-text">Twitter</span></a>
<a href="https://github.com/Krzysztof-Cieslak" class="fa-github-alt square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="https://gitlab.com/Krzysztof-Cieslak" class="fa-gitlab square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="mailto:[email protected]" class="fa-envelope square fill-horizontal"><span class="screen-reader-text">E-Mail</span></a>
</section><!-- .widget-text -->
</aside><!-- .sidebar -->
</div><!-- .inner -->
</div><!-- .site-content -->
<footer class="site-footer">
<div class="inner">
<div class="social-links">
<a href="https://twitter.com/k_cieslak" class="fa-twitter square fill-horizontal"><span class="screen-reader-text">Twitter</span></a>
<a href="https://github.com/Krzysztof-Cieslak" class="fa-github-alt square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="https://gitlab.com/Krzysztof-Cieslak" class="fa-gitlab square fill-horizontal"><span class="screen-reader-text">GitHub</span></a>
<a href="mailto:[email protected]" class="fa-envelope square fill-horizontal"><span class="screen-reader-text">E-Mail</span></a>
<a class="fa-feed square fill-horizontal" href="/feed.xml"><span class="screen-reader-text">RSS</span></a>
</div><!-- .social-links -->
<div class="site-info">
© <a href="/">Krzysztof Cieslak</a> all rights reserved. <br />Powered by <a target="_blank" href="https://jekyllrb.com/">Jekyll</a>. BlogInn theme by <a target="_blank" href="http://justgoodthemes.com/">JustGoodThemes</a>
<a href="#top" id="top-link" class="fa-chevron-up top-link square fill-horizontal"><span class="screen-reader-text">Back to the top</span></a>
</div><!-- .site-info -->
</div><!-- .inner -->
</footer><!-- .site-footer -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/highlight.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/languages/fsharp.min.js"></script>
<script type="text/javascript" src="/js/plugins.js"></script>
<script type="text/javascript" src="/js/custom.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<!-- Asynchronous Google Analytics snippet -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-74514389-1', 'auto');
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');
</script>
</body>
</html>