In the build systems that I'm familiar with (make and msbuild) there's a way to specify the inputs and outputs for a target. If the time stamps on the input files are earlier than those on the outputs the task is skipped. I can't find something similar in FAKE.
For example if I wanted to translate this Makefile to Fake
a.exe: a.fs
fsharpc a.fs -o a.exe
it might look like:
Target "a.exe" (fun _ -> ["a.fs"] |> FscHelper.compile [...])
However, when I run the build command it will always execute the compiler and produce a new a.exe regardless the modification time on a.fs. Is there a simple way to get the same behavior as the makefile?
You could use =?>and provide a function that returns true or false if the task should run.
let fileModified f1 f2 =
FileInfo(f1).LastWriteTime > FileInfo(f2).LastWriteTime
and then in target dependencies
=?> ("a.exe", fileModified "a.fs" "a.exe")
A more complete code example to flesh out Lazydevs answer:
#r "packages/FAKE/tools/FakeLib.dll"
open Fake
open System.IO
Target "build" (fun _ ->
trace "built"
)
let needsUpdate f1 f2 =
let lastWrite files =
files
|> Seq.map (fun f -> FileInfo(f).LastWriteTime)
|> Seq.max
let t1 = lastWrite f1
let t2 = lastWrite f2
t1 > t2
let BuildTarget name infiles outfiles fn =
Target name (fn infiles)
name =?> ("build", needsUpdate infiles outfiles)
BuildTarget "compile" ["Test2.fs"; "Test1.fs"] ["Test2.dll"] (fun files _ ->
files
|> FscHelper.compile [
FscHelper.Target FscHelper.TargetType.Library
]
|> function 0 -> () | c -> failwithf "compile error"
)
RunTargetOrDefault "build"
Related
Hope everybody is having a beautiful 2019 even though we're just a day in.
I am currently working on a small Phoenix app where I'm manipulating PDF files (in the context of this question I'm splitting them) and then uploading them to S3. Later on I have to delete the temporary files created by pdftk ( a pdf tool ) I use to split them up and also show the s3 links in the response body since this is an API request.
The way I have structured this is as following:
Inside my Split module where the core business logic is:
filenames = []
s3_links = []
Enum.map(pages, fn(item) ->
split_filename = item
|> split(filename)
link = split_filename
|> FileHelper.result_file_bytes()
|> ManageS3.upload()
|> FileHelper.save_file(work_group_id, pass)
[filenames ++ split_filename, s3_links ++ link]
end)
|> transform()
{filenames, s3_links}
The important things are split_filename and link
This is what I'm getting when I call an IO.inspect in the transform() method:
[
["87cdcd73-5b27-4757-a472-78aaf6cc6864.pdf",
"Some_S3_LINK00"],
["0ab460ca-5019-4864-b0ff-343966c7d72a.pdf",
"Some_S3_LINK01"]
]
The structuring is [[filename, s3_link], [filename, s3_link]] whereas the desired outcome would be that of [ [list of all filenames], [list of s3 links].
If anybody can lend a hand I would be super grateful. Thanks in advance!
Sidenotes:
Assigning filenames = []; s3_links = [] in the very beginning makes zero sense. Enum.map already maps the input. What you need is probably Enum.reduce/3.
Don’t use the pipe |> operator when the pipe consists of the only call, it is considered an anti-pattern by Elixir core team.
Always start pipes with a term.
Solution:
Reduce the input into the result using Enum.reduce/3 directly to what you need.
pages
|> Enum.reduce([[], []], fn item, [files, links] ->
split_filename = split(item, filename)
link =
split_filename
|> FileHelper.result_file_bytes()
|> ManageS3.upload()
|> FileHelper.save_file(work_group_id, pass)
[[split_filename | files], [link | links]]
end)
|> Enum.map(&Enum.reverse/1)
|> IO.inspect(label: "Before transform")
|> transform()
You did not provide the input to test it, but I believe it should work.
Instead of working on lists of lists, you may want to consider using tuples with lists. Something like the following should work for you.
List.foldl(pages, {[], []}, fn(item, {filenames, links}) ->
filename = split(item, filename)
link =
file_name
|> FileHelper.result_file_bytes()
|> ManagerS3.upload()
|> FileHelper.save_file(work_group_id, pass)
{[filename | filenames], [link | links]}
end)
This will return a value that looks like
{
["87cdcd73-5b27-4757-a472-78aaf6cc6864.pdf",
"0ab460ca-5019-4864-b0ff-343966c7d72a.pdf"],
["Some_S3_LINK00",
"Some_S3_LINK01"]
}
Though, depending on how you are using these values, maybe a list of tuples would be more appropriate. Something like
Enum.map(pages, fn(item) ->
filename = split(item, filename)
link =
filename
|> FileHelper.result_file_bytes()
|> ManageS3.upload()
|> FileHelper.save_file(work_group_id, pass)
{filename, link}
end)
would return
[
{"87cdcd73-5b27-4757-a472-78aaf6cc6864.pdf", "Some_S3_LINK00"},
{"0ab460ca-5019-4864-b0ff-343966c7d72a.pdf", "Some_S3_LINK01"}
]
I need to build vb6 projects in order. How should I pass them to Vb6Make?
let projs = [
"a.vbp"
"b.vbp"
"c.vbp"
]
Target "VB6" (fun _ ->
!! projs // <- ?
|> Vb6Make
)
!! and ++ can be used for single files files as well. And FullName is important for directories.
let vb6dir = FullName "./bin"
let tempdir = FullName "./temp"
let projs =
!! "src\a\a.vbp"
++ "src\b\b.vbp"
++ "src\c\c.vbp"
Target "BuildVb6" (fun _ ->
projs
|> Fake.Vb6Helper.Vb6Make(fun c ->
{ c with
Logdir = tempdir
Outdir = vb6dir })
)
I have a target that looks like this:
Target "builddotnetcode" (fun _ ->
!! "../Mercury.sln"
|> MSBuildRelease null "Clean,Build"
|> Log "MercuryBuild - Output: "
)
I want to simply set the verbosity in there somewhere. As far as I can tell from the docs you need to specify the Verbosity member of the MSBuildParams object. But build is the only MSBuildHelper function that provides a way to pass a MSBuildParams. Using build I then need to specify Configuration=Release property, the project list, and remove the pipeline to the Log. It seems like there ought to be a simpler way that does not cause me to redefine the entire task. Am I missing something?
So what i did is the following. The reason i did is it this was as I want to create a log file per solution file that I am building
let loggerConfig : list<MSBuildFileLoggerConfig> = [
{
Number = 1
Filename = Some (baseDir + name + "_build.log")
Verbosity = Some MSBuildVerbosity.Minimal
Parameters = Some [MSBuildLogParameter.Append]
}
]
let setParams defaults =
{ defaults with
Verbosity = Some MSBuildVerbosity.Minimal
Targets = ["Build"]
MaxCpuCount = Some (Some 4)
FileLoggers = Some loggerConfig
ToolsVersion = Some "12.0"
Properties =
[
"Optimize", "True"
"DebugSymbols", "True"
"Configuration", buildMode
]
}
Lastly the only msbuild task that I could see that will let you override the msbuilddefaults was standard build.
build setParams solution
|> DoNothing
I am trying to build a solution where one of the projects needs to be build with the unsafe flag on, it is set correctly in the project however when building I get the error:
"Unsafe code may only appear if compiling with /unsafe"
This is my target at the moment
Target "CompileApp" (fun _ ->
!! #"**\*.csproj"
|> MSBuildRelease buildDir "Build"
|> Log "AppBuild-Output: "
)
I tried adding MsBuildParams but not sure how to use them yet (ie there doesnt seem to be an option in MsBuildRelease to add something like this
let setParams defaults =
{ defaults with
Verbosity = Some(Quiet)
Targets = ["Build"]
Properties =
[
"AllowUnsafeBlocks", "True"
"Configuration", "Release"
]
}
Also would the best option here be create two different targets for projects with safe and unsafe code, of would there be a better way?
I found that the AllowUnsafeBlocks=true element was only defined under the DEBUG|AnyCPU and Release|AnyCPU PropertyGroups in my project file.
Using this fixed it for me:
Target "BuildApp" (fun _ ->
!! ".\**\MyApp.*.csproj"
|> MSBuild buildDir "Build" ["Configuration", "Release"; "Platform", "AnyCPU"]
|> Log "AppBuild-Output: "
)
Hope this helps.
Ok I think this might be the way:
Target "CompileUnsafe" (fun _ ->
let buildMode = getBuildParamOrDefault "buildMode" "Release"
let setParams defaults =
{ defaults with
Verbosity = Some(Quiet)
Targets = ["Build"]
Properties =
[
"Optimize", "True"
"DebugSymbols", "True"
"Configuration", buildMode
"AllowUnsafeBlocks", "True"
]
}
build setParams "./ProjectPlugins.sln"
)
IF there are better solutions I'm all ears (the solution was there in the docs and I just missed it)
I'm having a surprising amount of difficulty getting the unit tests to run under cabal. I've copied the test code verbatim from the cabal documentation, with the exception of changing the module name
{-# LANGUAGE FlexibleInstances #-}
module Test.Integral ( tests ) where
import Distribution.TestSuite
instance TestOptions (String, Bool) where
name = fst
options = const []
defaultOptions _ = return (Options [])
check _ _ = []
instance PureTestable (String, Bool) where
run (name, result) _ | result == True = Pass
| result == False = Fail (name ++ " failed!")
test :: (String, Bool) -> Test
test = pure
-- In actual usage, the instances 'TestOptions (String, Bool)' and
-- 'PureTestable (String, Bool)', as well as the function 'test', would be
-- provided by the test framework.
tests :: [Test]
tests =
[ test ("bar-1", True)
, test ("bar-2", False)
]
However, when I try to build the tests, I get the following messages:
Test/Integral.hs:6:10:
Not in scope: type constructor or class `TestOptions'
Test/Integral.hs:12:10:
Not in scope: type constructor or class `PureTestable'
I tried importing them directly from Distribution.TestSuite, but it said that they weren't exported. This is simple enough that I have to be doing something stupid, but I can't see what it is.
But for what it's worth, here is some code that works:
module Main (tests) where
import Distribution.TestSuite
tests :: IO [Test]
tests = do
return [
test "foo" Pass
, test "bar" (Fail "It did not work out!")
]
test :: String -> Result -> Test
test name r = Test t
where
t = TestInstance {
run = return (Finished r)
, name = name
, tags = []
, options = []
, setOption = \_ _ -> Right t
}
There is not much support for detailed-0.9 out there. It's possible to hook up existing testing libraries to use it, but even then you will not get progress information as tests pass.
I recommend to use the exitcode-stdio-1.0 interface together with an existing testing framework + use GHCi during development.
A full example for Hspec is here https://github.com/sol/hspec-example.